Dragon Engine:窗口
2020-08-15 本文已影响0人
Dragon_boy
本节介绍窗口的实现。这里使用开源库GLFW,之前也介绍过,只不过这里的主要任务就是将GLFW的API抽象出来,方便我们使用。
首先建立一个窗口基类,Window.h
:
#include "dgpch.h"
#include "Dragon/Core/Core.h"
#include "Dragon/Events/Event.h"
namespace Dragon
{
struct WindowProps
{
std::string Title;
unsigned int Width;
unsigned int Height;
WindowProps(const std::string& title = "Dragon Engine",
unsigned int width = 1280,
unsigned int height = 720)
: Title(title), Width(width), Height(height)
{
}
};
class Window
{
public:
using EventCallbackFn = std::function<void(Event&)>;
virtual ~Window() = default;
virtual void OnUpdate() = 0;
virtual unsigned int GetWidth() const = 0;
virtual unsigned int GetHeight() const = 0;
virtual void SetEventCallback(const EventCallbackFn& callback) = 0;
virtual void SetVSync(bool enabled) = 0;
virtual bool IsVSync() const = 0;
virtual void* GetNativeWindow() const = 0;
static Window* Create(const WindowProps& props = WindowProps());
};
}
类成员方法中主要是获取宽高函数、设置事件响应函数、设置垂直同步函数、获取窗口对象函数、以及一个静态的创建窗口的类函数。
之后是创建相应平台的窗口类,常见的系统平台是Windows,MacOS和Linux,这里暂时只考虑Windows。
WindowsWindow
:
#include "Dragon/Core/Window.h"
#include <GLFW/glfw3.h>
#include "Dragon/Renderer/GraphicsContext.h"
namespace Dragon
{
class WindowsWindow : public Window
{
public:
WindowsWindow(const WindowProps& props);
virtual ~WindowsWindow();
void OnUpdate() override;
inline unsigned int GetWidth() const override { return m_Data.Width; }
inline unsigned int GetHeight() const override { return m_Data.Height; }
inline void SetEventCallback(const EventCallbackFn& callback) override { m_Data.EventCallback = callback; }
void SetVSync(bool enabled) override;
bool IsVSync() const override;
inline void* GetNativeWindow() const { return m_Window; };
private:
virtual void Init(const WindowProps& props);
virtual void Shutdown();
private:
GLFWwindow* m_Window;
GraphicsContext* m_Context;
struct WindowData
{
std::string Title;
unsigned int Width, Height;
bool VSync;
EventCallbackFn EventCallback;
};
WindowData m_Data;
};
}
主要是继承Window.h
,大部分方法没什么区别,只不过新增了private的Init和Shutdown函数,用于初始化窗口以及关闭窗口。(暂时忽略Dragon/Renderer/GraphicsContext.h
)
接下来看各个方法的实现:
#include "dgpch.h"
#include "WindowsWindow.h"
#include "Dragon/Events/ApplicationEvent.h"
#include "Dragon/Events/MouseEvent.h"
#include "Dragon/Events/KeyEvents.h"
#include "Platform/OpenGL/OpenGLContext.h"
namespace Dragon
{
static bool s_GLFWInitialized = false;
static void GLFWErrorCallback(int error, const char* description)
{
DG_CORE_ERROR("GLFW Error ({0}): {1}", error, description);
}
Window* Window::Create(const WindowProps& props)
{
return new WindowsWindow(props);
}
WindowsWindow::WindowsWindow(const WindowProps& props)
{
Init(props);
}
WindowsWindow::~WindowsWindow()
{
Shutdown();
}
void WindowsWindow::Init(const WindowProps& props)
{
m_Data.Title = props.Title;
m_Data.Width = props.Width;
m_Data.Height = props.Height;
DG_CORE_INFO("Creating window {0} ({1}, {2})", props.Title, props.Width, props.Height);
if (!s_GLFWInitialized)
{
int success = glfwInit();
DG_CORE_ASSERT(success, "Could not initialize GLFW.");
glfwSetErrorCallback(GLFWErrorCallback);
s_GLFWInitialized = true;
}
m_Window = glfwCreateWindow((int)props.Width, (int)props.Height, m_Data.Title.c_str(), nullptr, nullptr);
m_Context = new OpenGLContext(m_Window);
m_Context->Init();
glfwSetWindowUserPointer(m_Window, &m_Data);
SetVSync(true);
//Set GLFW callback
glfwSetWindowSizeCallback(m_Window, [](GLFWwindow* window, int width, int height)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
data.Width = width;
data.Height = height;
WindowResizeEvent event(width, height);
data.EventCallback(event);
});
glfwSetWindowCloseCallback(m_Window, [](GLFWwindow* window)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
WindowCloseEvent event;
data.EventCallback(event);
});
glfwSetKeyCallback(m_Window, [](GLFWwindow* window, int key, int scancode, int action, int mods)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
switch (action)
{
case GLFW_PRESS:
{
KeyPressedEvent event(key, 0);
data.EventCallback(event);
break;
}
case GLFW_RELEASE:
{
KeyReleasedEvent event(key);
data.EventCallback(event);
break;
}
case GLFW_REPEAT:
{
KeyPressedEvent event(key, 1);
data.EventCallback(event);
break;
}
}
});
glfwSetCharCallback(m_Window, [](GLFWwindow* window, unsigned int codepoint)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
KeyTypedEvent event(codepoint);
data.EventCallback(event);
});
glfwSetMouseButtonCallback(m_Window, [](GLFWwindow* window, int button, int action, int mods)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
switch (action)
{
case GLFW_PRESS:
{
MouseButtonPressedEvent event(button);
data.EventCallback(event);
break;
}
case GLFW_RELEASE:
{
MouseButtonReleasedEvent event(button);
data.EventCallback(event);
break;
}
}
});
glfwSetScrollCallback(m_Window, [](GLFWwindow* window, double xOffset, double yOffset)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
MouseScrolledEvent event((float)xOffset, (float)yOffset);
data.EventCallback(event);
});
glfwSetCursorPosCallback(m_Window, [](GLFWwindow* window, double xPos, double yPos)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
MouseMovedEvent event((float)xPos, (float)yPos);
data.EventCallback(event);
});
glfwSetInputMode(m_Window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
}
void WindowsWindow::Shutdown()
{
glfwDestroyWindow(m_Window);
}
void WindowsWindow::OnUpdate()
{
glfwPollEvents();
m_Context->SwapBuffers();
}
void WindowsWindow::SetVSync(bool enabled)
{
if (enabled)
glfwSwapInterval(1);
else
glfwSwapInterval(0);
m_Data.VSync = enabled;
}
bool WindowsWindow::IsVSync() const
{
return m_Data.VSync;
}
}
Init
函数就是GLFW初始化的那一套代码,没什么可说的,重点要说的是其中的事件调用函数设置,使用的API是GLFW里的,比如:
//Set GLFW callback
glfwSetWindowSizeCallback(m_Window, [](GLFWwindow* window, int width, int height)
{
WindowData& data = *(WindowData*)glfwGetWindowUserPointer(window);
data.Width = width;
data.Height = height;
WindowResizeEvent event(width, height);
data.EventCallback(event);
});
第二个参数接受的是一个事件注册函数,这里我们使用Lambda构建一个匿名函数。调用glfwGetWindowUserPointer
获取窗口指针,得设置其上的窗口事件。
我们回过头看我们的Application
类,加上之前介绍过的一些元素:
Application.h
namespace Dragon
{
class Application
{
public :
Application();
virtual ~Application() = default;
void Run();
void OnEvent(Event& e);
void PushLayer(Layer* layer);
void PushOverlay(Layer* overlay);
inline static Application& Get() { return *s_Instance; }
inline Window& GetWindow() { return *m_Window; }
private:
bool OnWindowClose(WindowCloseEvent& e);
bool OnKeyBoard(KeyPressedEvent& e);
private:
std::unique_ptr<Window> m_Window;
ImGuiLayer* m_ImGuiLayer;
bool m_Running = true;
bool m_Cursor = true;
LayerStack m_LayerStack;
Timestep timestep;
float m_LastFrameTime = 0.0f;
private:
static Application* s_Instance;
};
// to be defined in the client
Application* CreateApplication();
}
加上了一些事件响应以及窗口对象获取。
然后看一下实现:
namespace Dragon
{
#define BIND_EVENT_FN(x) std::bind(&Application::x, this, std::placeholders::_1)
Application* Application::s_Instance = nullptr;
Application::Application()
{
DG_CORE_ASSERT(!s_Instance, "Application already exists!");
s_Instance = this;
m_Window = std::unique_ptr<Window>(Window::Create());
m_Window->SetEventCallback(BIND_EVENT_FN(OnEvent));
Renderer::Init();
m_ImGuiLayer = new ImGuiLayer();
PushOverlay(m_ImGuiLayer);
}
void Application::PushLayer(Layer* layer)
{
m_LayerStack.PushLayer(layer);
}
void Application::PushOverlay(Layer* overlay)
{
m_LayerStack.PushOverlay(overlay);
}
void Application::OnEvent(Event& e)
{
EventDispatcher dispatcher(e);
dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(OnWindowClose));
dispatcher.Dispatch<KeyPressedEvent>(BIND_EVENT_FN(OnKeyBoard));
for (auto it = m_LayerStack.end(); it != m_LayerStack.begin();)
{
(*--it)->OnEvent(e);
if (e.Handled)
break;
}
}
void Application::Run()
{
while (m_Running)
{
if(!m_Cursor)
glfwSetInputMode((GLFWwindow*)m_Window->GetNativeWindow(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
else
glfwSetInputMode((GLFWwindow*)m_Window->GetNativeWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
float time = (float)glfwGetTime();
Timestep timestep = time - m_LastFrameTime;
m_LastFrameTime = time;
for (Layer* layer : m_LayerStack)
layer->OnUpdate(timestep);
m_ImGuiLayer->Begin();
for (Layer* layer : m_LayerStack)
layer->OnImGuiRender();
m_ImGuiLayer->End();
m_Window->OnUpdate();
}
}
bool Application::OnWindowClose(WindowCloseEvent& e)
{
m_Running = false;
return true;
}
bool Application::OnKeyBoard(KeyPressedEvent& e)
{
if (e.GetKeyCode() == DG_KEY_Q)
{
if (!m_Cursor)
m_Cursor = true;
else
m_Cursor = false;
}
return false;
}
}
加上了一些窗口的更新函数以及事件响应,其它大部分和之前一样。
下一节介绍GUI的实现。
项目github地址:https://github.com/Dragon-Baby/Dragon