<2021SC@SDUSC>开源游戏引擎Overload代码分析三(OvWindowing结束):OvWindowing——Dialogs

2021SC@SDUSC

前言

这是Overload引擎相关的第五篇文章,同时也是OvWindowing分析的第三篇。Overload引擎的Github主页在这里。
本篇文章主要会介绍OvWindowing中Dialogs文件夹所包含的h和cpp文件,同时会把内容比较少,比较简单Inputs文件夹内的文件一并介绍,就算是把OvWindowing中剩余的内容全部讲完了。

Dialogs

首先我们来讲Dialogs,顾名思义,这个文件夹下的文件都是和对话框有关的,我们把h文件和cpp文件一对一的讲。

一、FileDialog

FileDialog.h

我们先看头文件,头文件代码如下:

	/**
	* Some flags that can be passed to FileDialog instances
	*/
	enum class EExplorerFlags
	{
		READONLY                 = 0x00000001,
		OVERWRITEPROMPT          = 0x00000002,
		HIDEREADONLY             = 0x00000004,
		NOCHANGEDIR              = 0x00000008,
		SHOWHELP                 = 0x00000010,
		ENABLEHOOK               = 0x00000020,
		ENABLETEMPLATE           = 0x00000040,
		ENABLETEMPLATEHANDLE     = 0x00000080,
		NOVALIDATE               = 0x00000100,
		ALLOWMULTISELECT         = 0x00000200,
		EXTENSIONDIFFERENT       = 0x00000400,
		PATHMUSTEXIST            = 0x00000800,
		FILEMUSTEXIST            = 0x00001000,
		CREATEPROMPT             = 0x00002000,
		SHAREAWARE               = 0x00004000,
		NOREADONLYRETURN         = 0x00008000,
		NOTESTFILECREATE         = 0x00010000,
		NONETWORKBUTTON          = 0x00020000,
		NOLONGNAMES              = 0x00040000,	// force no long names for 4.x modules
		EXPLORER                 = 0x00080000,	// new look commdlg
		NODEREFERENCELINKS       = 0x00100000,	
		LONGNAMES                = 0x00200000,	// force long names for 3.x modules
		ENABLEINCLUDENOTIFY      = 0x00400000,	// send include message to callback
		ENABLESIZING             = 0x00800000,	
		DONTADDTORECENT          = 0x02000000,	
		FORCESHOWHIDDEN          = 0x10000000	// Show All files including System and hidden files
	};

	inline EExplorerFlags operator~ (EExplorerFlags a) { return (EExplorerFlags)~(int)a; }
	inline EExplorerFlags operator| (EExplorerFlags a, EExplorerFlags b) { return (EExplorerFlags)((int)a | (int)b); }
	inline EExplorerFlags operator& (EExplorerFlags a, EExplorerFlags b) { return (EExplorerFlags)((int)a & (int)b); }
	inline EExplorerFlags operator^ (EExplorerFlags a, EExplorerFlags b) { return (EExplorerFlags)((int)a ^ (int)b); }
	inline EExplorerFlags& operator|= (EExplorerFlags& a, EExplorerFlags b) { return (EExplorerFlags&)((int&)a |= (int)b); }
	inline EExplorerFlags& operator&= (EExplorerFlags& a, EExplorerFlags b) { return (EExplorerFlags&)((int&)a &= (int)b); }
	inline EExplorerFlags& operator^= (EExplorerFlags& a, EExplorerFlags b) { return (EExplorerFlags&)((int&)a ^= (int)b); }

	/**
	* FileDialog is the base class for any dialog window that asks the user to select/save a file from/to the disk
	*/
	class FileDialog
	{
	public:
		/**
		* Constructor
		* @param p_callback
		* @param p_dialogTitle
		*/
		FileDialog(std::function<int(tagOFNA*)> p_callback, const std::string& p_dialogTitle);

		/**
		* Defines the initial directory (Where the FileDialog will open)
		* @param p_initalDirectory
		*/
		void SetInitialDirectory(const std::string& p_initialDirectory);

		/**
		* Show the file dialog
		* @param p_flags
		*/
		virtual void Show(EExplorerFlags p_flags = EExplorerFlags::DONTADDTORECENT | EExplorerFlags::FILEMUSTEXIST | EExplorerFlags::HIDEREADONLY | EExplorerFlags::NOCHANGEDIR);

		/**
		* Returns true if the file action succeeded
		*/
		bool HasSucceeded() const;

		/**
		* Returns the selected file name (Make sur that HasSucceeded() returned true before calling this method)
		*/
		std::string GetSelectedFileName();

		/**
		* Returns the selected file path (Make sur that HasSucceeded() returned true before calling this method)
		*/
		std::string GetSelectedFilePath();

		/**
		* Returns some information about the last error (Make sur that HasSucceeded() returned false before calling this method)
		*/
		std::string GetErrorInfo();

		/**
		* Returns true if the selected file exists
		*/
		bool IsFileExisting() const;

	private:
		void HandleError();

	protected:
		std::function<int(tagOFNA*)> m_callback;
		const std::string m_dialogTitle;
		std::string m_initialDirectory;
		std::string m_filter;
		std::string m_error;
		std::string m_filename;
		std::string m_filepath;
		bool m_succeeded;
	};

上来首先是一个枚举定义,事实上表示了对话框的性质,比如第一个READONLY就表示只读,这个看名字很明白了,不容易理解的还有注释,因为定义的比较多,所以我就不一一说明意思了,总之知道它是对话框性质就好了。

接下来一系列的内联函数都是对运算符的重载,需要知道EExplorerFlags实际上就是一个数,所以做运算是很正常的,而重载运算符最后又把运算结果转换成了EExplorerFlags类型,所以这些运算符重载后的作用就是改变对话框的性质。

