examples/example.windowing/example-glfw/glfw.cpp
File members: examples/example.windowing/example-glfw/glfw.cpp
// Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#ifndef DOXYGEN_BUILD // Just need the full listing
# define OMNI_LOG_DEFAULT_CHANNEL kGlfwChannel
// clang-format off
#include <omni/core/ReplaceCarbAssert.h>
// clang-format on
# include <SDLGameControllerDB/include/GameControllerDB.h>
# include <carb/thread/RecursiveSharedMutex.h>
# include <omni/core/Omni.h>
# include <windowing/IWindowSystem.h>
# include <omni/log/LogChannel.h>
# if CARB_PLATFORM_WINDOWS
# define GLFW_EXPOSE_NATIVE_WIN32
# elif CARB_PLATFORM_LINUX
# define GLFW_EXPOSE_NATIVE_X11
# endif
# if CARB_PLATFORM_WINDOWS
# define NOMINMAX
# endif
# include <GLFW/glfw3.h>
# include <GLFW/glfw3native.h>
# include <array>
# include <bitset>
# include <functional>
# include <memory>
# include <shared_mutex> // std::shared_lock
# include <vector>
# include <utility>
OMNI_LOG_ADD_CHANNEL(kInputChannel, "example.input-glfw", "Input related functionality in example-glfw");
OMNI_LOG_ADD_CHANNEL(kKeyboardChannel, "example.input.keyboard-glfw", "Keyboard related functionality in example-glfw");
OMNI_LOG_ADD_CHANNEL(kWindowChannel, "example.window-glfw", "Window system related functionality in example-glfw");
namespace omni
{
using namespace omni::core;
} // namespace omni
namespace
{
using SharedLock = std::shared_lock<carb::thread::recursive_shared_mutex>;
using UniqueLock = std::unique_lock<carb::thread::recursive_shared_mutex>;
std::thread::id s_mainThreadId;
bool isMainThread()
{
return (s_mainThreadId == std::this_thread::get_id());
}
// most glfw functions can only be called on the main thread.
# define ASSERT_IS_MAIN_THREAD() \
OMNI_ASSERT(isMainThread(), "a window system function was call on a thread other than the main thread");
// ensures glfwTerminate is called when the program exts (or when the module is unloaded)
struct GlfwTerminator
{
~GlfwTerminator()
{
ASSERT_IS_MAIN_THREAD();
glfwTerminate();
}
};
std::unique_ptr<GlfwTerminator> s_glfwTerminator;
// calls glfwInit() and sets up some global vars.
//
// returns true if initialization was successful or if initialize() previously succeeded.
bool initialize()
{
if (s_glfwTerminator)
{
return true;
}
glfwSetErrorCallback(
[](int errorCode, const char* description) { OMNI_LOG_ERROR("GLFW error: %d: %s", errorCode, description); });
int result = glfwInit();
if (GLFW_FALSE == result)
{
OMNI_LOG_ERROR("GLFW initialization failed.");
return false;
}
for (size_t idx = 0; idx < g_sdlGameControllerDbCount; ++idx)
{
glfwUpdateGamepadMappings(g_sdlGameControllerDb[idx]);
}
s_mainThreadId = std::this_thread::get_id();
s_glfwTerminator.reset(new GlfwTerminator);
return true;
}
// A list of objects to iterate over, invoking a supplied callback on each object. While executing a callback, the list
// can be modified by the callback itself.
//
// The design expectation of this class is that there will be few objects, objects will be infrequently added and
// removed from the list, and the callback will be invoked often.
template <typename T>
class CallbackList
{
public:
CallbackList() = default;
// invokes the given function on each item in the list
void call(const std::function<void(T*)>& callback)
{
m_inCallback = true;
// we capture the size of the list at the start of the loop. this ensures that if an object is added while
// processing, it will not be notified (it shouldn't be notified because the event happened before the object
// was added to the list).
size_t i = 0;
size_t sz = m_objects.size();
while (i < sz)
{
if (m_objects[i]) // pointer can be nullptr if it was removed in another callback
{
callback(m_objects[i].get());
}
++i;
}
m_inCallback = false;
// we repack (remove holes) the array if an object was removed during one of the callbacks
if (m_shouldRepack)
{
// remove all nullptrs
m_objects.erase(
std::remove_if(m_objects.begin(), m_objects.end(), [](const omni::ObjectPtr<T>& obj) { return !obj; }),
m_objects.end());
m_shouldRepack = false;
}
}
void add(T* obj)
{
OMNI_ASSERT(obj);
m_objects.emplace_back(obj, omni::kBorrow);
}
void remove(const T* obj)
{
// we use reverse iterators here because we want to remove the latest copy of obj
auto itr = std::find(m_objects.rbegin(), m_objects.rend(), obj);
if (m_objects.rend() != itr)
{
itr->release();
m_shouldRepack = true;
}
}
private:
CARB_PREVENT_COPY_AND_MOVE(CallbackList);
std::vector<omni::ObjectPtr<T>> m_objects;
bool m_inCallback{ false };
bool m_shouldRepack{ false };
};
class Keyboard;
class Mouse;
// A buffer of events. Each event is associated with a device it should be delivered to.
//
// This buffer is used to buffer events generated outside of the main thread. WindowSystem will process the events
// during its event loop.
//
// Events have an associated device pointer, but do not increment the device's reference count. This can lead to the
// device pointer becoming invalid. It is up to devices to call remove() when the device is being destructed to remove
// the device's events from the event buffer.
class EventBuffer
{
private:
enum class EventType
{
kKeyboard,
kMouse,
// when a device is removed, we mark all of its events as removed rather than physically removing them from the
// event list. the list is implicitly cleaned up during the ping-pong.
kRemoved,
};
public:
EventBuffer() = default;
~EventBuffer() = default; // events will be dropped
void add(Keyboard* keyboard, const input::KeyboardEvent& event)
{
UniqueLock lock(m_mutex);
auto buffer = getEventBuffer();
auto itr = buffer->emplace({});
itr->type = EventType::kKeyboard;
itr->keyboard = keyboard;
std::memcpy(&(itr->keyboardEvent), &event, sizeof(event));
}
void remove(Keyboard* keyboard)
{
UniqueLock lock(m_mutex);
for (auto& event : *getEventBuffer())
{
if ((EventType::kKeyboard == event.type) && (event.keyboard == keyboard))
{
event.type = EventType::kRemoved;
}
}
}
void add(Mouse* mouse, const input::MouseEvent* event)
{
UniqueLock lock(m_mutex);
auto buffer = getEventBuffer();
auto itr = buffer->emplace({});
itr->type = EventType::kMouse;
itr->mouse = mouse;
std::memcpy(&(itr->keyboardEvent), event, sizeof(*event));
}
void remove(Mouse* mouse)
{
UniqueLock lock(m_mutex);
for (auto& event : *getEventBuffer())
{
if ((EventType::kMouse == event.type) && (event.mouse == mouse))
{
event.type = EventType::kRemoved;
}
}
}
void processEvents();
bool isProcessingEvents()
{
SharedLock lock(m_mutex);
return m_isProcessingEvents;
}
private:
CARB_PREVENT_COPY_AND_MOVE(EventBuffer);
struct Event
{
EventType type;
union
{
struct
{
Keyboard* keyboard;
input::KeyboardEvent keyboardEvent;
};
struct
{
Mouse* mouse;
input::MouseEvent mouseEvent;
};
};
};
std::vector<Event>* getEventBuffer()
{
return &(m_eventBuffers[m_eventBufferIndex]);
}
carb::thread::recursive_shared_mutex m_mutex;
bool m_isProcessingEvents{ false };
size_t m_eventBufferIndex{ 0 };
std::array<std::vector<Event>, 2> m_eventBuffers;
};
class Gamepad : public omni::Implements<input::IGamepad>
{
protected:
// abi /////////////////////////////////////////////////////////////////////////////////////////////////////////////
virtual void addOnStateChangeConsumer_abi(input::IGamepadOnStateChangeConsumer* consumer) noexcept override
{
UniqueLock lock(m_mutex);
m_onStateChangeConsumers.add(consumer);
}
virtual void removeOnStateChangeConsumer_abi(input::IGamepadOnStateChangeConsumer* consumer) noexcept override
{
UniqueLock lock(m_mutex);
m_onStateChangeConsumers.remove(consumer);
}
virtual void getState_abi(input::GamepadState* outState) noexcept override
{
SharedLock lock(m_mutex);
const input::GamepadState* state = &(m_state[m_stateIndex]);
std::memcpy(outState, state, sizeof(*state));
}
virtual omni::Result setState_abi(const input::GamepadState* incomingState) noexcept override
{
UniqueLock lock(m_mutex);
if (m_inNotify)
{
// we're not allowed to change the state of the gamepad while we're in the process of notifying consumers of
// a state change.
return omni::kResultInvalidState;
}
const input::GamepadState* oldState = &(m_state[m_stateIndex]);
m_stateIndex = (m_stateIndex + 1) % m_state.size();
input::GamepadState* newState = &(m_state[m_stateIndex]);
std::memcpy(newState, incomingState, sizeof(*newState));
notifyConsumers(oldState, newState);
return omni::kResultSuccess;
}
// implementation details //////////////////////////////////////////////////////////////////////////////////////////
public:
using ConnectFn = std::function<void(Gamepad*, bool)>;
Gamepad(ConnectFn connectFn, int glfwIndex) : m_onConnect(std::move(connectFn)), m_glfwIndex{ glfwIndex }
{
input::GamepadState* state = &(m_state[m_stateIndex]);
std::memset(state, 0, sizeof(*state));
update();
}
void update()
{
ASSERT_IS_MAIN_THREAD();
UniqueLock lock(m_mutex);
const input::GamepadState* oldState = &(m_state[m_stateIndex]);
m_stateIndex = (m_stateIndex + 1) % m_state.size();
input::GamepadState* newState = &(m_state[m_stateIndex]);
memset(newState, 0, sizeof(*newState));
newState->isConnected = glfwJoystickPresent(m_glfwIndex);
GLFWgamepadstate state;
if (newState->isConnected && (GLFW_TRUE == glfwGetGamepadState(GLFW_JOYSTICK_1 + m_glfwIndex, &state)))
{
newState->leftStick.x = state.axes[GLFW_GAMEPAD_AXIS_LEFT_X];
newState->leftStick.y = -state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y];
newState->rightStick.x = state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X];
newState->rightStick.y = -state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y];
auto normalize = [](float x) -> float { return (x + 1.0f) / 2.0f; };
newState->leftTrigger = normalize(state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER]);
newState->rightTrigger = normalize(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]);
newState->a = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_A]) ? 1.0f : 0.0f;
newState->b = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_B]) ? 1.0f : 0.0f;
newState->x = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_X]) ? 1.0f : 0.0f;
newState->y = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_Y]) ? 1.0f : 0.0f;
newState->leftStickButton = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB]) ? 1.0f : 0.0f;
newState->rightStickButton = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB]) ? 1.0f : 0.0f;
newState->leftShoulder = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER]) ? 1.0f : 0.0f;
newState->rightShoulder = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER]) ? 1.0f : 0.0f;
newState->select = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_BACK]);
newState->start = (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_START]);
if (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT])
{
newState->directionPad.x = -1;
}
else if (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT])
{
newState->directionPad.x = 1;
}
if (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN])
{
newState->directionPad.y = -1;
}
else if (GLFW_PRESS == state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP])
{
newState->directionPad.y = 1;
}
}
notifyConsumers(oldState, newState);
}
void notifyConsumers(const input::GamepadState* oldState, const input::GamepadState* newState)
{
if (0 == std::memcmp(oldState, newState, sizeof(*newState)))
{
return; // no state change
}
m_inNotify = true;
m_onStateChangeConsumers.call([=](input::IGamepadOnStateChangeConsumer* consumer) {
consumer->onGamepadStateChange(this, oldState, newState);
});
if (oldState->isConnected != newState->isConnected)
{
m_onConnect(this, newState->isConnected);
}
m_inNotify = false;
}
private:
ConnectFn m_onConnect;
int m_glfwIndex;
bool m_inNotify{ false };
std::array<input::GamepadState, 2> m_state;
size_t m_stateIndex{ 0 };
CallbackList<input::IGamepadOnStateChangeConsumer> m_onStateChangeConsumers;
carb::thread::recursive_shared_mutex m_mutex;
};
class WindowSystem : public omni::Implements<windowing::IWindowSystem>
{
protected:
// abi /////////////////////////////////////////////////////////////////////////////////////////////////////////////
virtual windowing::IWindow* createWindow_abi(omni::UInt2 size,
const char* title,
windowing::WindowHints hints) noexcept override;
virtual void addOnGamepadConnectConsumer_abi(input::IGamepadOnConnectConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onGamepadConnectConsumers.add(consumer);
}
virtual void removeOnGamepadConnectConsumer_abi(input::IGamepadOnConnectConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onGamepadConnectConsumers.remove(consumer);
}
virtual void pollEvents_abi() noexcept override;
virtual void waitEvents_abi() noexcept override;
public:
// implementation details //////////////////////////////////////////////////////////////////////////////////////////
WindowSystem();
private:
void pollGamepads() noexcept;
std::shared_ptr<EventBuffer> m_eventBuffer;
std::vector<omni::ObjectPtr<Gamepad>> m_gamepads{ GLFW_JOYSTICK_LAST + 1 };
CallbackList<input::IGamepadOnConnectConsumer> m_onGamepadConnectConsumers;
};
class Keyboard : public omni::Implements<input::IKeyboard>
{
protected:
// abi /////////////////////////////////////////////////////////////////////////////////////////////////////////////
virtual void addOnEventConsumer_abi(input::IKeyboardOnEventConsumer* consumer) noexcept override
{
UniqueLock lock(m_mutex);
m_onEventConsumers.add(consumer);
}
virtual void removeOnEventConsumer_abi(input::IKeyboardOnEventConsumer* consumer) noexcept override
{
UniqueLock lock(m_mutex);
m_onEventConsumers.remove(consumer);
}
virtual bool isKeyDown_abi(input::KeyboardKey key) noexcept override
{
SharedLock lock(m_mutex);
return m_isKeyDown[static_cast<size_t>(key)];
}
virtual omni::Result addEvent_abi(const input::KeyboardEvent* event) noexcept override
{
UniqueLock lock(m_mutex);
if (!isMainThread() || m_eventBuffer->isProcessingEvents())
{
m_eventBuffer->add(this, *event);
}
else
{
doProcessEvent(*event);
}
return omni::kResultSuccess;
}
// implementation details //////////////////////////////////////////////////////////////////////////////////////////
public:
Keyboard(std::shared_ptr<EventBuffer> eventBuffer) : m_eventBuffer(std::move(eventBuffer))
{
}
~Keyboard()
{
// m_eventBuffer doesn't increment our reference count (to avoid cyclical dependencies) so we must explicitly
// remove our events from the event buffer.
m_eventBuffer->remove(this);
}
input::KeyboardModifierFlags getModifiers() noexcept
{
return m_modifiers;
}
void processEvent(const input::KeyboardEvent& event) noexcept
{
UniqueLock lock(m_mutex);
doProcessEvent(event);
}
private:
void doProcessEvent(const input::KeyboardEvent& event) noexcept
{
if (input::KeyboardEventType::eKeyPress == event.type)
{
m_isKeyDown[static_cast<size_t>(event.key)] = true;
}
else if (input::KeyboardEventType::eKeyRelease == event.type)
{
m_isKeyDown[static_cast<size_t>(event.key)] = false;
}
m_modifiers = event.modifiers;
m_onEventConsumers.call(
[=](input::IKeyboardOnEventConsumer* consumer) { consumer->onKeyboardEvent(this, &event); });
}
std::shared_ptr<EventBuffer> m_eventBuffer;
carb::thread::recursive_shared_mutex m_mutex;
CallbackList<input::IKeyboardOnEventConsumer> m_onEventConsumers;
std::bitset<static_cast<size_t>(input::KeyboardKey::eCount)> m_isKeyDown;
input::KeyboardModifierFlags m_modifiers{ 0 };
};
class Mouse : public omni::Implements<input::IMouse>
{
protected:
// abi /////////////////////////////////////////////////////////////////////////////////////////////////////////////
virtual void addOnEventConsumer_abi(input::IMouseOnEventConsumer* consumer) noexcept override
{
UniqueLock lock(m_mutex);
m_onEventConsumers.add(consumer);
}
virtual void removeOnEventConsumer_abi(input::IMouseOnEventConsumer* consumer) noexcept override
{
UniqueLock lock(m_mutex);
m_onEventConsumers.remove(consumer);
}
virtual bool isButtonDown_abi(input::MouseButton button) noexcept override
{
SharedLock lock(m_mutex);
return m_isButtonDown[static_cast<size_t>(button)];
}
virtual omni::Result addEvent_abi(const input::MouseEvent* event) noexcept override
{
UniqueLock lock(m_mutex);
if (!isMainThread() || m_eventBuffer->isProcessingEvents())
{
m_eventBuffer->add(this, event);
}
else
{
doProcessEvent(event);
}
return omni::kResultSuccess;
}
virtual omni::Float2 getPosition_abi() noexcept override
{
SharedLock lock(m_mutex);
return m_position;
}
virtual omni::UInt2 getSize_abi() noexcept override
{
SharedLock lock(m_mutex);
return m_size;
}
// implementation details //////////////////////////////////////////////////////////////////////////////////////////
public:
Mouse(std::shared_ptr<EventBuffer> eventBuffer) : m_eventBuffer(std::move(eventBuffer))
{
}
~Mouse()
{
// m_eventBuffer doesn't increment our reference count (to avoid cyclical dependencies) so we must explicitly
// remove our events from the event buffer.
m_eventBuffer->remove(this);
}
void setSize(omni::UInt2& sz) noexcept
{
UniqueLock lock(m_mutex);
m_size = sz;
}
void processEvent(const input::MouseEvent* event) noexcept
{
UniqueLock lock(m_mutex);
doProcessEvent(event);
}
private:
void doProcessEvent(const input::MouseEvent* event) noexcept
{
if (input::MouseEventType::eButtonPress == event->type)
{
m_isButtonDown[static_cast<size_t>(event->button)] = true;
}
else if (input::MouseEventType::eButtonRelease == event->type)
{
m_isButtonDown[static_cast<size_t>(event->button)] = false;
}
else if (input::MouseEventType::eMotion == event->type)
{
m_position = event->position;
}
m_onEventConsumers.call([=](input::IMouseOnEventConsumer* consumer) { consumer->onMouseEvent(this, event); });
}
std::shared_ptr<EventBuffer> m_eventBuffer;
carb::thread::recursive_shared_mutex m_mutex;
CallbackList<input::IMouseOnEventConsumer> m_onEventConsumers;
std::bitset<static_cast<size_t>(input::MouseButton::eCount)> m_isButtonDown;
omni::Float2 m_position{ 0.0f, 0.0f };
omni::UInt2 m_size{ 0, 0 };
};
int getRectangleOverlap(omni::Int2 r0Pos, omni::Int2 r0Size, omni::Int2 r1Pos, omni::Int2 r1Size)
{
omni::Int2 r0PosSize;
r0PosSize.x = r0Pos.x + r0Size.x;
r0PosSize.y = r0Pos.y + r0Size.y;
omni::Int2 r1PosSize;
r1PosSize.x = r1Pos.x + r1Size.x;
r1PosSize.y = r1Pos.y + r1Size.y;
int overlapX = std::max(0, std::min(r0PosSize.x, r1PosSize.x) - std::max(r0Pos.x, r1Pos.x));
int overlapY = std::max(0, std::min(r0PosSize.y, r1PosSize.y) - std::max(r0Pos.y, r1Pos.y));
return overlapX * overlapY;
}
class Window : public omni::Implements<windowing::IWindow>
{
protected:
// abi /////////////////////////////////////////////////////////////////////////////////////////////////////////////
virtual void setShown_abi(bool shouldShow) noexcept override
{
ASSERT_IS_MAIN_THREAD();
if (shouldShow)
{
glfwShowWindow(m_window);
}
else
{
glfwHideWindow(m_window);
}
}
virtual bool isShown_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return !!glfwGetWindowAttrib(m_window, GLFW_VISIBLE);
}
virtual omni::Int2 getPosition_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
omni::Int2 pos;
glfwGetWindowPos(m_window, &(pos.x), &(pos.y));
return { pos.x, pos.y };
}
virtual void setPosition_abi(omni::Int2 pos) noexcept override
{
ASSERT_IS_MAIN_THREAD();
glfwSetWindowPos(m_window, pos.x, pos.y);
}
virtual void setFullscreen_abi(bool enable) noexcept override
{
ASSERT_IS_MAIN_THREAD();
if (enable == m_isFullscreen)
{
return;
}
if (enable)
{
glfwGetWindowPos(m_window, &(m_windowedPosition.x), &(m_windowedPosition.y));
glfwGetWindowSize(m_window, &(m_windowedSize.x), &(m_windowedSize.y));
GLFWmonitor* monitor = getMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(m_window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
glfwSetWindowSize(m_window, mode->width, mode->height);
}
else
{
glfwSetWindowMonitor(m_window, nullptr, m_windowedPosition.x, m_windowedPosition.y, m_windowedSize.x,
m_windowedSize.y, GLFW_DONT_CARE);
glfwSetWindowSize(m_window, m_windowedSize.x, m_windowedSize.y);
}
m_isFullscreen = enable;
}
virtual bool isFullscreen_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return m_isFullscreen;
}
virtual void setMaximized_abi(bool maximized) noexcept override
{
ASSERT_IS_MAIN_THREAD();
if (maximized == isMaximized_abi())
{
return;
}
if (maximized)
{
glfwMaximizeWindow(m_window);
}
else
{
glfwRestoreWindow(m_window);
}
}
virtual bool isMaximized_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return glfwGetWindowAttrib(m_window, GLFW_MAXIMIZED) == GLFW_TRUE;
}
virtual void addOnMaximizeConsumer_abi(windowing::IWindowOnMaximizeConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onMaximizeConsumers.add(consumer);
}
virtual void removeOnMaximizeConsumer_abi(windowing::IWindowOnMaximizeConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onMaximizeConsumers.remove(consumer);
}
virtual void setMinimized_abi(bool enable) noexcept override
{
ASSERT_IS_MAIN_THREAD();
if (enable == isMinimized_abi())
{
return;
}
if (enable)
{
glfwIconifyWindow(m_window);
}
else
{
glfwRestoreWindow(m_window);
}
}
virtual bool isMinimized_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return glfwGetWindowAttrib(m_window, GLFW_ICONIFIED) == GLFW_TRUE;
}
virtual void addOnMinimizeConsumer_abi(windowing::IWindowOnMinimizeConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onMinimizeConsumers.add(consumer);
}
virtual void removeOnMinimizeConsumer_abi(windowing::IWindowOnMinimizeConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onMinimizeConsumers.remove(consumer);
}
virtual void focus_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
glfwFocusWindow(m_window);
}
virtual bool isFocused_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return !!glfwGetWindowAttrib(m_window, GLFW_FOCUSED);
}
virtual void addOnFocusConsumer_abi(windowing::IWindowOnFocusConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onFocusConsumers.add(consumer);
}
virtual void removeOnFocusConsumer_abi(windowing::IWindowOnFocusConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onFocusConsumers.remove(consumer);
}
virtual void setSize_abi(omni::UInt2 sz) noexcept override
{
ASSERT_IS_MAIN_THREAD();
glfwSetWindowSize(m_window, int(sz.x), int(sz.y));
}
virtual omni::UInt2 getSize_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return { uint32_t(m_size.x), uint32_t(m_size.y) };
}
virtual void addOnResizeConsumer_abi(windowing::IWindowOnResizeConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onResizeConsumers.add(consumer);
}
virtual void removeOnResizeConsumer_abi(windowing::IWindowOnResizeConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onResizeConsumers.remove(consumer);
}
virtual void setShouldClose_abi(bool shouldClose) noexcept override
{
ASSERT_IS_MAIN_THREAD();
glfwSetWindowShouldClose(m_window, shouldClose);
}
virtual bool getShouldClose_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return glfwWindowShouldClose(m_window);
}
virtual void addOnCloseConsumer_abi(windowing::IWindowOnCloseConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onCloseConsumers.add(consumer);
}
virtual void removeOnCloseConsumer_abi(windowing::IWindowOnCloseConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onCloseConsumers.remove(consumer);
}
virtual void addOnDropConsumer_abi(windowing::IWindowOnDropConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onDropConsumers.add(consumer);
}
virtual void removeOnDropConsumer_abi(windowing::IWindowOnDropConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onDropConsumers.remove(consumer);
}
virtual omni::Float2 getContentScale_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
omni::Float2 out;
glfwGetWindowContentScale(m_window, &(out.x), &(out.y));
return out;
}
virtual void addOnContentScaleConsumer_abi(windowing::IWindowOnContentScaleConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onContentScaleConsumers.add(consumer);
}
virtual void removeOnContentScaleConsumer_abi(windowing::IWindowOnContentScaleConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onContentScaleConsumers.remove(consumer);
}
virtual void addOnMouseOverConsumer_abi(windowing::IWindowOnMouseOverConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onMouseOverConsumers.add(consumer);
}
virtual void removeOnMouseOverConsumer_abi(windowing::IWindowOnMouseOverConsumer* consumer) noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_onMouseOverConsumers.remove(consumer);
}
virtual void* getNativeDisplay_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
# if CARB_PLATFORM_WINDOWS
return nullptr;
# elif CARB_PLATFORM_LINUX
return glfwGetX11Display();
# elif CARB_PLATFORM_MACOS
CARB_MACOS_UNIMPLEMENTED(); // CC-673
return nullptr;
# else
CARB_UNSUPPORTED_PLATFORM();
# endif
}
virtual void* getNativeWindow_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
# if CARB_PLATFORM_WINDOWS
return glfwGetWin32Window(m_window);
# elif CARB_PLATFORM_LINUX
return reinterpret_cast<void*>(glfwGetX11Window(m_window));
# elif CARB_PLATFORM_MACOS
CARB_MACOS_UNIMPLEMENTED(); // CC-673
return nullptr;
# else
CARB_UNSUPPORTED_PLATFORM();
# endif
}
virtual input::IKeyboard* getKeyboard_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_keyboard->acquire();
return m_keyboard.get();
}
virtual input::IMouse* getMouse_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
m_mouse->acquire();
return m_mouse.get();
}
virtual void setClipboardText_abi(const char* text) noexcept override
{
ASSERT_IS_MAIN_THREAD();
glfwSetClipboardString(m_window, text);
}
virtual const char* getClipboardText_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return glfwGetClipboardString(m_window);
}
virtual omni::Result setCursor_abi(windowing::CursorType type) noexcept override
{
ASSERT_IS_MAIN_THREAD();
omni::Result result = omni::kResultSuccess;
GLFWcursor* glfwCursor = nullptr;
switch (type)
{
case windowing::CursorType::eArrow:
glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
break;
case windowing::CursorType::eIBeam:
glfwCursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
break;
case windowing::CursorType::eCrosshair:
glfwCursor = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR);
break;
case windowing::CursorType::eHand:
glfwCursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
break;
case windowing::CursorType::eHorizontalResize:
glfwCursor = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
break;
case windowing::CursorType::eVerticalResize:
glfwCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
break;
default:
glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); // fallback to arrow.
result = omni::kResultNotSupported;
}
m_cursor.reset(glfwCursor); // accepts nullptr
glfwSetCursor(m_window, m_cursor.get()); // accepts nullptr
return result;
}
virtual void setCustomCursor_abi(windowing::ICursor* cursor) noexcept override
{
ASSERT_IS_MAIN_THREAD();
if (cursor)
{
omni::UInt2 sz = cursor->getSize();
omni::Int2 hotSpot = cursor->getHotSpot();
GLFWimage image{ int(sz.x), int(sz.y), const_cast<uint8_t*>(cursor->getPixels()) };
GLFWcursor* glfwCursor = glfwCreateCursor(&image, hotSpot.x, hotSpot.y);
m_cursor.reset(glfwCursor);
}
else
{
m_cursor.reset();
}
glfwSetCursor(m_window, m_cursor.get());
}
virtual void setTitle_abi(const char* title) noexcept override
{
ASSERT_IS_MAIN_THREAD();
if (!title)
{
title = "";
}
m_title = title;
glfwSetWindowTitle(m_window, title);
}
virtual const char* getTitle_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return m_title.c_str();
}
virtual void setOpacity_abi(float opacity) noexcept override
{
ASSERT_IS_MAIN_THREAD();
glfwSetWindowOpacity(m_window, opacity);
}
virtual float getOpacity_abi() noexcept override
{
ASSERT_IS_MAIN_THREAD();
return glfwGetWindowOpacity(m_window);
}
// implementation details //////////////////////////////////////////////////////////////////////////////////////////
public:
Window(GLFWwindow* glfwWindow,
const std::shared_ptr<EventBuffer>& eventBuffer,
const char* title,
windowing::WindowHints hints);
~Window()
{
glfwDestroyWindow(m_window);
}
void onMaximize(bool maximized) noexcept
{
m_onMaximizeConsumers.call(
[=](windowing::IWindowOnMaximizeConsumer* consumer) { consumer->onMaximize(this, maximized); });
}
void onMinimize(bool minimized) noexcept
{
m_onMinimizeConsumers.call(
[=](windowing::IWindowOnMinimizeConsumer* consumer) { consumer->onMinimize(this, minimized); });
}
void onFocus(bool focused) noexcept
{
m_onFocusConsumers.call([=](windowing::IWindowOnFocusConsumer* consumer) { consumer->onFocus(this, focused); });
}
void onResize(const omni::Int2 size) noexcept
{
m_size = size;
omni::UInt2 uSize{ uint32_t(m_size.x), uint32_t(m_size.y) };
m_mouse->setSize(uSize);
m_onResizeConsumers.call([=](windowing::IWindowOnResizeConsumer* consumer) { consumer->onResize(this, uSize); });
}
void onClose() noexcept
{
m_onCloseConsumers.call([=](windowing::IWindowOnCloseConsumer* consumer) { consumer->onClose(this); });
}
void onDrop(const char* const* paths, int count) noexcept
{
m_onDropConsumers.call([=](windowing::IWindowOnDropConsumer* consumer) { consumer->onDrop(this, paths, count); });
}
void onContentScale(const omni::Float2& scale) noexcept
{
m_onContentScaleConsumers.call(
[=](windowing::IWindowOnContentScaleConsumer* consumer) { consumer->onContentScale(this, scale); });
}
void onMouseOver(bool over) noexcept
{
// glfw sends a motion event followed by an enter event when the mouse enters the window. we don't want to
// compute the position delta for the initial motion event. so, we mark when the mouse leaves the window.
if (!over)
{
m_shouldComputeMousePositionDelta = false;
}
m_onMouseOverConsumers.call(
[=](windowing::IWindowOnMouseOverConsumer* consumer) { consumer->onMouseOver(this, over); });
}
Keyboard* getKeyboardWithoutAcquire() noexcept
{
return m_keyboard.get();
}
Mouse* getMouseWithoutAcquire() noexcept
{
return m_mouse.get();
}
bool shouldComputeMousePositionDelta() const noexcept
{
return m_shouldComputeMousePositionDelta;
}
void setShouldComputeMousePositionDelta(bool should) noexcept
{
m_shouldComputeMousePositionDelta = should;
}
private:
GLFWmonitor* getMonitor();
GLFWwindow* m_window = nullptr;
bool m_isFullscreen{ false };
omni::Int2 m_size;
omni::Int2 m_windowedSize;
omni::Int2 m_windowedPosition;
omni::ObjectPtr<Keyboard> m_keyboard;
bool m_shouldComputeMousePositionDelta{ false };
omni::ObjectPtr<Mouse> m_mouse;
std::string m_title;
std::unique_ptr<GLFWcursor, decltype(&glfwDestroyCursor)> m_cursor;
CallbackList<windowing::IWindowOnMaximizeConsumer> m_onMaximizeConsumers;
CallbackList<windowing::IWindowOnMinimizeConsumer> m_onMinimizeConsumers;
CallbackList<windowing::IWindowOnFocusConsumer> m_onFocusConsumers;
CallbackList<windowing::IWindowOnResizeConsumer> m_onResizeConsumers;
CallbackList<windowing::IWindowOnCloseConsumer> m_onCloseConsumers;
CallbackList<windowing::IWindowOnDropConsumer> m_onDropConsumers;
CallbackList<windowing::IWindowOnContentScaleConsumer> m_onContentScaleConsumers;
CallbackList<windowing::IWindowOnMouseOverConsumer> m_onMouseOverConsumers;
};
void onWindowMaximize(GLFWwindow* glfwWindow, int maximized)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onMaximize(!!maximized);
}
void onWindowMinimize(GLFWwindow* glfwWindow, int iconified)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onMinimize(!!iconified);
}
void onWindowFocus(GLFWwindow* glfwWindow, int focused)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onFocus(!!focused);
}
void onWindowResize(GLFWwindow* glfwWindow, int width, int height)
{
// We also get here in case the window was minimized, so we need to ignore it
if ((width < 1) || (height < 1))
{
return;
}
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onResize({ width, height });
}
void onWindowClose(GLFWwindow* glfwWindow)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onClose();
}
void onWindowDrop(GLFWwindow* glfwWindow, int count, const char** paths)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onDrop(paths, count);
}
void onWindowContentScale(GLFWwindow* glfwWindow, float scaleX, float scaleY)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onContentScale({ scaleX, scaleY });
}
input::KeyboardKey glfwToKey(int glfwKey)
{
switch (glfwKey)
{
case GLFW_KEY_SPACE:
return input::KeyboardKey::eSpace;
case GLFW_KEY_APOSTROPHE:
return input::KeyboardKey::eApostrophe;
case GLFW_KEY_COMMA:
return input::KeyboardKey::eComma;
case GLFW_KEY_MINUS:
return input::KeyboardKey::eMinus;
case GLFW_KEY_PERIOD:
return input::KeyboardKey::ePeriod;
case GLFW_KEY_SLASH:
return input::KeyboardKey::eSlash;
case GLFW_KEY_0:
return input::KeyboardKey::eKey0;
case GLFW_KEY_1:
return input::KeyboardKey::eKey1;
case GLFW_KEY_2:
return input::KeyboardKey::eKey2;
case GLFW_KEY_3:
return input::KeyboardKey::eKey3;
case GLFW_KEY_4:
return input::KeyboardKey::eKey4;
case GLFW_KEY_5:
return input::KeyboardKey::eKey5;
case GLFW_KEY_6:
return input::KeyboardKey::eKey6;
case GLFW_KEY_7:
return input::KeyboardKey::eKey7;
case GLFW_KEY_8:
return input::KeyboardKey::eKey8;
case GLFW_KEY_9:
return input::KeyboardKey::eKey9;
case GLFW_KEY_SEMICOLON:
return input::KeyboardKey::eSemicolon;
case GLFW_KEY_EQUAL:
return input::KeyboardKey::eEqual;
case GLFW_KEY_A:
return input::KeyboardKey::eA;
case GLFW_KEY_B:
return input::KeyboardKey::eB;
case GLFW_KEY_C:
return input::KeyboardKey::eC;
case GLFW_KEY_D:
return input::KeyboardKey::eD;
case GLFW_KEY_E:
return input::KeyboardKey::eE;
case GLFW_KEY_F:
return input::KeyboardKey::eF;
case GLFW_KEY_G:
return input::KeyboardKey::eG;
case GLFW_KEY_H:
return input::KeyboardKey::eH;
case GLFW_KEY_I:
return input::KeyboardKey::eI;
case GLFW_KEY_J:
return input::KeyboardKey::eJ;
case GLFW_KEY_K:
return input::KeyboardKey::eK;
case GLFW_KEY_L:
return input::KeyboardKey::eL;
case GLFW_KEY_M:
return input::KeyboardKey::eM;
case GLFW_KEY_N:
return input::KeyboardKey::eN;
case GLFW_KEY_O:
return input::KeyboardKey::eO;
case GLFW_KEY_P:
return input::KeyboardKey::eP;
case GLFW_KEY_Q:
return input::KeyboardKey::eQ;
case GLFW_KEY_R:
return input::KeyboardKey::eR;
case GLFW_KEY_S:
return input::KeyboardKey::eS;
case GLFW_KEY_T:
return input::KeyboardKey::eT;
case GLFW_KEY_U:
return input::KeyboardKey::eU;
case GLFW_KEY_V:
return input::KeyboardKey::eV;
case GLFW_KEY_W:
return input::KeyboardKey::eW;
case GLFW_KEY_X:
return input::KeyboardKey::eX;
case GLFW_KEY_Y:
return input::KeyboardKey::eY;
case GLFW_KEY_Z:
return input::KeyboardKey::eZ;
case GLFW_KEY_LEFT_BRACKET:
return input::KeyboardKey::eLeftBracket;
case GLFW_KEY_BACKSLASH:
return input::KeyboardKey::eBackslash;
case GLFW_KEY_RIGHT_BRACKET:
return input::KeyboardKey::eRightBracket;
case GLFW_KEY_GRAVE_ACCENT:
return input::KeyboardKey::eGraveAccent;
case GLFW_KEY_WORLD_1:
return input::KeyboardKey::eUnknown;
case GLFW_KEY_WORLD_2:
return input::KeyboardKey::eUnknown;
case GLFW_KEY_ESCAPE:
return input::KeyboardKey::eEscape;
case GLFW_KEY_ENTER:
return input::KeyboardKey::eEnter;
case GLFW_KEY_TAB:
return input::KeyboardKey::eTab;
case GLFW_KEY_BACKSPACE:
return input::KeyboardKey::eBackspace;
case GLFW_KEY_INSERT:
return input::KeyboardKey::eInsert;
case GLFW_KEY_DELETE:
return input::KeyboardKey::eDel;
case GLFW_KEY_RIGHT:
return input::KeyboardKey::eRight;
case GLFW_KEY_LEFT:
return input::KeyboardKey::eLeft;
case GLFW_KEY_DOWN:
return input::KeyboardKey::eDown;
case GLFW_KEY_UP:
return input::KeyboardKey::eUp;
case GLFW_KEY_PAGE_UP:
return input::KeyboardKey::ePageUp;
case GLFW_KEY_PAGE_DOWN:
return input::KeyboardKey::ePageDown;
case GLFW_KEY_HOME:
return input::KeyboardKey::eHome;
case GLFW_KEY_END:
return input::KeyboardKey::eEnd;
case GLFW_KEY_CAPS_LOCK:
return input::KeyboardKey::eCapsLock;
case GLFW_KEY_SCROLL_LOCK:
return input::KeyboardKey::eScrollLock;
case GLFW_KEY_NUM_LOCK:
return input::KeyboardKey::eNumLock;
case GLFW_KEY_PRINT_SCREEN:
return input::KeyboardKey::ePrintScreen;
case GLFW_KEY_PAUSE:
return input::KeyboardKey::ePause;
case GLFW_KEY_F1:
return input::KeyboardKey::eF1;
case GLFW_KEY_F2:
return input::KeyboardKey::eF2;
case GLFW_KEY_F3:
return input::KeyboardKey::eF3;
case GLFW_KEY_F4:
return input::KeyboardKey::eF4;
case GLFW_KEY_F5:
return input::KeyboardKey::eF5;
case GLFW_KEY_F6:
return input::KeyboardKey::eF6;
case GLFW_KEY_F7:
return input::KeyboardKey::eF7;
case GLFW_KEY_F8:
return input::KeyboardKey::eF8;
case GLFW_KEY_F9:
return input::KeyboardKey::eF9;
case GLFW_KEY_F10:
return input::KeyboardKey::eF10;
case GLFW_KEY_F11:
return input::KeyboardKey::eF11;
case GLFW_KEY_F12:
return input::KeyboardKey::eF12;
case GLFW_KEY_KP_0:
return input::KeyboardKey::eNumpad0;
case GLFW_KEY_KP_1:
return input::KeyboardKey::eNumpad1;
case GLFW_KEY_KP_2:
return input::KeyboardKey::eNumpad2;
case GLFW_KEY_KP_3:
return input::KeyboardKey::eNumpad3;
case GLFW_KEY_KP_4:
return input::KeyboardKey::eNumpad4;
case GLFW_KEY_KP_5:
return input::KeyboardKey::eNumpad5;
case GLFW_KEY_KP_6:
return input::KeyboardKey::eNumpad6;
case GLFW_KEY_KP_7:
return input::KeyboardKey::eNumpad7;
case GLFW_KEY_KP_8:
return input::KeyboardKey::eNumpad8;
case GLFW_KEY_KP_9:
return input::KeyboardKey::eNumpad9;
case GLFW_KEY_KP_DECIMAL:
return input::KeyboardKey::eNumpadDel;
case GLFW_KEY_KP_DIVIDE:
return input::KeyboardKey::eNumpadDivide;
case GLFW_KEY_KP_MULTIPLY:
return input::KeyboardKey::eNumpadMultiply;
case GLFW_KEY_KP_SUBTRACT:
return input::KeyboardKey::eNumpadSubtract;
case GLFW_KEY_KP_ADD:
return input::KeyboardKey::eNumpadAdd;
case GLFW_KEY_KP_ENTER:
return input::KeyboardKey::eNumpadEnter;
case GLFW_KEY_KP_EQUAL:
return input::KeyboardKey::eNumpadEqual;
case GLFW_KEY_LEFT_SHIFT:
return input::KeyboardKey::eLeftShift;
case GLFW_KEY_LEFT_CONTROL:
return input::KeyboardKey::eLeftControl;
case GLFW_KEY_LEFT_ALT:
return input::KeyboardKey::eLeftAlt;
case GLFW_KEY_LEFT_SUPER:
return input::KeyboardKey::eLeftSuper;
case GLFW_KEY_RIGHT_SHIFT:
return input::KeyboardKey::eRightShift;
case GLFW_KEY_RIGHT_CONTROL:
return input::KeyboardKey::eRightControl;
case GLFW_KEY_RIGHT_ALT:
return input::KeyboardKey::eRightAlt;
case GLFW_KEY_RIGHT_SUPER:
return input::KeyboardKey::eRightSuper;
case GLFW_KEY_MENU:
return input::KeyboardKey::eMenu;
case GLFW_KEY_UNKNOWN:
return input::KeyboardKey::eUnknown;
default:
OMNI_ASSERT(!"logic error: not all glfw keys are handled in switch");
return input::KeyboardKey::eUnknown;
}
}
static input::KeyboardModifierFlags glfwToKeyboardModifiers(int glfwModifiers)
{
// mapped one to one currently:
static_assert(GLFW_MOD_SHIFT == input::fKeyboardModifierFlagShift, "");
static_assert(GLFW_MOD_CONTROL == input::fKeyboardModifierFlagControl, "");
static_assert(GLFW_MOD_ALT == input::fKeyboardModifierFlagAlt, "");
static_assert(GLFW_MOD_SUPER == input::fKeyboardModifierFlagSuper, "");
static_assert(GLFW_MOD_CAPS_LOCK == input::fKeyboardModifierFlagCapsLock, "");
static_assert(GLFW_MOD_NUM_LOCK == input::fKeyboardModifierFlagNumLock, "");
return glfwModifiers;
}
void onKey(GLFWwindow* glfwWindow, int key, int scancode, int action, int mods)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
input::KeyboardEvent event;
event.type = ((action == GLFW_RELEASE) ? input::KeyboardEventType::eKeyRelease : input::KeyboardEventType::eKeyPress);
event.key = glfwToKey(key);
event.modifiers = glfwToKeyboardModifiers(mods);
window->getKeyboardWithoutAcquire()->addEvent(&event);
}
input::MouseButton glfwToButton(int glfwButton)
{
switch (glfwButton)
{
case GLFW_MOUSE_BUTTON_1:
return input::MouseButton::eLeft;
case GLFW_MOUSE_BUTTON_2:
return input::MouseButton::eRight;
case GLFW_MOUSE_BUTTON_3:
return input::MouseButton::eMiddle;
case GLFW_MOUSE_BUTTON_4:
return input::MouseButton::e3;
case GLFW_MOUSE_BUTTON_5:
return input::MouseButton::e4;
case GLFW_MOUSE_BUTTON_6:
return input::MouseButton::e5;
case GLFW_MOUSE_BUTTON_7:
return input::MouseButton::e6;
case GLFW_MOUSE_BUTTON_8:
return input::MouseButton::e7;
default:
return input::MouseButton::eUnknown;
}
}
input::MouseModifierFlags glfwToMouseModifiers(int glfwModifiers)
{
// mapped one to one currently:
static_assert(GLFW_MOD_SHIFT == input::fMouseModifierFlagShift, "");
static_assert(GLFW_MOD_CONTROL == input::fMouseModifierFlagControl, "");
static_assert(GLFW_MOD_ALT == input::fMouseModifierFlagAlt, "");
static_assert(GLFW_MOD_SUPER == input::fMouseModifierFlagSuper, "");
static_assert(GLFW_MOD_CAPS_LOCK == input::fMouseModifierFlagCapsLock, "");
static_assert(GLFW_MOD_NUM_LOCK == input::fMouseModifierFlagNumLock, "");
return glfwModifiers;
}
void onMouse(GLFWwindow* glfwWindow, int button, int action, int mods)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
input::MouseEvent event;
event.type = ((action == GLFW_RELEASE) ? input::MouseEventType::eButtonRelease : input::MouseEventType::eButtonPress);
event.button = glfwToButton(button);
event.modifiers = glfwToMouseModifiers(mods);
window->getMouseWithoutAcquire()->addEvent(&event);
}
void onMouseMotion(GLFWwindow* glfwWindow, double mouseX, double mouseY)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
input::MouseEvent event{ input::MouseEventType::eMotion };
event.position.x = float(mouseX);
event.position.y = float(mouseY);
omni::Float2 oldPosition = window->getMouseWithoutAcquire()->getPosition();
if (window->shouldComputeMousePositionDelta())
{
event.positionDelta.x = event.position.x - oldPosition.x;
event.positionDelta.y = event.position.y - oldPosition.y;
}
else
{
event.positionDelta.x = 0.0f;
event.positionDelta.y = 0.0f;
// we don't compute the position delta on the first event
window->setShouldComputeMousePositionDelta(true);
}
event.modifiers = window->getKeyboardWithoutAcquire()->getModifiers();
window->getMouseWithoutAcquire()->addEvent(&event);
}
void onMouseScroll(GLFWwindow* glfwWindow, double scrollX, double scrollY)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
input::MouseEvent event{ input::MouseEventType::eScroll };
event.scrollDelta.x = float(scrollX);
event.scrollDelta.y = float(scrollY);
event.modifiers = window->getKeyboardWithoutAcquire()->getModifiers();
window->getMouseWithoutAcquire()->addEvent(&event);
}
void onMouseEnter(GLFWwindow* glfwWindow, int enter)
{
auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
window->onMouseOver(!!enter);
}
Window::Window(GLFWwindow* glfwWindow,
const std::shared_ptr<EventBuffer>& eventBuffer,
const char* title,
windowing::WindowHints hints)
: m_window(glfwWindow),
m_keyboard{ new Keyboard(eventBuffer), omni::kSteal },
m_mouse{ new Mouse(eventBuffer), omni::kSteal },
m_cursor(nullptr, glfwDestroyCursor)
{
setTitle(title);
glfwSetWindowUserPointer(glfwWindow, this);
glfwSetWindowMaximizeCallback(glfwWindow, onWindowMaximize);
glfwSetWindowIconifyCallback(glfwWindow, onWindowMinimize);
glfwSetWindowFocusCallback(glfwWindow, onWindowFocus);
glfwSetWindowSizeCallback(glfwWindow, onWindowResize);
glfwSetWindowCloseCallback(glfwWindow, onWindowClose);
glfwSetDropCallback(glfwWindow, onWindowDrop);
glfwSetWindowContentScaleCallback(glfwWindow, onWindowContentScale);
glfwSetCursorEnterCallback(glfwWindow, onMouseEnter);
glfwSetKeyCallback(glfwWindow, onKey);
glfwSetMouseButtonCallback(glfwWindow, onMouse);
glfwSetCursorPosCallback(glfwWindow, onMouseMotion);
glfwSetScrollCallback(glfwWindow, onMouseScroll);
// glfwSetCharCallback(glfwWindow, onChar); // TODO
// TODO: needed?
glfwGetWindowSize(glfwWindow, &(m_windowedSize.x), &(m_windowedSize.y));
glfwGetWindowPos(glfwWindow, &(m_windowedPosition.x), &(m_windowedPosition.y));
m_size = m_windowedSize;
setFullscreen(hints & windowing::fWindowHintFullscreen);
}
GLFWmonitor* Window::getMonitor()
{
GLFWmonitor* retMonitor = nullptr;
omni::Int2 wndPos;
glfwGetWindowPos(m_window, &wndPos.x, &wndPos.y);
int monitorCount;
GLFWmonitor** monitors = glfwGetMonitors(&monitorCount);
int bestOverlap = 0;
for (int mIdx = 0; mIdx < monitorCount; ++mIdx)
{
GLFWmonitor* curMonitor = monitors[mIdx];
const GLFWvidmode* mode = glfwGetVideoMode(curMonitor);
omni::Int2 monitorPos;
glfwGetMonitorPos(curMonitor, &monitorPos.x, &monitorPos.y);
int overlap = getRectangleOverlap(
wndPos, { int32_t(m_size.x), int32_t(m_size.y) }, monitorPos, { mode->width, mode->height });
if (bestOverlap < overlap)
{
bestOverlap = overlap;
retMonitor = curMonitor;
}
}
return retMonitor;
}
windowing::IWindow* WindowSystem::createWindow_abi(omni::UInt2 size, const char* title, windowing::WindowHints hints) noexcept
{
OMNI_LOG_VERBOSE(kWindowChannel, "creating GLFW window");
ASSERT_IS_MAIN_THREAD();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
# if CARB_DEBUG
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
# endif
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, (hints & windowing::fWindowHintNoResize) ? GLFW_FALSE : GLFW_TRUE);
glfwWindowHint(GLFW_DECORATED, (hints & windowing::fWindowHintNoDecoration) ? GLFW_FALSE : GLFW_TRUE);
glfwWindowHint(GLFW_AUTO_ICONIFY, (hints & windowing::fWindowHintNoAutoMinimize) ? GLFW_FALSE : GLFW_TRUE);
glfwWindowHint(GLFW_FOCUS_ON_SHOW, (hints & windowing::fWindowHintNoFocusOnShow) ? GLFW_FALSE : GLFW_TRUE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, (hints & windowing::fWindowHintScaleToMonitor) ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_FLOATING, (hints & windowing::fWindowHintFloating) ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_MAXIMIZED, (hints & windowing::fWindowHintMaximized) ? GLFW_TRUE : GLFW_FALSE);
// Due to the issue with GLFW not properly saving the windows hinting state when
// window is created fullscreen - we create window in windowed mode first always,
// this triggers hinting state save on the GLFW side. Then we can proceed to
// change the fullscreen mode to desired.
if (hints & windowing::fWindowHintFullscreen)
{
const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
// Hints to help setting "borderless" fullscreen
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
}
GLFWwindow* glfwWindow = glfwCreateWindow(int(size.x), int(size.y), title, nullptr, nullptr);
if (!glfwWindow)
{
OMNI_LOG_ERROR(kWindowChannel, "GLFW window creation failed.");
return nullptr;
}
auto window{ new Window(glfwWindow, m_eventBuffer, title, hints) };
return window;
}
void WindowSystem::pollEvents_abi() noexcept
{
ASSERT_IS_MAIN_THREAD();
glfwPollEvents();
pollGamepads();
m_eventBuffer->processEvents();
}
void WindowSystem::waitEvents_abi() noexcept
{
ASSERT_IS_MAIN_THREAD();
glfwWaitEvents();
m_eventBuffer->processEvents();
}
WindowSystem::WindowSystem() : m_eventBuffer{ new EventBuffer }
{
OMNI_LOG_VERBOSE(kWindowChannel, "created GLFW IWindowSystem");
}
void WindowSystem::pollGamepads() noexcept
{
ASSERT_IS_MAIN_THREAD();
for (size_t i = 0; i < m_gamepads.size(); ++i)
{
int glfwIndex = GLFW_JOYSTICK_1 + int(i);
bool isPresent = glfwJoystickPresent(glfwIndex);
if (!m_gamepads[i])
{
if (isPresent)
{
m_gamepads[i].steal(new Gamepad(
[=](Gamepad* gamepad, bool isConnected) {
this->m_onGamepadConnectConsumers.call([=](input::IGamepadOnConnectConsumer* consumer) {
consumer->onGamepadConnect(gamepad, isConnected);
});
},
glfwIndex)); // new joystick connected
}
}
else
{
m_gamepads[i]->update();
if (!isPresent)
{
m_gamepads[i].release();
}
}
}
}
void EventBuffer::processEvents()
{
UniqueLock lock(m_mutex);
m_isProcessingEvents = true;
// ping-pong buffers so that we don't process events added while processing events (which could cause an infinite
// loop)
auto buffer = getEventBuffer();
m_eventBufferIndex = (m_eventBufferIndex + 1) % m_eventBuffers.size();
for (auto& event : *buffer)
{
switch (event.type)
{
case EventType::kKeyboard:
event.keyboard->processEvent(event.keyboardEvent);
break;
case EventType::kMouse:
event.mouse->processEvent(&(event.mouseEvent));
break;
case EventType::kRemoved:
// device was removed, meaning there's no one to listen for the event
break;
default:
// this is a non-fatal logic error
OMNI_ASSERT(!"logic error: unknown event type");
}
}
buffer->clear();
m_isProcessingEvents = false;
}
windowing::IWindowSystem* createWindowSystem()
{
if (initialize())
{
return new WindowSystem;
}
else
{
return nullptr;
}
}
void getInterfaceImplementations(const omni::InterfaceImplementation** out, uint32_t* outCount)
{
// clang-format off
static const char* interfacesImplemented[] = { "windowing.IWindowSystem" };
static omni::InterfaceImplementation impls[] =
{
{
"windowing.IWindowSystem-glfw",
[]() { return static_cast<omni::IObject*>(::createWindowSystem()); },
1, // version
interfacesImplemented, CARB_COUNTOF32(interfacesImplemented)
},
};
// clang-format on
*out = impls;
*outCount = CARB_COUNTOF32(impls);
}
} // end of anonymous namespace
# ifdef OMNI_COMPILE_AS_MODULE
OMNI_MODULE_GLOBALS("example-glfw", "GLFW implementation of example.input and example.windowing.");
namespace
{
omni::Result onLoad(const omni::InterfaceImplementation** out, uint32_t* outCount)
{
getInterfaceImplementations(out, outCount);
return omni::kResultSuccess;
}
bool onCanUnload()
{
return true;
}
} // anonymous namespace
OMNI_MODULE_API omni::Result omniModuleGetExports(omni::ModuleExports* out)
{
OMNI_MODULE_SET_EXPORTS(out);
OMNI_MODULE_ON_MODULE_LOAD(out, onLoad);
OMNI_MODULE_ON_MODULE_CAN_UNLOAD(out, onCanUnload);
return omni::kResultSuccess;
}
# else
# include <ExampleGlfw.h>
OMNI_LOG_ADD_CHANNEL(kGlfwChannel, "example-glfw", "GLFW implementation of example.input and example.windowing.");
OMNI_API void exampleRegisterGlfwInterfaceImplementations()
{
omni::log::addModulesChannels();
const omni::InterfaceImplementation* implementations;
uint32_t implementationCount;
getInterfaceImplementations(&implementations, &implementationCount);
omni::registerInterfaceImplementations(implementations, implementationCount);
}
OMNI_API windowing::IWindowSystem* exampleCreateGlfwWindowSystem()
{
return ::createWindowSystem();
}
# endif // OMNI_COMPILE_AS_MODULE
#endif