/* OmniaFramework - A collection of useful functionality Copyright (C) 2025 OmniaX-Dev This file is part of OmniaFramework. OmniaFramework 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. OmniaFramework 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 OmniaFramework. If not, see . */ #include "Window.hpp" #include "../../ostd/utils/Time.hpp" #include "utils/Keycodes.hpp" namespace ogfx { const Uint32 REDRAW_EVENT = SDL_RegisterEvents(1); WindowCore::~WindowCore(void) { __on_window_destroy(); SDL_DestroyCursor(m_cursor_Default); SDL_DestroyCursor(m_cursor_Text); SDL_DestroyCursor(m_cursor_Wait); SDL_DestroyCursor(m_cursor_Crosshair); SDL_DestroyCursor(m_cursor_Progress); SDL_DestroyCursor(m_cursor_NWSE_Resize); SDL_DestroyCursor(m_cursor_NESW_Resize); SDL_DestroyCursor(m_cursor_EW_Resize); SDL_DestroyCursor(m_cursor_NS_Resize); SDL_DestroyCursor(m_cursor_Move); SDL_DestroyCursor(m_cursor_NotAllowed); SDL_DestroyCursor(m_cursor_Pointer); SDL_DestroyCursor(m_cursor_NW_Resize); SDL_DestroyCursor(m_cursor_N_Resize); SDL_DestroyCursor(m_cursor_NE_Resize); SDL_DestroyCursor(m_cursor_E_Resize); SDL_DestroyCursor(m_cursor_SE_Resize); SDL_DestroyCursor(m_cursor_S_Resize); SDL_DestroyCursor(m_cursor_SW_Resize); SDL_DestroyCursor(m_cursor_W_Resize); SDL_DestroyRenderer(m_renderer); SDL_DestroyWindow(m_window); SDLSysten::release(); } void WindowCore::initialize(int32_t width, int32_t height, const ostd::String& title) { if (m_initialized) return; SDLSysten::acquire(); m_windowWidth = width; m_windowHeight = height; m_title = title; setBlockingEventsRefreshFPS(DefaultBlockingEventsFPS); m_window = SDL_CreateWindow("", m_windowWidth, m_windowHeight, SDL_WINDOW_RESIZABLE); m_renderer = SDL_CreateRenderer(m_window, nullptr); SDL_SetWindowMinimumSize(m_window, m_windowWidth, m_windowHeight); SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); SDL_SetWindowTitle(m_window, m_title.c_str()); SDL_StartTextInput(m_window); SDL_ShowWindow(m_window); m_cursor_Default = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); m_cursor_Text = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT); m_cursor_Wait = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); m_cursor_Crosshair = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); m_cursor_Progress = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_PROGRESS); m_cursor_NWSE_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE); m_cursor_NESW_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NESW_RESIZE); m_cursor_EW_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_EW_RESIZE); m_cursor_NS_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NS_RESIZE); m_cursor_Move = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE); m_cursor_NotAllowed = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED); m_cursor_Pointer = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER); m_cursor_NW_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NW_RESIZE); m_cursor_N_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_N_RESIZE); m_cursor_NE_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE); m_cursor_E_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_E_RESIZE); m_cursor_SE_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SE_RESIZE); m_cursor_S_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_S_RESIZE); m_cursor_SW_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SW_RESIZE); m_cursor_W_Resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_W_RESIZE); m_wout.attachWindow(*this); m_wout.setFontSize(22); m_wout.setConsoleMaxCharacters({ 1000, 1000 }); m_wout.setConsolePosition({ 5, 5 }); m_wout.setWrapMode(ogfx::GraphicsWindowOutputHandler::eWrapMode::NewLine); m_wout.setDefaultForegroundColor({ 255, 255, 255, 255 }); m_initialized = true; m_running = true; setTypeName("ogfx::WindowCore"); enableSignals(true); validate(); setSize(m_windowWidth, m_windowHeight); connectSignal(ostd::BuiltinSignals::KeyPressed); connectSignal(ostd::BuiltinSignals::KeyReleased); connectSignal(ostd::BuiltinSignals::TextEntered); connectSignal(ostd::BuiltinSignals::MousePressed); connectSignal(ostd::BuiltinSignals::MouseReleased); connectSignal(ostd::BuiltinSignals::MouseMoved); connectSignal(ostd::BuiltinSignals::MouseMoved); connectSignal(ostd::BuiltinSignals::OnGuiEvent); connectSignal(ostd::BuiltinSignals::WindowClosed); connectSignal(ostd::BuiltinSignals::WindowResized); connectSignal(ostd::BuiltinSignals::WindowFocused); connectSignal(ostd::BuiltinSignals::WindowLostFocus); __on_window_init(width, height, title); setSize(m_windowWidth, m_windowHeight); } void WindowCore::mainLoop(void) { if (isInitialized()) __main_loop(); } void WindowCore::close(void) { __on_window_close(); m_running = false; ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::WindowClosed, ostd::Signal::Priority::Normal, *this); } void WindowCore::setSize(int32_t width, int32_t height) { if (!isInitialized()) return; SDL_SetWindowSize(m_window, width, height); ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::WindowResized, ostd::Signal::Priority::RealTime); } void WindowCore::setTitle(const ostd::String& title) { if (!isInitialized()) return; m_title = title; SDL_SetWindowTitle(m_window, m_title.c_str()); } void WindowCore::setCursor(eCursor cursor) { switch (cursor) { case eCursor::Default: SDL_SetCursor(m_cursor_Default); break; case eCursor::Text: SDL_SetCursor(m_cursor_Text); break; case eCursor::Wait: SDL_SetCursor(m_cursor_Wait); break; case eCursor::Crosshair: SDL_SetCursor(m_cursor_Crosshair); break; case eCursor::Progress: SDL_SetCursor(m_cursor_Progress); break; case eCursor::NWSE_Resize: SDL_SetCursor(m_cursor_NWSE_Resize); break; case eCursor::NESW_Resize: SDL_SetCursor(m_cursor_NESW_Resize); break; case eCursor::EW_Resize: SDL_SetCursor(m_cursor_EW_Resize); break; case eCursor::NS_Resize: SDL_SetCursor(m_cursor_NS_Resize); break; case eCursor::Move: SDL_SetCursor(m_cursor_Move); break; case eCursor::NotAllowed: SDL_SetCursor(m_cursor_NotAllowed); break; case eCursor::Pointer: SDL_SetCursor(m_cursor_Pointer); break; case eCursor::NW_Resize: SDL_SetCursor(m_cursor_NW_Resize); break; case eCursor::N_Resize: SDL_SetCursor(m_cursor_N_Resize); break; case eCursor::NE_Resize: SDL_SetCursor(m_cursor_NE_Resize); break; case eCursor::E_Resize: SDL_SetCursor(m_cursor_E_Resize); break; case eCursor::SE_Resize: SDL_SetCursor(m_cursor_SE_Resize); break; case eCursor::S_Resize: SDL_SetCursor(m_cursor_S_Resize); break; case eCursor::SW_Resize: SDL_SetCursor(m_cursor_SW_Resize); break; case eCursor::W_Resize: SDL_SetCursor(m_cursor_W_Resize); break; default: SDL_SetCursor(m_cursor_Default); break; } } void WindowCore::enableResizable(bool enable) { SDL_SetWindowResizable(m_window, enable); m_resizeable = enable; } void WindowCore::setIcon(const ostd::String& iconFilePath) { SDL_Surface* appIcon = IMG_Load(iconFilePath); if (appIcon == nullptr) { //TODO: Error return; } SDL_SetWindowIcon(m_window, appIcon); SDL_DestroySurface(appIcon); } void WindowCore::setBlockingEventsRefreshFPS(uint32_t fps) { if (fps == 0 || fps > MaxBlockingEventsFPS) { setBlockingEventsRefreshFPS(DefaultBlockingEventsFPS); return; } m_blockingEventsDelay = static_cast(std::floor((1.0 / (double)fps) * 1000)); } void WindowCore::requestRedraw(void) { SDL_Event e{}; SDL_zero(e); e.type = REDRAW_EVENT; SDL_PushEvent(&e); } void WindowCore::handleSignal(ostd::Signal& signal) { __on_signal(signal); } MouseEventData WindowCore::get_mouse_state(void) { float mx = 0, my = 0; uint32_t btn = SDL_GetMouseState(&mx, &my); MouseEventData::eButton button = MouseEventData::eButton::None; switch (btn) { case SDL_BUTTON_MASK(1): button = MouseEventData::eButton::Left; break; case SDL_BUTTON_MASK(2): button = MouseEventData::eButton::Middle; break; case SDL_BUTTON_MASK(3): button = MouseEventData::eButton::Right; break; default: button = MouseEventData::eButton::None; break; } MouseEventData mmd(*this, mx, my, button); return mmd; } void WindowCore::handle_events(void) { if (!isInitialized()) return; SDL_Event event; if (isBlockingEventsEnabled()) { if (SDL_WaitEventTimeout(&event, m_blockingEventsDelay)) __handle_event(event); } else { while (SDL_PollEvent(&event)) __handle_event(event); } } void WindowCore::before_render(void) { SDL_SetRenderDrawColor(m_renderer, getClearColor().r, getClearColor().g, getClearColor().b, getClearColor().a); if (m_refreshScreen) SDL_RenderClear(m_renderer); wout().beginFrame(); } void WindowCore::after_render(void) { SDL_RenderPresent(m_renderer); ostd::SignalHandler::handleDelegateSignals(); } void WindowCore::__handle_event(SDL_Event& event) { if (event.type == REDRAW_EVENT) { //Doesn't need to do anything, the event exists just to make SDL_WaitEventTimeout() return early } else if (event.type == SDL_EVENT_QUIT) { close(); } else if (event.type == SDL_EVENT_WINDOW_FOCUS_GAINED) { ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::WindowFocused, ostd::Signal::Priority::RealTime, *this); } else if (event.type == SDL_EVENT_WINDOW_FOCUS_LOST) { ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::WindowLostFocus, ostd::Signal::Priority::RealTime, *this); } else if (event.type == SDL_EVENT_WINDOW_RESIZED) { WindowResizedData wrd(*this, m_windowWidth, m_windowHeight, 0, 0); SDL_GetWindowSize(m_window, &m_windowWidth, &m_windowHeight); wrd.new_width = m_windowWidth; wrd.new_height = m_windowHeight; ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::WindowResized, ostd::Signal::Priority::RealTime, wrd); } else if (event.type == SDL_EVENT_MOUSE_MOTION) { MouseEventData mmd = get_mouse_state(); ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::MouseMoved, ostd::Signal::Priority::RealTime, mmd); } else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { MouseEventData mmd = get_mouse_state(); ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::MousePressed, ostd::Signal::Priority::RealTime, mmd); } else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) { MouseEventData mmd = get_mouse_state(); switch (event.button.button) { case SDL_BUTTON_MASK(1): mmd.button = MouseEventData::eButton::Left; break; case SDL_BUTTON_MASK(2): mmd.button = MouseEventData::eButton::Middle; break; case SDL_BUTTON_MASK(3): mmd.button = MouseEventData::eButton::Right; break; default: mmd.button = MouseEventData::eButton::None; break; } ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::MouseReleased, ostd::Signal::Priority::RealTime, mmd); } else if (event.type == SDL_EVENT_KEY_DOWN) { KeyEventData ked(*this, (int32_t)event.key.key, 0, KeyEventData::eKeyEvent::Pressed); ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::KeyPressed, ostd::Signal::Priority::RealTime, ked); } else if (event.type == SDL_EVENT_KEY_UP) { KeyEventData ked(*this, (int32_t)event.key.key, 0, KeyEventData::eKeyEvent::Released); ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::KeyReleased, ostd::Signal::Priority::RealTime, ked); } else if (event.type == SDL_EVENT_TEXT_INPUT) { KeyEventData ked(*this, 0, event.text.text[0], KeyEventData::eKeyEvent::Text); ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::TextEntered, ostd::Signal::Priority::RealTime, ked); } __on_event(event); } void GraphicsWindow::__on_window_init(int32_t width, int32_t height, const ostd::String& title) { SDL_SetWindowResizable(m_window, false); m_fixedUpdateTImer.create(60.0, [this](double frameTime_s){ this->onFixedUpdate(frameTime_s); }); m_fpsUpdateTimer.create(1.0, [this](double frameTime_s){ if (this->m_frameCount == 0) return; if (this->m_frameTimeAcc == 0) return; this->m_fps = (int32_t)(1.0f / (static_cast(this->m_frameTimeAcc) / static_cast(this->m_frameCount))); this->m_frameTimeAcc = 0; this->m_frameCount = 0; }); setTypeName("ogfx::GraphicsWindow"); onInitialize(); } void GraphicsWindow::__on_window_close(void) { onClose(); } void GraphicsWindow::__main_loop(void) { handle_events(); before_render(); m_fixedUpdateTImer.update(); onUpdate(); onRender(); after_render(); m_frameTimeAcc += m_fpsUpdateClock.restart(ostd::eTimeUnits::Seconds); m_frameCount++; m_fpsUpdateTimer.update(); } void GraphicsWindow::__on_signal(ostd::Signal& signal) { onSignal(signal); } void GraphicsWindow::__on_event(SDL_Event& event) { onSDLEvent(event); } void GraphicsWindow::__on_window_destroy(void) { onDestroy(); } namespace gui { void Window::addWidget(Widget& widget) { m_rootWidget.addChild(widget); setTheme(*m_guiTheme); } void Window::setTheme(const gui::Theme& theme) { m_guiTheme = &theme; m_rootWidget.__applyTheme(theme, true); } void Window::__on_window_init(int32_t width, int32_t height, const ostd::String& title) { enableBlockingEvents(); setTypeName("ogfx::gui::Window"); m_gfx.init(*this); loadDefaultTHeme(); onInitialize(); m_rootWidget.setSize(static_cast(width), static_cast(height)); } void Window::__on_window_close(void) { onClose(); } void Window::__main_loop(void) { while (isRunning()) { handle_events(); before_render(); m_rootWidget.__update(); m_rootWidget.__draw(m_gfx); onRedraw(m_gfx); after_render(); } } void Window::__on_signal(ostd::Signal& signal) { Event evt(*this); if (signal.ID == ostd::BuiltinSignals::WindowClosed) { m_rootWidget.__onWindowClosed(evt); } else if (signal.ID == ostd::BuiltinSignals::WindowFocused) { m_rootWidget.__onWIndowFocused(evt); } else if (signal.ID == ostd::BuiltinSignals::WindowLostFocus) { m_rootWidget.__onWindowFocusLost(evt); } else if (signal.ID == ostd::BuiltinSignals::WindowResized) { evt.windowResized = &(ogfx::WindowResizedData&)signal.userData; m_rootWidget.__onWindowResized(evt); } else if (signal.ID == ostd::BuiltinSignals::MouseMoved) { evt.mouse = &(ogfx::MouseEventData&)signal.userData; m_rootWidget.__onMouseMoved(evt); } else if (signal.ID == ostd::BuiltinSignals::MousePressed) { evt.mouse = &(ogfx::MouseEventData&)signal.userData; m_rootWidget.__onMousePressed(evt); } else if (signal.ID == ostd::BuiltinSignals::MouseReleased) { evt.mouse = &(ogfx::MouseEventData&)signal.userData; m_rootWidget.__onMouseReleased(evt); } else if (signal.ID == ostd::BuiltinSignals::KeyPressed) { evt.keyboard = &(ogfx::KeyEventData&)signal.userData; m_rootWidget.__onKeyPressed(evt); } else if (signal.ID == ostd::BuiltinSignals::KeyReleased) { evt.keyboard = &(ogfx::KeyEventData&)signal.userData; m_rootWidget.__onKeyReleased(evt); if (evt.keyboard->keyCode == KeyCode::Escape) close(); } else if (signal.ID == ostd::BuiltinSignals::TextEntered) { evt.keyboard = &(ogfx::KeyEventData&)signal.userData; m_rootWidget.__onTextEntered(evt); } onSignal(signal); } void Window::__on_event(SDL_Event& event) { onSDLEvent(event); } void Window::__on_window_destroy(void) { onDestroy(); } } }