/* * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. */ #include #include #include #if TURBINE_USE_IMGUI #include #include #endif #include "config.hpp" #include #include "context.hpp" namespace turbine { namespace gl { void addDrawer(Context &ctx, Drawer *cd) noexcept { ctx.drawers.emplace_back(cd); } void removeDrawer(Context &ctx, Drawer *cd) noexcept { for (auto i = 0u; i < ctx.drawers.size(); ++i) { if (ctx.drawers[i] == cd) { std::ignore = ctx.drawers.erase(i); break; } } } } static void handleGlfwError(int err, char const*desc) noexcept { oxErrf("GLFW error ({}): {}\n", err, desc); } static auto setKeyDownStatus(Context &ctx, Key key, bool down) noexcept { ctx.keysDown &= ~(1llu << static_cast(key)); ctx.keysDown |= static_cast(down) << static_cast(key); } static void handleKeyPress(Context &ctx, int key, bool down) noexcept { static constexpr auto keyMap = [] { ox::Array map = {}; for (auto i = 0u; i < 26; ++i) { map[GLFW_KEY_A + i] = static_cast(static_cast(Key::Alpha_A) + i); } for (auto i = 0u; i < 10; ++i) { map[GLFW_KEY_0 + i] = static_cast(static_cast(Key::Num_0) + i); } map[GLFW_KEY_LEFT_ALT] = Key::Mod_Alt; map[GLFW_KEY_RIGHT_ALT] = Key::Mod_Alt; map[GLFW_KEY_LEFT_CONTROL] = Key::Mod_Ctrl; map[GLFW_KEY_RIGHT_CONTROL] = Key::Mod_Ctrl; map[GLFW_KEY_LEFT_SUPER] = Key::Mod_Super; map[GLFW_KEY_RIGHT_SUPER] = Key::Mod_Super; map[GLFW_KEY_LEFT_SHIFT] = Key::Mod_Shift; map[GLFW_KEY_RIGHT_SHIFT] = Key::Mod_Shift; map[GLFW_KEY_ESCAPE] = Key::Escape; return map; }(); auto const eventHandler = keyEventHandler(ctx); auto const keyIdx = static_cast(key); if (keyIdx < keyMap.size()) { auto const k = keyMap[keyIdx]; setKeyDownStatus(ctx, k, down); if (eventHandler) { eventHandler(ctx, k, down); } } } static void handleGlfwCursorPosEvent(GLFWwindow*, double, double) noexcept { } static void handleGlfwMouseButtonEvent(GLFWwindow *window, int, int, int) noexcept { auto &ctx = *static_cast(glfwGetWindowUserPointer(window)); ctx.mandatoryRefreshPeriodEnd = ticksMs(ctx) + config::MandatoryRefreshPeriod; } static void handleGlfwKeyEvent(GLFWwindow *window, int key, int, int action, int) noexcept { auto &ctx = *static_cast(glfwGetWindowUserPointer(window)); ctx.mandatoryRefreshPeriodEnd = ticksMs(ctx) + config::MandatoryRefreshPeriod; if (action == GLFW_PRESS) { handleKeyPress(ctx, key, true); } else if (action == GLFW_RELEASE) { handleKeyPress(ctx, key, false); } } #if TURBINE_USE_IMGUI static void themeImgui() noexcept { // Dark Ruda style by Raikiri from ImThemes auto &style = ImGui::GetStyle(); style.Alpha = 1.0; style.DisabledAlpha = 0.6000000238418579; style.WindowPadding = ImVec2(8.0, 8.0); style.WindowRounding = 0.0; style.WindowBorderSize = 1.0; style.WindowMinSize = ImVec2(32.0, 32.0); style.WindowTitleAlign = ImVec2(0.0, 0.5); style.WindowMenuButtonPosition = ImGuiDir_Left; style.ChildRounding = 0.0; style.ChildBorderSize = 1.0; style.PopupRounding = 0.0; style.PopupBorderSize = 1.0; style.FramePadding = ImVec2(4.0, 3.0); // custom value style.FrameRounding = 3.0; style.FrameBorderSize = 0.0; style.ItemSpacing = ImVec2(8.0, 4.0); style.ItemInnerSpacing = ImVec2(4.0, 4.0); style.CellPadding = ImVec2(4.0, 2.0); style.IndentSpacing = 21.0; style.ColumnsMinSpacing = 6.0; style.ScrollbarSize = 14.0; style.ScrollbarRounding = 9.0; style.GrabMinSize = 10.0; style.GrabRounding = 4.0; style.TabRounding = 4.0; style.TabBorderSize = 0.0; style.TabMinWidthForCloseButton = 0.0; style.ColorButtonPosition = ImGuiDir_Right; style.ButtonTextAlign = ImVec2(0.5, 0.5); style.SelectableTextAlign = ImVec2(0.0, 0.0); // colors constexpr auto imVec4 = [](double r, double g, double b, double a) { return ImVec4( static_cast(r), static_cast(g), static_cast(b), static_cast(a)); }; auto colors = ox::Span(style.Colors); colors[ImGuiCol_Text] = imVec4(0.9490196108818054, 0.95686274766922, 0.9764705896377563, 1.0); colors[ImGuiCol_TextDisabled] = imVec4(0.3568627536296844, 0.4196078479290009, 0.4666666686534882, 1.0); colors[ImGuiCol_WindowBg] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0); colors[ImGuiCol_ChildBg] = imVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0); colors[ImGuiCol_PopupBg] = imVec4(0.0784313753247261, 0.0784313753247261, 0.0784313753247261, 0.9399999976158142); colors[ImGuiCol_Border] = imVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0); colors[ImGuiCol_BorderShadow] = imVec4(0.0, 0.0, 0.0, 0.0); colors[ImGuiCol_FrameBg] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0); colors[ImGuiCol_FrameBgHovered] = imVec4(0.1176470592617989, 0.2000000029802322, 0.2784313857555389, 1.0); colors[ImGuiCol_FrameBgActive] = imVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 1.0); colors[ImGuiCol_TitleBg] = imVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 0.6499999761581421); colors[ImGuiCol_TitleBgActive] = imVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0); colors[ImGuiCol_TitleBgCollapsed] = imVec4(0.0, 0.0, 0.0, 0.5099999904632568); colors[ImGuiCol_MenuBarBg] = imVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0); colors[ImGuiCol_ScrollbarBg] = imVec4(0.01960784383118153, 0.01960784383118153, 0.01960784383118153, 0.3899999856948853); colors[ImGuiCol_ScrollbarGrab] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0); colors[ImGuiCol_ScrollbarGrabHovered] = imVec4(0.1764705926179886, 0.2196078449487686, 0.2470588237047195, 1.0); colors[ImGuiCol_ScrollbarGrabActive] = imVec4(0.08627451211214066, 0.2078431397676468, 0.3098039329051971, 1.0); colors[ImGuiCol_CheckMark] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0); colors[ImGuiCol_SliderGrab] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0); colors[ImGuiCol_SliderGrabActive] = imVec4(0.3686274588108063, 0.6078431606292725, 1.0, 1.0); colors[ImGuiCol_Button] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0); colors[ImGuiCol_ButtonHovered] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0); colors[ImGuiCol_ButtonActive] = imVec4(0.05882352963089943, 0.529411792755127, 0.9764705896377563, 1.0); // custom value colors[ImGuiCol_Header] = imVec4(0.4000000029802322, 0.4470588237047195, 0.4862745225429535, 0.550000011920929); colors[ImGuiCol_HeaderHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929); colors[ImGuiCol_HeaderActive] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0); colors[ImGuiCol_Separator] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0); colors[ImGuiCol_SeparatorHovered] = imVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 0.7799999713897705); colors[ImGuiCol_SeparatorActive] = imVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 1.0); colors[ImGuiCol_ResizeGrip] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.25); colors[ImGuiCol_ResizeGripHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.6700000166893005); colors[ImGuiCol_ResizeGripActive] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.949999988079071); colors[ImGuiCol_Tab] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0); colors[ImGuiCol_TabHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929); colors[ImGuiCol_TabActive] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0); colors[ImGuiCol_TabUnfocused] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0); colors[ImGuiCol_TabUnfocusedActive] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0); colors[ImGuiCol_PlotLines] = imVec4(0.6078431606292725, 0.6078431606292725, 0.6078431606292725, 1.0); colors[ImGuiCol_PlotLinesHovered] = imVec4(1.0, 0.4274509847164154, 0.3490196168422699, 1.0); colors[ImGuiCol_PlotHistogram] = imVec4(0.8980392217636108, 0.6980392336845398, 0.0, 1.0); colors[ImGuiCol_PlotHistogramHovered] = imVec4(1.0, 0.6000000238418579, 0.0, 1.0); colors[ImGuiCol_TableHeaderBg] = imVec4(0.1882352977991104, 0.1882352977991104, 0.2000000029802322, 1.0); colors[ImGuiCol_TableBorderStrong] = imVec4(0.3098039329051971, 0.3098039329051971, 0.3490196168422699, 1.0); colors[ImGuiCol_TableBorderLight] = imVec4(0.2274509817361832, 0.2274509817361832, 0.2470588237047195, 1.0); colors[ImGuiCol_TableRowBg] = imVec4(0.0, 0.0, 0.0, 0.0); colors[ImGuiCol_TableRowBgAlt] = imVec4(1.0, 1.0, 1.0, 0.05999999865889549); colors[ImGuiCol_TextSelectedBg] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.3499999940395355); colors[ImGuiCol_DragDropTarget] = imVec4(1.0, 1.0, 0.0, 0.8999999761581421); colors[ImGuiCol_NavHighlight] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0); colors[ImGuiCol_NavWindowingHighlight] = imVec4(1.0, 1.0, 1.0, 0.699999988079071); colors[ImGuiCol_NavWindowingDimBg] = imVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.2000000029802322); colors[ImGuiCol_ModalWindowDimBg] = imVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.3499999940395355); } #endif ox::Error initGfx(Context &ctx) noexcept { glfwSetErrorCallback(handleGlfwError); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); constexpr auto Scale = 5; ctx.window = glfwCreateWindow(240 * Scale, 160 * Scale, ctx.keelCtx.appName.c_str(), nullptr, nullptr); //ctx.window = glfwCreateWindow(876, 743, ctx.keelCtx.appName.c_str(), nullptr, nullptr); if (ctx.window == nullptr) { return ox::Error(1, "Could not open GLFW window"); } glfwSetCursorPosCallback(ctx.window, handleGlfwCursorPosEvent); glfwSetMouseButtonCallback(ctx.window, handleGlfwMouseButtonEvent); glfwSetKeyCallback(ctx.window, handleGlfwKeyEvent); glfwSetWindowUserPointer(ctx.window, &ctx); glfwMakeContextCurrent(ctx.window); if (!gladLoadGLES2Loader(reinterpret_cast(glfwGetProcAddress))) { return ox::Error(2, "Could not init Glad"); } #if TURBINE_USE_IMGUI IMGUI_CHECKVERSION(); ImGui::CreateContext(); auto &io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; //io.MouseDrawCursor = true; ImGui_ImplGlfw_InitForOpenGL(ctx.window, true); ImGui_ImplOpenGL3_Init(); io.IniFilename = nullptr; themeImgui(); #endif return {}; } struct IconData { std::vector pixels; int w{}, h{}; }; [[nodiscard]] static ox::Result toGlfwImgPixels(ox::SpanView const &iconPng) noexcept { ox::Result out; unsigned w{}, h{}; if (lodepng::decode(out.value.pixels, w, h, iconPng.data(), iconPng.size())) { return ox::Error{1, "unable to decode window icon PNG data"}; } out.value.w = static_cast(w); out.value.h = static_cast(h); return out; } ox::Error setWindowIcon(Context &ctx, ox::SpanView> const &iconPngs) noexcept { ox::Vector src; ox::Vector imgs; for (auto const &iconPng : iconPngs) { OX_RETURN_ERROR(toGlfwImgPixels(iconPng).moveTo(src.emplace_back())); auto &icon = *src.back().unwrap(); imgs.emplace_back(GLFWimage{ .width = icon.w, .height = icon.h, .pixels = icon.pixels.data(), }); } glfwSetWindowIcon(ctx.window, static_cast(imgs.size()), imgs.data()); return {}; } void setWindowTitle(Context &ctx, ox::StringViewCR title) noexcept { auto cstr = ox_malloca(title.bytes() + 1, char); ox::strncpy(cstr.get(), title.data(), title.bytes()); glfwSetWindowTitle(ctx.window, cstr.get()); } void focusWindow(Context &ctx) noexcept { glfwFocusWindow(ctx.window); } int getScreenWidth(Context const &ctx) noexcept { int w = 0, h = 0; glfwGetFramebufferSize(ctx.window, &w, &h); return w; } int getScreenHeight(Context const &ctx) noexcept { int w = 0, h = 0; glfwGetFramebufferSize(ctx.window, &w, &h); return h; } ox::Size getScreenSize(Context const &ctx) noexcept { int w = 0, h = 0; glfwGetFramebufferSize(ctx.window, &w, &h); return {w, h}; } ox::Bounds getWindowBounds(Context const &ctx) noexcept { ox::Bounds bnds; glfwGetWindowPos(ctx.window, &bnds.x, &bnds.y); glfwGetWindowSize(ctx.window, &bnds.width, &bnds.height); return bnds; } ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept { glfwSetWindowPos(ctx.window, bnds.x, bnds.y); glfwSetWindowSize(ctx.window, bnds.width, bnds.height); return {}; } void setRefreshWithin(Context &ctx, int ms) noexcept { ctx.refreshWithinMs = ox::min(ms, ctx.refreshWithinMs); } }