531 lines
18 KiB
C++
531 lines
18 KiB
C++
// IAEngine: 2D Game Engine by IA
|
|
// Copyright (C) 2025 IASoft (PVT) LTD (oss@iasoft.dev)
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#include <IAEngine/Engine.hpp>
|
|
#include <IAEngine/UI.hpp>
|
|
|
|
#include <Renderer/Renderer.hpp>
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <RmlUi/Core.h>
|
|
#include <RmlUi/Debugger.h>
|
|
|
|
namespace ia::iae
|
|
{
|
|
Rml::Context *g_context{};
|
|
BOOL g_debuggerEnabled{false};
|
|
Rml::ElementDocument *g_document{};
|
|
|
|
class RmlUIRenderInterface : public Rml::RenderInterface
|
|
{
|
|
public:
|
|
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices,
|
|
Rml::Span<const int> indices);
|
|
void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation,
|
|
Rml::TextureHandle texture);
|
|
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry);
|
|
Rml::TextureHandle LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source);
|
|
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions);
|
|
void ReleaseTexture(Rml::TextureHandle texture);
|
|
void EnableScissorRegion(bool enable);
|
|
void SetScissorRegion(Rml::Rectanglei region);
|
|
} g_rmlUIRenderInterface{};
|
|
|
|
class RmlEventListener : public Rml::EventListener
|
|
{
|
|
public:
|
|
RmlEventListener()
|
|
{
|
|
}
|
|
|
|
VOID AddClickListener(IN Rml::Element *element, IN std::function<VOID()> callback)
|
|
{
|
|
element->AddEventListener("click", this);
|
|
m_clickCallbacks[element->GetId().c_str()] = callback;
|
|
}
|
|
|
|
// VOID AddHoverEnterListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
|
|
//{
|
|
// m_document->GetElementById(elementId)->AddEventListener("mouseover", this);
|
|
// m_hoverEnterCallbacks[elementId] = callback;
|
|
// }
|
|
//
|
|
// VOID AddHoverExitListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
|
|
//{
|
|
// m_document->GetElementById(elementId)->AddEventListener("mouseout", this);
|
|
// m_hoverExitCallbacks[elementId] = callback;
|
|
//}
|
|
//
|
|
// VOID AddPointerDownListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
|
|
//{
|
|
// m_document->GetElementById(elementId)->AddEventListener("mousedown", this);
|
|
// m_pointerDownCallbacks[elementId] = callback;
|
|
//}
|
|
//
|
|
// VOID AddPointerUpListener(IN PCCHAR elementId, IN std::function<VOID()> callback)
|
|
//{
|
|
// m_document->GetElementById(elementId)->AddEventListener("mouseup", this);
|
|
// m_pointerUpCallbacks[elementId] = callback;
|
|
//}
|
|
|
|
VOID ProcessEvent(IN Rml::Event &event)
|
|
{
|
|
switch (event.GetId())
|
|
{
|
|
case Rml::EventId::Click:
|
|
m_clickCallbacks[event.GetTargetElement()->GetId().c_str()]();
|
|
break;
|
|
|
|
case Rml::EventId::Mouseover:
|
|
m_hoverEnterCallbacks[event.GetTargetElement()->GetId().c_str()]();
|
|
break;
|
|
|
|
case Rml::EventId::Mouseout:
|
|
m_hoverExitCallbacks[event.GetTargetElement()->GetId().c_str()]();
|
|
break;
|
|
|
|
case Rml::EventId::Mousedown:
|
|
m_pointerDownCallbacks[event.GetTargetElement()->GetId().c_str()]();
|
|
break;
|
|
|
|
case Rml::EventId::Mouseup:
|
|
m_pointerUpCallbacks[event.GetTargetElement()->GetId().c_str()]();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private:
|
|
Map<String, std::function<VOID()>> m_clickCallbacks;
|
|
Map<String, std::function<VOID()>> m_hoverEnterCallbacks;
|
|
Map<String, std::function<VOID()>> m_hoverExitCallbacks;
|
|
Map<String, std::function<VOID()>> m_pointerDownCallbacks;
|
|
Map<String, std::function<VOID()>> m_pointerUpCallbacks;
|
|
} g_eventListener{};
|
|
|
|
/* Taken from https://github.com/mikke89/RmlUi/blob/master/Backends/RmlUi_Platform_SDL.cpp */
|
|
INT32 GetKeyModifierState()
|
|
{
|
|
SDL_Keymod sdl_mods = SDL_GetModState();
|
|
INT32 keymods = 0;
|
|
if (sdl_mods & SDL_KMOD_CTRL)
|
|
keymods |= Rml::Input::KM_CTRL;
|
|
if (sdl_mods & SDL_KMOD_SHIFT)
|
|
keymods |= Rml::Input::KM_SHIFT;
|
|
if (sdl_mods & SDL_KMOD_ALT)
|
|
keymods |= Rml::Input::KM_ALT;
|
|
if (sdl_mods & SDL_KMOD_NUM)
|
|
keymods |= Rml::Input::KM_NUMLOCK;
|
|
if (sdl_mods & SDL_KMOD_CAPS)
|
|
keymods |= Rml::Input::KM_CAPSLOCK;
|
|
return keymods;
|
|
}
|
|
|
|
/* Taken from https://github.com/mikke89/RmlUi/blob/master/Backends/RmlUi_Platform_SDL.cpp */
|
|
INT32 SDLMouseButtonToRml(IN INT32 button)
|
|
{
|
|
switch (button)
|
|
{
|
|
case SDL_BUTTON_LEFT:
|
|
return 0;
|
|
case SDL_BUTTON_RIGHT:
|
|
return 1;
|
|
case SDL_BUTTON_MIDDLE:
|
|
return 2;
|
|
default:
|
|
break;
|
|
}
|
|
return 3;
|
|
}
|
|
|
|
/* Taken from https://github.com/mikke89/RmlUi/blob/master/Backends/RmlUi_Platform_SDL.cpp */
|
|
Rml::Input::KeyIdentifier SDLKeyToRml(int sdlkey)
|
|
{
|
|
switch (sdlkey)
|
|
{
|
|
case SDLK_UNKNOWN:
|
|
return Rml::Input::KI_UNKNOWN;
|
|
case SDLK_ESCAPE:
|
|
return Rml::Input::KI_ESCAPE;
|
|
case SDLK_SPACE:
|
|
return Rml::Input::KI_SPACE;
|
|
case SDLK_0:
|
|
return Rml::Input::KI_0;
|
|
case SDLK_1:
|
|
return Rml::Input::KI_1;
|
|
case SDLK_2:
|
|
return Rml::Input::KI_2;
|
|
case SDLK_3:
|
|
return Rml::Input::KI_3;
|
|
case SDLK_4:
|
|
return Rml::Input::KI_4;
|
|
case SDLK_5:
|
|
return Rml::Input::KI_5;
|
|
case SDLK_6:
|
|
return Rml::Input::KI_6;
|
|
case SDLK_7:
|
|
return Rml::Input::KI_7;
|
|
case SDLK_8:
|
|
return Rml::Input::KI_8;
|
|
case SDLK_9:
|
|
return Rml::Input::KI_9;
|
|
case SDLK_A:
|
|
return Rml::Input::KI_A;
|
|
case SDLK_B:
|
|
return Rml::Input::KI_B;
|
|
case SDLK_C:
|
|
return Rml::Input::KI_C;
|
|
case SDLK_D:
|
|
return Rml::Input::KI_D;
|
|
case SDLK_E:
|
|
return Rml::Input::KI_E;
|
|
case SDLK_F:
|
|
return Rml::Input::KI_F;
|
|
case SDLK_G:
|
|
return Rml::Input::KI_G;
|
|
case SDLK_H:
|
|
return Rml::Input::KI_H;
|
|
case SDLK_I:
|
|
return Rml::Input::KI_I;
|
|
case SDLK_J:
|
|
return Rml::Input::KI_J;
|
|
case SDLK_K:
|
|
return Rml::Input::KI_K;
|
|
case SDLK_L:
|
|
return Rml::Input::KI_L;
|
|
case SDLK_M:
|
|
return Rml::Input::KI_M;
|
|
case SDLK_N:
|
|
return Rml::Input::KI_N;
|
|
case SDLK_O:
|
|
return Rml::Input::KI_O;
|
|
case SDLK_P:
|
|
return Rml::Input::KI_P;
|
|
case SDLK_Q:
|
|
return Rml::Input::KI_Q;
|
|
case SDLK_R:
|
|
return Rml::Input::KI_R;
|
|
case SDLK_S:
|
|
return Rml::Input::KI_S;
|
|
case SDLK_T:
|
|
return Rml::Input::KI_T;
|
|
case SDLK_U:
|
|
return Rml::Input::KI_U;
|
|
case SDLK_V:
|
|
return Rml::Input::KI_V;
|
|
case SDLK_W:
|
|
return Rml::Input::KI_W;
|
|
case SDLK_X:
|
|
return Rml::Input::KI_X;
|
|
case SDLK_Y:
|
|
return Rml::Input::KI_Y;
|
|
case SDLK_Z:
|
|
return Rml::Input::KI_Z;
|
|
case SDLK_SEMICOLON:
|
|
return Rml::Input::KI_OEM_1;
|
|
case SDLK_PLUS:
|
|
return Rml::Input::KI_OEM_PLUS;
|
|
case SDLK_COMMA:
|
|
return Rml::Input::KI_OEM_COMMA;
|
|
case SDLK_MINUS:
|
|
return Rml::Input::KI_OEM_MINUS;
|
|
case SDLK_PERIOD:
|
|
return Rml::Input::KI_OEM_PERIOD;
|
|
case SDLK_SLASH:
|
|
return Rml::Input::KI_OEM_2;
|
|
case SDLK_GRAVE:
|
|
return Rml::Input::KI_OEM_3;
|
|
case SDLK_LEFTBRACKET:
|
|
return Rml::Input::KI_OEM_4;
|
|
case SDLK_BACKSLASH:
|
|
return Rml::Input::KI_OEM_5;
|
|
case SDLK_RIGHTBRACKET:
|
|
return Rml::Input::KI_OEM_6;
|
|
case SDLK_DBLAPOSTROPHE:
|
|
return Rml::Input::KI_OEM_7;
|
|
case SDLK_KP_0:
|
|
return Rml::Input::KI_NUMPAD0;
|
|
case SDLK_KP_1:
|
|
return Rml::Input::KI_NUMPAD1;
|
|
case SDLK_KP_2:
|
|
return Rml::Input::KI_NUMPAD2;
|
|
case SDLK_KP_3:
|
|
return Rml::Input::KI_NUMPAD3;
|
|
case SDLK_KP_4:
|
|
return Rml::Input::KI_NUMPAD4;
|
|
case SDLK_KP_5:
|
|
return Rml::Input::KI_NUMPAD5;
|
|
case SDLK_KP_6:
|
|
return Rml::Input::KI_NUMPAD6;
|
|
case SDLK_KP_7:
|
|
return Rml::Input::KI_NUMPAD7;
|
|
case SDLK_KP_8:
|
|
return Rml::Input::KI_NUMPAD8;
|
|
case SDLK_KP_9:
|
|
return Rml::Input::KI_NUMPAD9;
|
|
case SDLK_KP_ENTER:
|
|
return Rml::Input::KI_NUMPADENTER;
|
|
case SDLK_KP_MULTIPLY:
|
|
return Rml::Input::KI_MULTIPLY;
|
|
case SDLK_KP_PLUS:
|
|
return Rml::Input::KI_ADD;
|
|
case SDLK_KP_MINUS:
|
|
return Rml::Input::KI_SUBTRACT;
|
|
case SDLK_KP_PERIOD:
|
|
return Rml::Input::KI_DECIMAL;
|
|
case SDLK_KP_DIVIDE:
|
|
return Rml::Input::KI_DIVIDE;
|
|
case SDLK_KP_EQUALS:
|
|
return Rml::Input::KI_OEM_NEC_EQUAL;
|
|
case SDLK_BACKSPACE:
|
|
return Rml::Input::KI_BACK;
|
|
case SDLK_TAB:
|
|
return Rml::Input::KI_TAB;
|
|
case SDLK_CLEAR:
|
|
return Rml::Input::KI_CLEAR;
|
|
case SDLK_RETURN:
|
|
return Rml::Input::KI_RETURN;
|
|
case SDLK_PAUSE:
|
|
return Rml::Input::KI_PAUSE;
|
|
case SDLK_CAPSLOCK:
|
|
return Rml::Input::KI_CAPITAL;
|
|
case SDLK_PAGEUP:
|
|
return Rml::Input::KI_PRIOR;
|
|
case SDLK_PAGEDOWN:
|
|
return Rml::Input::KI_NEXT;
|
|
case SDLK_END:
|
|
return Rml::Input::KI_END;
|
|
case SDLK_HOME:
|
|
return Rml::Input::KI_HOME;
|
|
case SDLK_LEFT:
|
|
return Rml::Input::KI_LEFT;
|
|
case SDLK_UP:
|
|
return Rml::Input::KI_UP;
|
|
case SDLK_RIGHT:
|
|
return Rml::Input::KI_RIGHT;
|
|
case SDLK_DOWN:
|
|
return Rml::Input::KI_DOWN;
|
|
case SDLK_INSERT:
|
|
return Rml::Input::KI_INSERT;
|
|
case SDLK_DELETE:
|
|
return Rml::Input::KI_DELETE;
|
|
case SDLK_HELP:
|
|
return Rml::Input::KI_HELP;
|
|
case SDLK_F1:
|
|
return Rml::Input::KI_F1;
|
|
case SDLK_F2:
|
|
return Rml::Input::KI_F2;
|
|
case SDLK_F3:
|
|
return Rml::Input::KI_F3;
|
|
case SDLK_F4:
|
|
return Rml::Input::KI_F4;
|
|
case SDLK_F5:
|
|
return Rml::Input::KI_F5;
|
|
case SDLK_F6:
|
|
return Rml::Input::KI_F6;
|
|
case SDLK_F7:
|
|
return Rml::Input::KI_F7;
|
|
case SDLK_F8:
|
|
return Rml::Input::KI_F8;
|
|
case SDLK_F9:
|
|
return Rml::Input::KI_F9;
|
|
case SDLK_F10:
|
|
return Rml::Input::KI_F10;
|
|
case SDLK_F11:
|
|
return Rml::Input::KI_F11;
|
|
case SDLK_F12:
|
|
return Rml::Input::KI_F12;
|
|
case SDLK_F13:
|
|
return Rml::Input::KI_F13;
|
|
case SDLK_F14:
|
|
return Rml::Input::KI_F14;
|
|
case SDLK_F15:
|
|
return Rml::Input::KI_F15;
|
|
case SDLK_NUMLOCKCLEAR:
|
|
return Rml::Input::KI_NUMLOCK;
|
|
case SDLK_SCROLLLOCK:
|
|
return Rml::Input::KI_SCROLL;
|
|
case SDLK_LSHIFT:
|
|
return Rml::Input::KI_LSHIFT;
|
|
case SDLK_RSHIFT:
|
|
return Rml::Input::KI_RSHIFT;
|
|
case SDLK_LCTRL:
|
|
return Rml::Input::KI_LCONTROL;
|
|
case SDLK_RCTRL:
|
|
return Rml::Input::KI_RCONTROL;
|
|
case SDLK_LALT:
|
|
return Rml::Input::KI_LMENU;
|
|
case SDLK_RALT:
|
|
return Rml::Input::KI_RMENU;
|
|
case SDLK_LGUI:
|
|
return Rml::Input::KI_LMETA;
|
|
case SDLK_RGUI:
|
|
return Rml::Input::KI_RMETA;
|
|
default:
|
|
break;
|
|
}
|
|
return Rml::Input::KI_UNKNOWN;
|
|
}
|
|
} // namespace ia::iae
|
|
|
|
namespace ia::iae
|
|
{
|
|
VOID UI::Initialize()
|
|
{
|
|
Rml::SetRenderInterface(&g_rmlUIRenderInterface);
|
|
Rml::Initialise();
|
|
|
|
const auto displayExtent = Engine::GetDisplayExtent();
|
|
g_context = Rml::CreateContext("main", Rml::Vector2i(displayExtent.x, displayExtent.y));
|
|
Rml::Debugger::Initialise(g_context);
|
|
Rml::Debugger::SetVisible(false);
|
|
g_document = g_context->CreateDocument();
|
|
}
|
|
|
|
VOID UI::Terminate()
|
|
{
|
|
Rml::Shutdown();
|
|
}
|
|
|
|
VOID UI::Update()
|
|
{
|
|
if (Engine::WasInputKeyPressed(InputKey::F8))
|
|
Rml::Debugger::SetVisible(g_debuggerEnabled = !g_debuggerEnabled);
|
|
|
|
g_context->Update();
|
|
}
|
|
|
|
VOID UI::Draw()
|
|
{
|
|
g_context->Render();
|
|
}
|
|
|
|
VOID UI::OnSDLEvent(IN PVOID _event)
|
|
{
|
|
const auto keymods = GetKeyModifierState();
|
|
const auto event = (SDL_Event *) _event;
|
|
switch (event->type)
|
|
{
|
|
case SDL_EventType::SDL_EVENT_MOUSE_MOTION:
|
|
g_context->ProcessMouseMove(event->motion.x, event->motion.y, keymods);
|
|
break;
|
|
case SDL_EventType::SDL_EVENT_MOUSE_BUTTON_UP:
|
|
g_context->ProcessMouseButtonUp(SDLMouseButtonToRml(event->button.button), keymods);
|
|
break;
|
|
case SDL_EventType::SDL_EVENT_MOUSE_BUTTON_DOWN:
|
|
g_context->ProcessMouseButtonDown(SDLMouseButtonToRml(event->button.button), keymods);
|
|
break;
|
|
case SDL_EventType::SDL_EVENT_KEY_DOWN:
|
|
g_context->ProcessKeyDown(SDLKeyToRml(event->key.key), keymods);
|
|
break;
|
|
case SDL_EventType::SDL_EVENT_KEY_UP:
|
|
g_context->ProcessKeyUp(SDLKeyToRml(event->key.key), keymods);
|
|
break;
|
|
case SDL_EventType::SDL_EVENT_TEXT_INPUT:
|
|
g_context->ProcessTextInput(Rml::String(&event->text.text[0]));
|
|
break;
|
|
}
|
|
}
|
|
|
|
VOID UI::OnScreenResize(IN INT32 newWidth, IN INT32 newHeight)
|
|
{
|
|
}
|
|
|
|
VOID UI::AddFontFromFile(IN CONST String &path)
|
|
{
|
|
Rml::LoadFontFace(path.c_str());
|
|
}
|
|
|
|
VOID UI::SetHTML(IN CONST String &source)
|
|
{
|
|
g_document->SetInnerRML(
|
|
BuildString("<body style=\"display: block; width: 100%; height: 100%;\">", source, "</body>").c_str());
|
|
}
|
|
|
|
VOID UI::AddClickEvent(IN PCCHAR elementId, IN std::function<VOID()> callback)
|
|
{
|
|
g_eventListener.AddClickListener(g_document->GetElementById(elementId), callback);
|
|
}
|
|
} // namespace ia::iae
|
|
|
|
namespace ia::iae
|
|
{
|
|
Rml::CompiledGeometryHandle RmlUIRenderInterface::CompileGeometry(Rml::Span<const Rml::Vertex> vertices,
|
|
Rml::Span<const int> indices)
|
|
{
|
|
Vector<GeometryVertex> _vertices;
|
|
Vector<INT32> _indices;
|
|
for (const auto &v : vertices)
|
|
_vertices.pushBack({Vec2{v.position.x, v.position.y}, Vec2{v.tex_coord.x, v.tex_coord.y},
|
|
Vec4{v.colour.red / 255.0f, v.colour.green / 255.0f, v.colour.blue / 255.0f,
|
|
v.colour.alpha / 255.0f}});
|
|
for (const auto &v : indices)
|
|
_indices.pushBack(v);
|
|
return (Rml::CompiledGeometryHandle) Engine::CreateGeometry(_vertices, _indices);
|
|
}
|
|
|
|
void RmlUIRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation,
|
|
Rml::TextureHandle texture)
|
|
{
|
|
|
|
Engine::SetRenderState_Texture((Handle) texture);
|
|
Engine::SetRenderState_FlippedH(false);
|
|
Engine::SetRenderState_FlippedV(false);
|
|
Engine::SetRenderState_ColorOverlay({255, 255, 255, 255});
|
|
Engine::SetRenderState_TextureOffset({0, 0});
|
|
Engine::SetRenderState_CameraRelative(false);
|
|
Engine::SetRenderState_Transform({translation.x, translation.y}, {1.0f, 1.0f}, 0, 0xFF, 0);
|
|
|
|
Engine::DrawGeometry((Handle) geometry);
|
|
}
|
|
|
|
void RmlUIRenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle geometry)
|
|
{
|
|
Engine::DestroyGeometry((Handle) geometry);
|
|
}
|
|
|
|
Rml::TextureHandle RmlUIRenderInterface::LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source)
|
|
{
|
|
return Engine::CreateImageFromFile(Engine::GetUniqueResourceName(), source.c_str(), texture_dimensions.x,
|
|
texture_dimensions.y);
|
|
}
|
|
|
|
Rml::TextureHandle RmlUIRenderInterface::GenerateTexture(Rml::Span<const Rml::byte> source,
|
|
Rml::Vector2i source_dimensions)
|
|
{
|
|
return Engine::CreateImage(Engine::GetUniqueResourceName(), (PCUINT8) source.data(), source_dimensions.x,
|
|
source_dimensions.y);
|
|
}
|
|
|
|
void RmlUIRenderInterface::ReleaseTexture(Rml::TextureHandle texture)
|
|
{
|
|
Engine::DestroyImage((Handle) texture);
|
|
}
|
|
|
|
void RmlUIRenderInterface::EnableScissorRegion(bool enable)
|
|
{
|
|
if (!enable)
|
|
Engine::SetRenderState_Scissor({});
|
|
}
|
|
|
|
void RmlUIRenderInterface::SetScissorRegion(Rml::Rectanglei region)
|
|
{
|
|
Engine::SetRenderState_Scissor({region.Left(), region.Top(), region.Right(), region.Bottom()});
|
|
}
|
|
} // namespace ia::iae
|