接着它定义了FileDialog类这就是咱们文件相关的对话框的基类了,具体的函数作用在这有注释,我们到cpp文件中实现的时候再细说。

FileDialog.cpp

因为文件并不是很长,函数实现也并不算复杂,所以咱们把整个文件放在一起讲了,先上代码:

OvWindowing::Dialogs::FileDialog::FileDialog(std::function<int(tagOFNA*)> p_callback, const std::string & p_dialogTitle) :
	m_callback(p_callback),
	m_dialogTitle(p_dialogTitle),
	m_initialDirectory("")
{
}

void OvWindowing::Dialogs::FileDialog::SetInitialDirectory(const std::string & p_initialDirectory)
{
	m_initialDirectory = p_initialDirectory;
}

void OvWindowing::Dialogs::FileDialog::Show(EExplorerFlags p_flags)
{
	OPENFILENAME ofn;

	if (!m_initialDirectory.empty())
		m_filepath = m_initialDirectory;

	m_filepath.resize(MAX_PATH);

	ZeroMemory(&ofn, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = NULL;  // If you have a window to center over, put its HANDLE here
	ofn.lpstrFilter = m_filter.c_str();
	ofn.lpstrFile = m_filepath.data();
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrTitle = m_dialogTitle.c_str();

	if (!m_initialDirectory.empty())
		ofn.lpstrInitialDir = m_initialDirectory.c_str();

	ofn.Flags = static_cast<DWORD>(p_flags);

	m_succeeded = m_callback(&ofn);

	if (!m_succeeded)
		HandleError();
	else
		m_filepath = m_filepath.c_str();

	/* Extract filename from filepath */
	m_filename.clear();
	for (auto it = m_filepath.rbegin(); it != m_filepath.rend() && *it != '\\' && *it != '/'; ++it)
		m_filename += *it;
	std::reverse(m_filename.begin(), m_filename.end());
}

bool OvWindowing::Dialogs::FileDialog::HasSucceeded() const
{
	return m_succeeded;
}

std::string OvWindowing::Dialogs::FileDialog::GetSelectedFileName()
{
	return m_filename;
}

std::string OvWindowing::Dialogs::FileDialog::GetSelectedFilePath()
{
	return m_filepath;
}

std::string OvWindowing::Dialogs::FileDialog::GetErrorInfo()
{
	return m_error;
}

bool OvWindowing::Dialogs::FileDialog::IsFileExisting() const
{
	return std::filesystem::exists(m_filepath);
}

void OvWindowing::Dialogs::FileDialog::HandleError()
{
	switch (CommDlgExtendedError())
	{
	case CDERR_DIALOGFAILURE:	m_error = "CDERR_DIALOGFAILURE";   break;
	case CDERR_FINDRESFAILURE:	m_error = "CDERR_FINDRESFAILURE";  break;
	case CDERR_INITIALIZATION:	m_error = "CDERR_INITIALIZATION";  break;
	case CDERR_LOADRESFAILURE:	m_error = "CDERR_LOADRESFAILURE";  break;
	case CDERR_LOADSTRFAILURE:	m_error = "CDERR_LOADSTRFAILURE";  break;
	case CDERR_LOCKRESFAILURE:	m_error = "CDERR_LOCKRESFAILURE";  break;
	case CDERR_MEMALLOCFAILURE: m_error = "CDERR_MEMALLOCFAILURE"; break;
	case CDERR_MEMLOCKFAILURE:	m_error = "CDERR_MEMLOCKFAILURE";  break;
	case CDERR_NOHINSTANCE:		m_error = "CDERR_NOHINSTANCE";     break;
	case CDERR_NOHOOK:			m_error = "CDERR_NOHOOK";          break;
	case CDERR_NOTEMPLATE:		m_error = "CDERR_NOTEMPLATE";      break;
	case CDERR_STRUCTSIZE:		m_error = "CDERR_STRUCTSIZE";      break;
	case FNERR_BUFFERTOOSMALL:	m_error = "FNERR_BUFFERTOOSMALL";  break;
	case FNERR_INVALIDFILENAME: m_error = "FNERR_INVALIDFILENAME"; break;
	case FNERR_SUBCLASSFAILURE: m_error = "FNERR_SUBCLASSFAILURE"; break;
	default:					m_error = "You cancelled.";
	}
}

首先是构造函数,只做了属性的赋值,p_callback类型中可以看到tagOFNA,这是OPENFILENAME相关的内部结构,比较复杂,咱们真的用到再讲。m_dialogTitle是对话框的标题。

接下来的SetInitialDirectory()就是普通的赋值,更改m_initialDirectory,就是初始目录的位置。

再下来是一个稍微复杂点的Show()函数,函数先定义了一个OPENFILENAME类型的变量,事实上是OPENFILENAMEA类型,而OPENFILENAMEA就是tagOFNA结构。接下来函数确认是否有初始路径,如果有就把这个初始路径赋值给当前文件路径。之后给当前文件路径分配更多的空间,也给ofn分配了一块新的空间。接下来就是对ofn的属性进行赋值,我们来讲一下它的属性,实际就是tagOFNA的属性:

lStructSize是当前结构的大小,单位是字节;
hwndOwner是所有者对话框窗口的句柄;
lpstrFilter是指定文件名筛选字符串,决定了对话框中“文件类型”下拉式列表框中的内容,赋值比较复杂;
lpstrFile应指向一个包含文件名的缓冲区;
nMaxFile指定lpstrFile参数指向的缓冲区的长度,单位为TCHARs;
lpstrTitle应当指向一个缓冲区,用来接收用户选择的文件的文件名和扩展名;
lpstrInitialDir是一个指向对话框的初始化目录的以空字符结束的字符串,在咱们的Show()函数里只有有初始路径时才赋值;
Flags这个标志字段决定了对话框的不同行为。
这就是我们在函数里提到的所有的属性,想要全面了解tagOFNA的属性的话,可以去看这篇文章,当然也可以看windows官方的说明

在对ofn操作完成后,会用m_succeeded来接收一下回调,确认是否创建赋值成功。如果失败了,那么我们会返回一个错误,如果成功的话,我们就把真实的文件路径赋给咱们当前实例的属性。

再往下,就是如果咱们指定读取了一个路径,就会先清空已保存的当前路径,然后读取我们指定的路径。

接下来的函数是HasSucceeded(),GetSelectedFileName(),GetSelectedFilePath(),GetErrorInfo(),IsFileExisting(),都是一行就结束的函数,我们在一起讲。分别是用于读取属性m_succeeded,来确定是否成功生成;读取选中文件名,读取选中文件路径,获得报错信息,以及判断我们选中的文件是否存在。

最后是HandleError(),用于先从CommDlgExtendedError获取错误类型,然后对不同的错误类型报不同的错误。

二、MessageBox

接下来讲MessageBox这个文件夹,先讲头文件:

MessageBox.h

	/**
	* Displays a modal dialog box that contains a system icon,
	* a set of buttons, and a brief application-specific message,
	* such as status or error information
	*/
	class MessageBox
	{
	public:
		/**
		* Defines some severity levels for MessageBox instances
		*/
		enum class EMessageType
		{
			QUESTION	= 0x00000020L,
			INFORMATION = 0x00000040L,
			WARNING		= 0x00000030L,
			ERROR		= 0x00000010L
		};

		/**
		* Defines some button layouts for MessageBox instances
		*/
		enum class EButtonLayout
		{
			OK							= 0x00000000L,
			OK_CANCEL					= 0x00000001L,
			YES_NO						= 0x00000004L,
			YES_NO_CANCEL				= 0x00000003L,
			RETRY_CANCEL				= 0x00000005L,
			ABORT_RETRY_IGNORE			= 0x00000002L,
			CANCEL_TRYAGAIN_CONTINUE	= 0x00000006L,
			HELP						= 0x00004000L
		};

		/**
		* Defines some actions that the MessageBox should provide
		*/
		enum class EUserAction
		{
			OK			= 1,
			CANCEL		= 2,
			YES			= 6,
			NO			= 7,
			CONTINUE	= 11,
			IGNORE		= 5,
			RETRY		= 4,
			TRYAGAIN	= 10
		};

		/**
		* Create the MessageBox
		* @param p_title
		* @param p_message
		* @param p_messageType
		* @param p_buttonLayout
		* @param p_autoSpawn
		*/
		MessageBox(std::string p_title, std::string p_message, EMessageType p_messageType = EMessageType::INFORMATION, EButtonLayout p_buttonLayout = EButtonLayout::OK, bool p_autoSpawn = true);

		/**
		* Show the MessageBox on the screen
		*/
		void Spawn();

		/**
		* Return the user action
		*/
		const EUserAction& GetUserAction() const;

	private:
		std::string		m_title;
		std::string		m_message;
		EButtonLayout	m_buttonLayout;
		EMessageType	m_messageType;
		EUserAction		m_userResult;
	};

这个头文件定义了MessageBox,其实和咱们windows的MessageBox差不多。我们可以看到它有三个枚举:EMessageType是定义这个box的级别,是问题、信息、警告还是错误;EButtonLayout则是咱们的按钮类型;EUserAction则是用户做出的选择。其他的函数实现,我们在cpp文件中讲:

MessageBox.cpp

OvWindowing::Dialogs::MessageBox::MessageBox(std::string p_title, std::string p_message, EMessageType p_messageType, EButtonLayout p_buttonLayout, bool p_autoSpawn) :
	m_title(p_title),
	m_message(p_message),
	m_buttonLayout(p_buttonLayout),
	m_messageType(p_messageType)
{
	if (p_autoSpawn)
		Spawn();
}

const OvWindowing::Dialogs::MessageBox::EUserAction& OvWindowing::Dialogs::MessageBox::GetUserAction() const
{
	return m_userResult;
}

void OvWindowing::Dialogs::MessageBox::Spawn()
{
	int msgboxID = MessageBoxA
	(
		nullptr,
		static_cast<LPCSTR>(m_message.c_str()),
		static_cast<LPCSTR>(m_title.c_str()),
		static_cast<UINT>(m_messageType) | static_cast<UINT>(m_buttonLayout) | MB_DEFBUTTON2
	);

	m_userResult = static_cast<EUserAction>(msgboxID);
}

首先是构造函数,同样也是比较简单的赋值,唯一的一个判断是是否自动显现,如果是的话就显现窗口。

GetUserAction()只是返回一个属性,这个属性是用于记录用户的动作结果。

Spawn()是用于显示窗口的,其实就是调用了windows的函数MessageBoxA(),这个函数会显示一个对话框,之后会返回的整数表示用户所点击的按钮,所以我们会把那个得到的整数值存入m_userResult,也就是GetUserAction()返回的属性。

三、OpenFileDialog

接下来是OpenFileDialog文件夹内的文件。

OpenFileDialog.h

	/**
	* Dialog window that asks the user to select a file from the disk
	*/
	class OpenFileDialog : public FileDialog
	{
	public:
		/**
		* Constructor
		* @param p_dialogTitle
		*/
		OpenFileDialog(const std::string& p_dialogTitle);

		/**
		* Add a supported file type to the dialog window
		* @param p_label
		* @param p_filter
		*/
		void AddFileType(const std::string& p_label, const std::string& p_filter);
	};

这个头文件很短,定义了一个OpenFileDialog类,是让用户从磁盘选择一个文件的对话框,具体的函数实现在cpp文件中说明:

OpenFileDialog.cpp

OvWindowing::Dialogs::OpenFileDialog::OpenFileDialog(const std::string & p_dialogTitle) : FileDialog(GetOpenFileNameA, p_dialogTitle)
{
}

void OvWindowing::Dialogs::OpenFileDialog::AddFileType(const std::string & p_label, const std::string & p_filter)
{
	m_filter += p_label + '\0' + p_filter + '\0';
}

这个实现实际上也很简单,构造函数就是调用继承的FileDialog类的构造函数,而AddFileType()也就是往我们的文件过滤器添加了一种类型,让我们能支持新的文件格式罢了。

四、SaveFileDialog

这里是SaveFileDialog文件夹内的文件:

SaveFileDialog.h

	/**
	* Dialog window that asks the user to save a file to the disk
	*/
	class SaveFileDialog : public FileDialog
	{
	public:
		/**
		* Constructor
		* @param p_dialogTitle
		*/
		SaveFileDialog(const std::string& p_dialogTitle);

		/**
		* Show the file dialog
		* @param p_flags
		*/
		virtual void Show(EExplorerFlags p_flags = EExplorerFlags::DONTADDTORECENT | EExplorerFlags::FILEMUSTEXIST | EExplorerFlags::HIDEREADONLY | EExplorerFlags::NOCHANGEDIR) override;

		/**
		* Define the extension of the saved file
		* @param p_label
		* @param p_extension
		*/
		void DefineExtension(const std::string& p_label, const std::string& p_extension);

	private:
		void AddExtensionToFilePathAndName();

	private:
		std::string m_extension;
	};

这个头文件也比较简单,定义了一个SaveFileDialog类,是保存文件到磁盘的对话框,具体函数实现看下面:

SaveFileDialog.cpp

OvWindowing::Dialogs::SaveFileDialog::SaveFileDialog(const std::string & p_dialogTitle) : FileDialog(GetSaveFileNameA, p_dialogTitle)
{
}

void OvWindowing::Dialogs::SaveFileDialog::Show(EExplorerFlags p_flags)
{
	FileDialog::Show(p_flags);

	if (m_succeeded)
		AddExtensionToFilePathAndName();
}

void OvWindowing::Dialogs::SaveFileDialog::DefineExtension(const std::string & p_label, const std::string & p_extension)
{
	m_filter = p_label + '\0' + '*' + p_extension + '\0';
	m_extension = p_extension;
}

void OvWindowing::Dialogs::SaveFileDialog::AddExtensionToFilePathAndName()
{
	if (m_filename.size() >= m_extension.size())
	{
		std::string fileEnd(m_filename.data() + m_filename.size() - m_extension.size(), m_filename.data() + m_filename.size());

		if (fileEnd != m_extension)
		{
			m_filepath += m_extension;
			m_filename += m_extension;
		}
	}
	else
	{
		m_filepath += m_extension;
		m_filename += m_extension;
	}
}

首先,构造函数还是直接调用继承的FileDialog类的构造函数;

Show()函数实际上也是调用FileDialog类中的同名函数,但是添加了一句,用处是如果成功加载,就调用我们下面的一个函数AddExtensionToFilePathAndName(),作用在下面再讲;

DefineExtension()是定义我们要保存文件的类型,事实上就是后缀名;

AddExtensionToFilePathAndName()是添加我们的后缀名到文件路径和文件名,如果咱们的文件已经有名字了,那就看看拓展名对不对的上,不行就换掉当前的拓展名。如果没有名字,就直接加上我们给定的拓展名就好了。

Inputs

接下来我们讲Inputs文件夹内的文件,这里面的文件很简单,就是普通的输入相关的各种文件,我们先把各个头文件中的定义讲完了,再去看cpp文件。

一、EKey.h

	/**
	* Keyboard keys
	*/
	enum class EKey
	{
		KEY_UNKNOWN			= -1,
		KEY_SPACE			= 32,
		KEY_APOSTROPHE		= 39,
		KEY_COMMA			= 44,
		KEY_MINUS			= 45,
		KEY_PERIOD			= 46,
		KEY_SLASH			= 47,
		KEY_0				= 48,
		KEY_1				= 49,
		KEY_2				= 50,
		KEY_3				= 51,
		KEY_4				= 52,
		KEY_5				= 53,
		KEY_6				= 54,
		KEY_7				= 55,
		KEY_8				= 56,
		KEY_9				= 57,
		KEY_SEMICOLON		= 59,
		KEY_EQUAL			= 61,
		KEY_A				= 65,
		KEY_B				= 66,
		KEY_C				= 67,
		KEY_D				= 68,
		KEY_E				= 69,
		KEY_F				= 70,
		KEY_G				= 71,
		KEY_H				= 72,
		KEY_I				= 73,
		KEY_J				= 74,
		KEY_K				= 75,
		KEY_L				= 76,
		KEY_M				= 77,
		KEY_N				= 78,
		KEY_O				= 79,
		KEY_P				= 80,
		KEY_Q				= 81,
		KEY_R				= 82,
		KEY_S				= 83,
		KEY_T				= 84,
		KEY_U				= 85,
		KEY_V				= 86,
		KEY_W				= 87,
		KEY_X				= 88,
		KEY_Y				= 89,
		KEY_Z				= 90,
		KEY_LEFT_BRACKET	= 91,
		KEY_BACKSLASH		= 92,
		KEY_RIGHT_BRACKET	= 93,
		KEY_GRAVE_ACCENT	= 96,
		KEY_WORLD_1			= 61,
		KEY_WORLD_2			= 62,
		KEY_ESCAPE			= 256,
		KEY_ENTER			= 257,
		KEY_TAB				= 258,
		KEY_BACKSPACE		= 259,
		KEY_INSERT			= 260,
		KEY_DELETE			= 261,
		KEY_RIGHT			= 262,
		KEY_LEFT			= 263,
		KEY_DOWN			= 264,
		KEY_UP				= 265,
		KEY_PAGE_UP			= 266,
		KEY_PAGE_DOWN		= 267,
		KEY_HOME			= 268,
		KEY_END				= 269,
		KEY_CAPS_LOCK		= 280,
		KEY_SCROLL_LOCK		= 281,
		KEY_NUM_LOCK		= 282,
		KEY_PRINT_SCREEN	= 283,
		KEY_PAUSE			= 284,
		KEY_F1				= 290,
		KEY_F2				= 291,
		KEY_F3				= 292,
		KEY_F4				= 293,
		KEY_F5				= 294,
		KEY_F6				= 295,
		KEY_F7				= 296,
		KEY_F8				= 297,
		KEY_F9				= 298,
		KEY_F10				= 299,
		KEY_F11				= 300,
		KEY_F12				= 301,
		KEY_F13				= 302,
		KEY_F14				= 303,
		KEY_F15				= 304,
		KEY_F16				= 305,
		KEY_F17				= 306,
		KEY_F18				= 307,
		KEY_F19				= 308,
		KEY_F20				= 309,
		KEY_F21				= 310,
		KEY_F22				= 311,
		KEY_F23				= 312,
		KEY_F24				= 313,
		KEY_F25				= 314,
		KEY_KP_0			= 320,
		KEY_KP_1			= 321,
		KEY_KP_2			= 322,
		KEY_KP_3			= 323,
		KEY_KP_4			= 324,
		KEY_KP_5			= 325,
		KEY_KP_6			= 326,
		KEY_KP_7			= 327,
		KEY_KP_8			= 328,
		KEY_KP_9			= 329,
		KEY_KP_DECIMAL		= 330,
		KEY_KP_DIVIDE		= 331,
		KEY_KP_MULTIPLY		= 332,
		KEY_KP_SUBTRACT		= 333,
		KEY_KP_ADD			= 334,
		KEY_KP_ENTER		= 335,
		KEY_KP_EQUAL		= 336,
		KEY_LEFT_SHIFT		= 340,
		KEY_LEFT_CONTROL	= 341,
		KEY_LEFT_ALT		= 342,
		KEY_LEFT_SUPER		= 343,
		KEY_RIGHT_SHIFT		= 344,
		KEY_RIGHT_CONTROL	= 345,
		KEY_RIGHT_ALT		= 346,
		KEY_RIGHT_SUPER		= 347,
		KEY_MENU			= 348
	};

这个枚举是把键盘上的按键都给了个序号用于对应,非常明了。

二、EKeyState.h

	/**
	* Defines some states that can be applied to keyboard keys
	*/
	enum class EKeyState
	{
		KEY_UP		= 0,
		KEY_DOWN	= 1
	};

这个枚举定义了按键的状态,是否被按下。

三、EMouseButton.h

	/**
	* Mouse buttons
	*/
	enum class EMouseButton
	{
		MOUSE_BUTTON_1		= 0,
		MOUSE_BUTTON_2		= 1,
		MOUSE_BUTTON_3		= 2,
		MOUSE_BUTTON_4		= 3,
		MOUSE_BUTTON_5		= 4,
		MOUSE_BUTTON_6		= 5,
		MOUSE_BUTTON_7		= 6,
		MOUSE_BUTTON_8		= 7,
		MOUSE_BUTTON_LEFT	= 0,
		MOUSE_BUTTON_RIGHT	= 1,
		MOUSE_BUTTON_MIDDLE = 2
	};

和EKey一样,这个枚举定义了鼠标的按键映射。

四、EMouseButtonState.h

	/**
	* Defines some states that can be applied to mouse buttons
	*/
	enum class EMouseButtonState
	{
		MOUSE_UP	= 0,
		MOUSE_DOWN	= 1
	};

和EKeyState一样,用于标识鼠标按键的状态,是否被按下。

五、InputManager

接下来这个类有头文件和cpp文件。

InputManager.h

	/**
	* Handles inputs (Mouse and keyboard)
	*/
	class InputManager
	{
	public:
		/**
		* Create the window
		* @param p_windowSettings
		*/
		InputManager(Window& p_window);

		/**
		* Destroy the input manager by removing listeners on the window
		*/
		~InputManager();

		/**
		* Return the current state of the given key
		* @param p_key
		*/
		EKeyState GetKeyState(EKey p_key) const;

		/**
		* Return the current state of the given mouse button
		* @param p_button
		*/
		EMouseButtonState GetMouseButtonState(EMouseButton p_button) const;

		/**
		* Return true if the given key has been pressed during the frame
		* @param p_key
		*/
		bool IsKeyPressed(EKey p_key) const;

		/**
		* Return true if the given key has been released during the frame
		* @param p_key
		*/
		bool IsKeyReleased(EKey p_key) const;

		/**
		* Return true if the given mouse button has been pressed during the frame
		* @param p_button
		*/
		bool IsMouseButtonPressed(EMouseButton p_button) const;

		/**
		* Return true if the given mouse button has been released during the frame
		* @param p_button
		*/
		bool IsMouseButtonReleased(EMouseButton p_button) const;

		/**
		* Return the current mouse position relative to the window
		*/
		std::pair<double, double> GetMousePosition() const;

		/**
		* Clear any event occured
		* @note Should be called at the end of every game tick
		*/
		void ClearEvents();

	private:
		void OnKeyPressed(int p_key);
		void OnKeyReleased(int p_key);
		void OnMouseButtonPressed(int p_button);
		void OnMouseButtonReleased(int p_button);

	private:
		Window& m_window;

		OvTools::Eventing::ListenerID m_keyPressedListener;
		OvTools::Eventing::ListenerID m_keyReleasedListener;
		OvTools::Eventing::ListenerID m_mouseButtonPressedListener;
		OvTools::Eventing::ListenerID m_mouseButtonReleasedListener;

		std::unordered_map<EKey, EKeyState>					m_keyEvents;
		std::unordered_map<EMouseButton, EMouseButtonState>	m_mouseButtonEvents;
	};

具体函数实现在分析cpp文件的时候说。

InputManager.cpp

OvWindowing::Inputs::InputManager::InputManager(Window& p_window) : m_window(p_window)
{
	m_keyPressedListener = m_window.KeyPressedEvent.AddListener(std::bind(&InputManager::OnKeyPressed, this, std::placeholders::_1));
	m_keyReleasedListener = m_window.KeyReleasedEvent.AddListener(std::bind(&InputManager::OnKeyReleased, this, std::placeholders::_1));
	m_mouseButtonPressedListener = m_window.MouseButtonPressedEvent.AddListener(std::bind(&InputManager::OnMouseButtonPressed, this, std::placeholders::_1));
	m_mouseButtonReleasedListener = m_window.MouseButtonReleasedEvent.AddListener(std::bind(&InputManager::OnMouseButtonReleased, this, std::placeholders::_1));
}

OvWindowing::Inputs::InputManager::~InputManager()
{
	m_window.KeyPressedEvent.RemoveListener(m_keyPressedListener);
	m_window.KeyReleasedEvent.RemoveListener(m_keyReleasedListener);
	m_window.MouseButtonPressedEvent.RemoveListener(m_mouseButtonPressedListener);
	m_window.MouseButtonReleasedEvent.RemoveListener(m_mouseButtonReleasedListener);
}

OvWindowing::Inputs::EKeyState OvWindowing::Inputs::InputManager::GetKeyState(EKey p_key) const
{
	switch (glfwGetKey(m_window.GetGlfwWindow(), static_cast<int>(p_key)))
	{
		case GLFW_PRESS:	return EKeyState::KEY_DOWN;
		case GLFW_RELEASE:	return EKeyState::KEY_UP;
	}

	return EKeyState::KEY_UP;
}

OvWindowing::Inputs::EMouseButtonState OvWindowing::Inputs::InputManager::GetMouseButtonState(EMouseButton p_button) const
{
	switch (glfwGetMouseButton(m_window.GetGlfwWindow(), static_cast<int>(p_button)))
	{
		case GLFW_PRESS:	return EMouseButtonState::MOUSE_DOWN;
		case GLFW_RELEASE:	return EMouseButtonState::MOUSE_UP;
	}

	return EMouseButtonState::MOUSE_UP;
}

bool OvWindowing::Inputs::InputManager::IsKeyPressed(EKey p_key) const
{
	return m_keyEvents.find(p_key) != m_keyEvents.end() && m_keyEvents.at(p_key) == EKeyState::KEY_DOWN;
}

bool OvWindowing::Inputs::InputManager::IsKeyReleased(EKey p_key) const
{
	return m_keyEvents.find(p_key) != m_keyEvents.end() && m_keyEvents.at(p_key) == EKeyState::KEY_UP;
}

bool OvWindowing::Inputs::InputManager::IsMouseButtonPressed(EMouseButton p_button) const
{
	return m_mouseButtonEvents.find(p_button) != m_mouseButtonEvents.end() && m_mouseButtonEvents.at(p_button) == EMouseButtonState::MOUSE_DOWN;
}

bool OvWindowing::Inputs::InputManager::IsMouseButtonReleased(EMouseButton p_button) const
{
	return m_mouseButtonEvents.find(p_button) != m_mouseButtonEvents.end() && m_mouseButtonEvents.at(p_button) == EMouseButtonState::MOUSE_UP;
}

std::pair<double, double> OvWindowing::Inputs::InputManager::GetMousePosition() const
{
	std::pair<double, double> result;
	glfwGetCursorPos(m_window.GetGlfwWindow(), &result.first, &result.second);
	return result;
}

void OvWindowing::Inputs::InputManager::ClearEvents()
{
	m_keyEvents.clear();
	m_mouseButtonEvents.clear();
}

void OvWindowing::Inputs::InputManager::OnKeyPressed(int p_key)
{
	m_keyEvents[static_cast<EKey>(p_key)] = EKeyState::KEY_DOWN;
}

void OvWindowing::Inputs::InputManager::OnKeyReleased(int p_key)
{
	m_keyEvents[static_cast<EKey>(p_key)] = EKeyState::KEY_UP;
}

void OvWindowing::Inputs::InputManager::OnMouseButtonPressed(int p_button)
{
	m_mouseButtonEvents[static_cast<EMouseButton>(p_button)] = EMouseButtonState::MOUSE_DOWN;
}

void OvWindowing::Inputs::InputManager::OnMouseButtonReleased(int p_button)
{
	m_mouseButtonEvents[static_cast<EMouseButton>(p_button)] = EMouseButtonState::MOUSE_UP;
}

我们一个个说:

首先构造函数,除了把p_window赋给m_window外,还创造了多个监听,用于得知键盘和鼠标的按键是按下了还是释放了。

析构函数则是把咱们之前创建的监听给移除了。

GetKeyState()用于获得给定键盘按键的状态,内部实现是调用了glfw的函数,看当前按键在glfw下的状态,之后转换到我们定义的状态。如果这个glfw的状态在我们的定义中没有对应,就当作没有按下按键。

GetMouseButtonState()内部实现和作用都和GetKeyState()几乎一样,只是一个对键盘用,一个对鼠标用。

IsKeyPressed(),IsKeyReleased(),IsMouseButtonPressed(),IsMouseButtonReleased()这几个的实现也差不多,功能一看就知,判断按键是被按下还是被释放。实现都是在我们的事件序列中寻找对应按键并和我们需要判断的状态作比较,如果既能找到这个按键的事件又能和我们希望的状态匹配,就返回真值。

GetMousePosition()很简单,就是获得当前鼠标位置,实现依靠glfw。

ClearEvents()是清空全部事件,实现就是把我们保存的键盘按键事件序列和鼠标按键序列清空。

OnKeyPressed(),OnKeyReleased(),OnMouseButtonPressed(),OnMouseButtonReleased()又是一组类似的函数,用于实时更新按键的状态。当监听给出回调时调用此函数,把对应按键在事件序列中更改状态。

一些剩下的文件

我们到这已经把OvWindowing中的文件基本讲完了,但是还有一些我们以前并未提到的文件,我在此做个收尾工作。

一、DeviceSettings.h

	/**
	* Contains device settings
	*/
	struct DeviceSettings
	{
		/**
		* specifies whether to create a debug OpenGL context, which may have additional error and
		* performance issue reporting functionality. If OpenGL ES is requested, this hint is ignored
		*/
		bool debugProfile = false;

		/**
		* Specifies whether the OpenGL context should be forward-compatible, i.e. one where all functionality
		* deprecated in the requested version of OpenGL is removed. This must only be used if the requested OpenGL
		* version is 3.0 or above. If OpenGL ES is requested, this hint is ignored.
		*/
		bool forwardCompatibility = false;

		/**
		* Specify the client API major version that the created context must be compatible with. The exact
		* behavior of these hints depend on the requested client API
		*/
		uint8_t contextMajorVersion = 3;

		/**
		* Specify the client API minor version that the created context must be compatible with. The exact
		* behavior of these hints depend on the requested client API
		*/
		uint8_t contextMinorVersion = 2;

		/**
		* Defines the amount of samples to use (Requiered for multi-sampling)
		*/
		uint8_t samples = 4;
	};

这些是窗口的一些环境设置,比如是否开启调试,是否向前兼容,当前版本号,以及采样数(用于MSAA)。一般这些都是定死的,修改的意义不是很大,我们只要知道有这些,可以改就行了。

二、Window.h

	/**
	* A simple OS-based window.
	* It needs a Device (GLFW) to work
	*/
	class Window
	{
	public:
		/* Inputs relatives */
		OvTools::Eventing::Event<int> KeyPressedEvent;
		OvTools::Eventing::Event<int> KeyReleasedEvent;
		OvTools::Eventing::Event<int> MouseButtonPressedEvent;
		OvTools::Eventing::Event<int> MouseButtonReleasedEvent;

		/* Window events */
		OvTools::Eventing::Event<uint16_t, uint16_t> ResizeEvent;
		OvTools::Eventing::Event<uint16_t, uint16_t> FramebufferResizeEvent;
		OvTools::Eventing::Event<int16_t, int16_t> MoveEvent;
		OvTools::Eventing::Event<int16_t, int16_t> CursorMoveEvent;
		OvTools::Eventing::Event<> MinimizeEvent;
		OvTools::Eventing::Event<> MaximizeEvent;
		OvTools::Eventing::Event<> GainFocusEvent;
		OvTools::Eventing::Event<> LostFocusEvent;
		OvTools::Eventing::Event<> CloseEvent;

		/**
		* Create the window
		* @param p_device
		* @param p_windowSettings
		*/
		Window(const Context::Device& p_device, const Settings::WindowSettings& p_windowSettings);

		/**
		* Destructor of the window, responsible of the GLFW window memory free
		*/
		~Window();

		/**
		* Set Icon
		* @param p_filePath
		*/
		void SetIcon(const std::string& p_filePath);

		/**
		* Set Icon from memory
		* @param p_data
		* @param p_width
		* @param p_height
		*/
		void SetIconFromMemory(uint8_t* p_data, uint32_t p_width, uint32_t p_height);

		/**
		* Find an instance of window with a given GLFWwindow
		* @param p_glfwWindow
		*/
		static Window* FindInstance(GLFWwindow* p_glfwWindow);

		/**
		* Resize the window
		* @param p_width
		* @param p_height
		*/
		void SetSize(uint16_t p_width, uint16_t p_height);

		/**
		* Defines a minimum size for the window
		* @param p_minimumWidth
		* @param p_minimumHeight
		* @note -1 (WindowSettings::DontCare) value means no limitation
		*/
		void SetMinimumSize(int16_t p_minimumWidth, int16_t p_minimumHeight);

		/**
		* Defines a maximum size for the window
		* @param p_maximumWidth
		* @param p_maximumHeight
		* @note -1 (WindowSettings::DontCare) value means no limitation
		*/
		void SetMaximumSize(int16_t p_maximumWidth, int16_t p_maximumHeight);

		/**
		* Define a position for the window
		* @param p_x
		* @param p_y
		*/
		void SetPosition(int16_t p_x, int16_t p_y);

		/**
		* Minimize the window
		*/
		void Minimize() const;

		/**
		* Maximize the window
		*/
		void Maximize() const;

		/**
		* Restore the window
		*/
		void Restore() const;

		/**
		* Hides the specified window if it was previously visible
		*/
		void Hide() const;

		/**
		* Show the specified window if it was previously hidden
		*/
		void Show() const;

		/**
		* Focus the window
		*/
		void Focus() const;

		/**
		* Set the should close flag of the window to true
		* @param p_value
		*/
		void SetShouldClose(bool p_value) const;

		/**
		* Return true if the window should close
		*/
		bool ShouldClose() const;

		/**
		* Set the window in fullscreen or windowed mode
		* @param p_value (True for fullscreen mode, false for windowed)
		*/
		void SetFullscreen(bool p_value);

		/**
		* Switch the window to fullscreen or windowed mode depending
		* on the current state
		*/
		void ToggleFullscreen();

		/**
		* Return true if the window is fullscreen
		*/
		bool IsFullscreen() const;

		/**
		* Return true if the window is hidden
		*/
		bool IsHidden() const;

		/**
		* Return true if the window is visible
		*/
		bool IsVisible() const;

		/**
		* Return true if the windows is maximized
		*/
		bool IsMaximized() const;

		/**
		* Return true if the windows is minimized
		*/
		bool IsMinimized() const;

		/**
		* Return true if the windows is focused
		*/
		bool IsFocused() const;

		/**
		* Return true if the windows is resizable
		*/
		bool IsResizable() const;

		/**
		* Return true if the windows is decorated
		*/
		bool IsDecorated() const;

		/**
		* Define the window as the current context
		*/
		void MakeCurrentContext() const;

		/**
		* Handle the buffer swapping with the current window
		*/
		void SwapBuffers() const;

		/**
		* Define a mode for the mouse cursor
		* @param p_cursorMode
		*/
		void SetCursorMode(Cursor::ECursorMode p_cursorMode);

		/**
		* Define a shape to apply to the current cursor
		* @param p_cursorShape
		*/
		void SetCursorShape(Cursor::ECursorShape p_cursorShape);

		/**
		* Move the cursor to the given position
		*/
		void SetCursorPosition(int16_t p_x, int16_t p_y);

		/**
		* Define a title for the window
		* @param p_title
		*/
		void SetTitle(const std::string& p_title);

		/**
		* Defines a refresh rate (Use WindowSettings::DontCare to use the highest available refresh rate)
		* @param p_refreshRate
		* @note You need to switch to fullscreen mode to apply this effect (Or leave fullscreen and re-apply)
		*/
		void SetRefreshRate(int32_t p_refreshRate);

		/**
		* Return the title of the window
		*/
		std::string GetTitle() const;

		/**
		* Return the current size of the window
		*/
		std::pair<uint16_t, uint16_t> GetSize() const;

		/**
		* Return the current minimum size of the window
		* @note -1 (WindowSettings::DontCare) values means no limitation
		*/
		std::pair<int16_t, int16_t> GetMinimumSize() const;

		/**
		* Return the current maximum size of the window
		* @note -1 (WindowSettings::DontCare) values means no limitation
		*/
		std::pair<int16_t, int16_t> GetMaximumSize() const;

		/**
		* Return the current position of the window
		*/
		std::pair<int16_t, int16_t> GetPosition() const;

		/**
		* Return the framebuffer size (Viewport size)
		*/
		std::pair<uint16_t, uint16_t> GetFramebufferSize() const;

		/**
		* Return the current cursor mode
		*/
		Cursor::ECursorMode GetCursorMode() const;

		/**
		* Return the current cursor shape
		*/
		Cursor::ECursorShape GetCursorShape() const;

		/**
		* Return the current refresh rate (Only applied to the fullscreen mode).
		* If the value is -1 (WindowSettings::DontCare) the highest refresh rate will be used
		*/
		int32_t GetRefreshRate() const;

		/**
		* Return GLFW window
		*/
		GLFWwindow* GetGlfwWindow() const;

	private:
		void CreateGlfwWindow(const Settings::WindowSettings& p_windowSettings);

		/* Callbacks binding */
		void BindKeyCallback() const;
		void BindMouseCallback() const;
		void BindResizeCallback() const;
		void BindFramebufferResizeCallback() const;
		void BindCursorMoveCallback() const;
		void BindMoveCallback() const;
		void BindIconifyCallback() const;
		void BindFocusCallback() const;
		void BindCloseCallback() const;

		/* Event listeners */
		void OnResize(uint16_t p_width, uint16_t p_height);
		void OnMove(int16_t p_x, int16_t p_y);

		/* Internal helpers */
		void UpdateSizeLimit() const;

	private:
		/* This map is used by callbacks to find a "Window" instance out of a "GLFWwindow" instnace*/
		static std::unordered_map<GLFWwindow*, Window*> __WINDOWS_MAP;

		const Context::Device& m_device;
		GLFWwindow* m_glfwWindow;

		/* Window settings */
		std::string m_title;
		std::pair<uint16_t, uint16_t> m_size;
		std::pair<int16_t, int16_t> m_minimumSize;
		std::pair<int16_t, int16_t> m_maximumSize;
		std::pair<int16_t, int16_t> m_position;
		bool m_fullscreen;
		int32_t m_refreshRate;
		Cursor::ECursorMode m_cursorMode;
		Cursor::ECursorShape m_cursorShape;
	};

这个虽说我之前没有直接提过,但是这里面的函数其实已经全部讲过了,就在讲Window.cpp的时候,所以我在这就不说了,需要的可以看代码分析一

总结

这样的话,我们已经把Overload其中的一个模块——OvWindowing给讲完了,可喜可贺。之后咱会开始讲OvEditor,也就是Overload的主体部分,加油!

Diana

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值