From ac9bbefa827401f59eb7707cdfef5c4049d84a01 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Fri, 22 May 2026 21:25:56 -0500 Subject: [PATCH] [studio,nostalgia] Add HiDPI support --- .../commands/addcolorcommand.hpp | 2 +- .../paletteeditor/commands/addpagecommand.hpp | 2 +- .../commands/applycolorallpagescommand.hpp | 2 +- .../commands/duplicatepagecommand.hpp | 2 +- .../commands/movecolorcommand.hpp | 2 +- .../commands/movepagecommand.hpp | 2 +- .../commands/removecolorcommand.hpp | 2 +- .../commands/removepagecommand.hpp | 2 +- .../commands/renamepagecommand.hpp | 2 +- .../commands/updatecolorcommand.hpp | 2 +- .../commands/updatecolorinfocommand.hpp | 2 +- .../paletteeditor/paletteeditor-imgui.cpp | 27 ++++--- .../tilesheeteditor/tilesheeteditor-imgui.cpp | 56 +++++++------- src/olympic/studio/applib/src/app.cpp | 3 +- .../studio/applib/src/popups/about.cpp | 2 +- .../studio/applib/src/popups/fileinfo.cpp | 3 +- .../studio/applib/src/popups/makecopy.cpp | 6 +- src/olympic/studio/applib/src/studioui.cpp | 14 +++- .../modlib/include/studio/imguiutil.hpp | 27 ++++--- .../studio/modlib/src/filepickerpopup.cpp | 5 +- src/olympic/studio/modlib/src/imguiutil.cpp | 74 +++++++++++++------ src/olympic/studio/modlib/src/popup.cpp | 2 +- .../turbine/include/turbine/turbine.hpp | 6 ++ src/olympic/turbine/src/glfw/context.hpp | 1 + src/olympic/turbine/src/glfw/turbine.cpp | 15 ++++ 25 files changed, 172 insertions(+), 91 deletions(-) diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addcolorcommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addcolorcommand.hpp index 9fad4bb5..f2046ee6 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addcolorcommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addcolorcommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addpagecommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addpagecommand.hpp index 01f659dd..4b5fd7c4 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addpagecommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/addpagecommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/applycolorallpagescommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/applycolorallpagescommand.hpp index 8b5eec23..be00d26b 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/applycolorallpagescommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/applycolorallpagescommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/duplicatepagecommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/duplicatepagecommand.hpp index 7765e275..35239dfb 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/duplicatepagecommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/duplicatepagecommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp index d1d8f21c..57d503ab 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movepagecommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movepagecommand.hpp index 0b42161d..ed58df86 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movepagecommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movepagecommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removecolorcommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removecolorcommand.hpp index d94e021c..48737e0c 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removecolorcommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removecolorcommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removepagecommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removepagecommand.hpp index bd4264b3..9e8b6d85 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removepagecommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/removepagecommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/renamepagecommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/renamepagecommand.hpp index fbc520e5..4c33cee5 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/renamepagecommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/renamepagecommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorcommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorcommand.hpp index 6b7b63cc..d4e07310 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorcommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorcommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorinfocommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorinfocommand.hpp index c5dac8c6..3365152d 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorinfocommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/updatecolorinfocommand.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp index d1138143..8f26b07c 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp @@ -78,15 +78,16 @@ PaletteEditorImGui::PaletteEditorImGui(studio::Context &sctx, ox::StringParam pa } void PaletteEditorImGui::draw(studio::Context&) noexcept { + auto const scale = ig::dpiScale(); auto const paneSize = ImGui::GetContentRegionAvail(); { - ImGui::BeginChild("Pages", {280, paneSize.y}, true); + ImGui::BeginChild("Pages", {scale * 280, paneSize.y}, true); drawPagesEditor(); ImGui::EndChild(); } ImGui::SameLine(); { - ImGui::BeginChild("Colors", {-1, paneSize.y}, true); + ImGui::BeginChild("Colors", {scale * -1, paneSize.y}, true); drawColorsEditor(); ImGui::EndChild(); } @@ -165,7 +166,7 @@ void PaletteEditorImGui::drawColorsEditor() noexcept { static constexpr auto toolbarHeight = 40; { auto constexpr sz = ImVec2{70, 24}; - if (ImGui::Button("Add", sz)) { + if (ig::PushButton("Add", sz)) { auto const colorSz = colorCnt(m_pal, m_page); constexpr Color16 c = 0; std::ignore = pushCommand(m_pal, c, colorSz); @@ -173,7 +174,7 @@ void PaletteEditorImGui::drawColorsEditor() noexcept { ImGui::SameLine(); ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page)); { - if (ImGui::Button("Remove", sz)) { + if (ig::PushButton("Remove", sz)) { std::ignore = pushCommand(m_pal, m_selectedColorRow); m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow); colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page); @@ -183,17 +184,18 @@ void PaletteEditorImGui::drawColorsEditor() noexcept { } auto const tblWidth = (colorsSz.x - static_cast(colorEditorWidth) - 8.f) * static_cast(colorEditor); + auto const scale = ig::dpiScale(); ImGui::BeginTable( "Colors", 6, tableFlags, - {tblWidth, colorsSz.y - (toolbarHeight + 5)}); + {tblWidth * scale, colorsSz.y - (toolbarHeight + 5) * scale}); { - ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25); - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100); - ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40); - ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40); - ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40); + ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25 * scale); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100 * scale); + ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40 * scale); + ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40 * scale); + ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40 * scale); ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide); ImGui::TableHeadersRow(); if (m_page < m_pal.pages.size()) { @@ -236,10 +238,11 @@ void PaletteEditorImGui::drawColorsEditor() noexcept { } void PaletteEditorImGui::drawPagesEditor() noexcept { + auto const scale = ig::dpiScale(); constexpr auto tableFlags = ImGuiTableFlags_RowBg; auto const paneSz = ImGui::GetContentRegionAvail(); constexpr auto toolbarHeight = 40; - auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f, 24}; + auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f * scale, 24 * scale}; if (ImGui::Button("Add", btnSz)) { if (m_pal.pages.empty()) { std::ignore = pushCommand(m_pal); @@ -265,7 +268,7 @@ void PaletteEditorImGui::drawPagesEditor() noexcept { "PageSelect", 2, tableFlags, - {paneSz.x, paneSz.y - (toolbarHeight + 5)}); + {paneSz.x, paneSz.y - (toolbarHeight + 5) * scale}); { ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 200); diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index 40c4a110..1a7aec98 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -23,7 +23,7 @@ struct SubSheetRef { TileSheet::SubSheetIdx subsheet{}; }; -OX_MODEL_BEGIN(SubSheetRef) +static OX_MODEL_BEGIN(SubSheetRef) OX_MODEL_FIELD(subsheet) OX_MODEL_END() @@ -33,7 +33,7 @@ struct TileSheetEditorConfig { TileSheet::SubSheetIdx activeSubsheet{}; }; -OX_MODEL_BEGIN(TileSheetEditorConfig) +static OX_MODEL_BEGIN(TileSheetEditorConfig) OX_MODEL_FIELD_RENAME(activeSubsheet, active_subsheet) OX_MODEL_END() @@ -149,27 +149,30 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept { if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) { if (ImGui::IsKeyPressed(ImGuiKey_A)) { auto const &img = m_model.activeSubSheet(); - m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}}); + m_model.setSelection({ + {}, + {img.columns * TileWidth - 1, img.rows * TileHeight - 1}}); } else if (ImGui::IsKeyPressed(ImGuiKey_G)) { m_model.clearSelection(); } } } + auto const scale = ig::dpiScale(); auto const paneSize = ImGui::GetContentRegionAvail(); - auto const tileSheetParentSize = ImVec2{paneSize.x - s_palViewWidth, paneSize.y}; - auto const fbSize = ox::Vec2{tileSheetParentSize.x - 16, tileSheetParentSize.y - 16}; + auto const tileSheetParentSize = ImVec2{paneSize.x - s_palViewWidth * scale, paneSize.y}; + auto const fbSize = ox::Vec2{tileSheetParentSize.x - 16 * scale, tileSheetParentSize.y - 16 * scale}; ImGui::BeginChild("TileSheetView", tileSheetParentSize, true); { drawTileSheet(fbSize); } ImGui::EndChild(); ImGui::SameLine(); - ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true); + ImGui::BeginChild("Controls", {(s_palViewWidth - 8) * scale, paneSize.y}, true); { auto const controlsSize = ImGui::GetContentRegionAvail(); - ImGui::BeginChild("ToolBox", {0, 32}, true); + ImGui::BeginChild("ToolBox", {0, 32 * scale}, true); { - auto const btnSz = ImVec2{45, 14}; + auto const btnSz = ImVec2{45 * scale, 14 * scale}; if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) { m_tool = TileSheetTool::Select; } @@ -193,7 +196,7 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept { //ig::ComboBox("##Operations", ox::Array{"Operations"}, i); } ImGui::EndChild(); - ImGui::BeginChild("OperationsBox", {0, 35}, ImGuiWindowFlags_NoTitleBar); + ImGui::BeginChild("OperationsBox", {0, 35 * scale}, ImGuiWindowFlags_NoTitleBar); { if (ImGui::BeginCombo("##Operations", "Operations", 0)) { if (ImGui::Selectable("Flip X", false)) { @@ -214,17 +217,17 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept { } } ImGui::EndChild(); - auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21); + auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21) * scale; // draw palette/color picker - ImGui::BeginChild("Palette", {s_palViewWidth - 24, ySize / 2.f}, true); + ImGui::BeginChild("Palette", {(s_palViewWidth - 24) * scale, ySize / 2.f}, true); { drawPaletteMenu(); } ImGui::EndChild(); - ImGui::BeginChild("SubSheets", {s_palViewWidth - 24, ySize / 2.f}, true); + ImGui::BeginChild("SubSheets", {(s_palViewWidth - 24) * scale, ySize / 2.f}, true); { - static constexpr auto btnHeight = ig::BtnSz.y; - auto constexpr btnSize = ImVec2{btnHeight, btnHeight}; + auto constexpr btnHeight = ig::BtnSz.y; + auto const btnSize = ImVec2{btnHeight, btnHeight}; if (ig::PushButton("+", btnSize)) { auto insertOnIdx = m_model.activeSubSheetIdx(); auto const &parent = m_model.activeSubSheet(); @@ -247,16 +250,16 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept { if (ig::PushButton("Export")) { m_exportMenu.show(); } - TileSheet::SubSheetIdx path; static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY; if (ImGui::BeginTable("Subsheets", 4, flags)) { + TileSheet::SubSheetIdx path; ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide); - ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 25); - ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50); - ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50); + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 25 * scale); + ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50 * scale); + ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50 * scale); ImGui::TableHeadersRow(); drawSubsheetSelector(m_view.img().subsheet, path); ImGui::EndTable(); @@ -445,10 +448,11 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const &fbSize) noexcept { void TileSheetEditorImGui::drawPaletteMenu() noexcept { ig::IDStackItem const idStackItem{"PaletteMenu"}; - auto constexpr comboWidthSub = 62; + auto const scale = ig::dpiScale(); + auto const comboWidthSub = 62 * scale; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub); - auto constexpr palTags = ImGuiInputTextFlags_ReadOnly; - if (ig::InputTextWithHint("##Palette", "Path to Palette", m_model.palPath(), palTags)) { + auto constexpr palFlags = ImGuiInputTextFlags_ReadOnly; + if (ig::InputTextWithHint("##Palette", "Path to Palette", m_model.palPath(), palFlags)) { oxLogError(m_model.setPalette(m_model.palPath())); } m_palPathFocused = ImGui::IsItemFocused(); @@ -571,7 +575,7 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept ImGui::InputInt("Columns", &m_cols); ImGui::InputInt("Rows", &m_rows); } - if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) { + if (ig::PopupControlsOkCancel(m_show) == ig::PopupResponse::OK) { inputSubmitted.emit(m_name, m_cols, m_rows); } ImGui::EndPopup(); @@ -593,13 +597,13 @@ void TileSheetEditorImGui::ExportMenu::draw(turbine::Context &tctx) noexcept { if (!m_show) { return; } - constexpr auto popupWidth = 235.f; - constexpr auto popupHeight = 85.f; - constexpr auto popupSz = ImVec2{popupWidth, popupHeight}; + auto constexpr popupWidth = 235.f; + auto constexpr popupHeight = 85.f; + auto constexpr popupSz = ImVec2{popupWidth, popupHeight}; if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) { ImGui::InputInt("Scale", &m_scale); m_scale = ox::clamp(m_scale, 1, 135); - if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) { + if (ig::PopupControlsOkCancel(m_show) == ig::PopupResponse::OK) { inputSubmitted.emit(m_scale); } ImGui::EndPopup(); diff --git a/src/olympic/studio/applib/src/app.cpp b/src/olympic/studio/applib/src/app.cpp index badf1554..1f775753 100644 --- a/src/olympic/studio/applib/src/app.cpp +++ b/src/olympic/studio/applib/src/app.cpp @@ -17,6 +17,7 @@ #include "subcommands/change-format/change-format.hpp" #include "configfile.hpp" +#include "font.hpp" #include "studioui.hpp" namespace studio { @@ -129,7 +130,7 @@ static ox::Error run( if (m->id() == moduleId) { for (auto const &c : m->commands()) { if (c.name == subCmd) { - auto kctx = keel::init( + auto const kctx = keel::init( ox::make_unique(projectDir), c.name); if (kctx.error) { diff --git a/src/olympic/studio/applib/src/popups/about.cpp b/src/olympic/studio/applib/src/popups/about.cpp index a1eb89ac..48db9f03 100644 --- a/src/olympic/studio/applib/src/popups/about.cpp +++ b/src/olympic/studio/applib/src/popups/about.cpp @@ -42,7 +42,7 @@ void AboutPopup::draw(Context &sctx) noexcept { if (ImGui::BeginPopupModal("About", &open, modalFlags)) { ImGui::Text("%s\n\nBuild date: %s", m_text.c_str(), __DATE__); ImGui::NewLine(); - ImGui::Dummy({148.0f, 0.0f}); + ig::Dummy({148.0f, 0.0f}); ImGui::SameLine(); if (ig::PushButton("Close")) { ImGui::CloseCurrentPopup(); diff --git a/src/olympic/studio/applib/src/popups/fileinfo.cpp b/src/olympic/studio/applib/src/popups/fileinfo.cpp index 1f13f88c..dbfc9400 100644 --- a/src/olympic/studio/applib/src/popups/fileinfo.cpp +++ b/src/olympic/studio/applib/src/popups/fileinfo.cpp @@ -79,13 +79,14 @@ void FileInfo::draw(Context &sctx) noexcept { } void FileInfo::drawTable() const noexcept { + auto const scale = ig::dpiScale(); ig::IDStackItem const idStackItem{"FileInfo"}; ImGui::Text("%s", m_filePath.c_str()); if (m_fileInfo && ImGui::BeginTable( "Table", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, 70); + ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, 70 * scale); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide); // asset id ImGui::TableNextRow(); diff --git a/src/olympic/studio/applib/src/popups/makecopy.cpp b/src/olympic/studio/applib/src/popups/makecopy.cpp index fa998171..3c82b248 100644 --- a/src/olympic/studio/applib/src/popups/makecopy.cpp +++ b/src/olympic/studio/applib/src/popups/makecopy.cpp @@ -37,9 +37,10 @@ void MakeCopyPopup::draw(Context &ctx) noexcept { m_stage = Stage::Open; m_open = true; [[fallthrough]]; - case Stage::Open: + case Stage::Open: { + auto const scale = ig::dpiScale(); ig::centerNextWindow(ctx.tctx); - ImGui::SetNextWindowSize({250, 0}); + ImGui::SetNextWindowSize({250 * scale, 0}); constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | @@ -67,6 +68,7 @@ void MakeCopyPopup::draw(Context &ctx) noexcept { ImGui::EndPopup(); } break; + } } } diff --git a/src/olympic/studio/applib/src/studioui.cpp b/src/olympic/studio/applib/src/studioui.cpp index 3f09cbe0..53e96564 100644 --- a/src/olympic/studio/applib/src/studioui.cpp +++ b/src/olympic/studio/applib/src/studioui.cpp @@ -122,6 +122,7 @@ StudioUI::StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexc m_sctx{*this, tctx}, m_projectDataDir{std::move(projectDataDir)} { { + auto const scale = turbine::isWayland() ? 1.f : turbine::scale(m_tctx); ImFontConfig fontCfg; fontCfg.FontDataOwnedByAtlas = false; auto const &io = ImGui::GetIO(); @@ -132,7 +133,11 @@ StudioUI::StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexc // that it will still be treated as const. // ImGui documentation recognizes that this is a bad design, // and hopefully it will change at some point. - io.Fonts->AddFontFromMemoryTTF(const_cast(font.data()), static_cast(font.size()), 13, &fontCfg); + io.Fonts->AddFontFromMemoryTTF( + const_cast(font.data()), + static_cast(font.size()), + 13 * scale, + &fontCfg); } auto &kctx = keelCtx(m_tctx); kctx.converters.emplace_back(keel::Converter::make()); @@ -197,6 +202,11 @@ void StudioUI::handleNavigationChange(ox::StringParam path, ox::StringParam navA } void StudioUI::draw() noexcept { + if (!turbine::isWayland()) { + ig::setDpiScale(ImGui::GetWindowDpiScale()); + } else { + ig::setDpiScale(1.f); + } glutils::clearScreen(); drawMenu(); auto const &viewport = *ImGui::GetMainViewport(); @@ -215,7 +225,7 @@ void StudioUI::draw() noexcept { ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy); if (m_showProjectExplorer) { auto const v = ImGui::GetContentRegionAvail(); - m_projectExplorer.draw(m_sctx, {300, v.y}); + m_projectExplorer.draw(m_sctx, {300 * ig::dpiScale(), v.y}); ImGui::SameLine(); } drawTabBar(); diff --git a/src/olympic/studio/modlib/include/studio/imguiutil.hpp b/src/olympic/studio/modlib/include/studio/imguiutil.hpp index 262e7998..23cd7a6c 100644 --- a/src/olympic/studio/modlib/include/studio/imguiutil.hpp +++ b/src/olympic/studio/modlib/include/studio/imguiutil.hpp @@ -27,6 +27,10 @@ namespace studio::ig { inline constexpr auto BtnSz = ImVec2{52, 22}; +void setDpiScale(float scale) noexcept; + +[[nodiscard]] +float dpiScale() noexcept; constexpr ImTextureID toImTextureID(ox::Unsigned_c auto id) noexcept requires(sizeof(id) <= sizeof(ox::Uint)) { @@ -133,7 +137,7 @@ auto dragDropTarget(auto const &cb) noexcept { class ChildStackItem { public: - explicit ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz = {}) noexcept; + explicit ChildStackItem(ox::CStringViewCR id, ImVec2 sz = {}) noexcept; ~ChildStackItem() noexcept; }; @@ -155,6 +159,11 @@ class IndentStackItem { void centerNextWindow(turbine::Context &ctx) noexcept; +inline void Dummy(ImVec2 const &sz) noexcept { + auto const scale = dpiScale(); + ImGui::Dummy({sz.x * scale, sz.y * scale}); +} + bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz = BtnSz) noexcept; template @@ -173,10 +182,10 @@ TextInput> InputText( ox::StringViewCR currentText, ImGuiInputTextFlags const flags = 0, ImGuiInputTextCallback const callback = nullptr, - void *user_data = nullptr) noexcept { + void *const userData = nullptr) noexcept { TextInput> out = {.text = currentText}; out.changed = ImGui::InputText( - label.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data); + label.c_str(), out.text.data(), MaxChars + 1, flags, callback, userData); if (out.changed) { OX_ALLOW_UNSAFE_BUFFERS_BEGIN std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str())); @@ -192,10 +201,10 @@ TextInput> InputTextWithHint( ox::StringViewCR currentText, ImGuiInputTextFlags const flags = 0, ImGuiInputTextCallback const callback = nullptr, - void *user_data = nullptr) noexcept { + void *userData = nullptr) noexcept { TextInput> out = {.text = currentText}; out.changed = ImGui::InputTextWithHint( - label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data); + label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, userData); if (out.changed) { OX_ALLOW_UNSAFE_BUFFERS_BEGIN std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str())); @@ -210,9 +219,9 @@ bool InputText( ox::IString &text, ImGuiInputTextFlags const flags = 0, ImGuiInputTextCallback const callback = nullptr, - void *user_data = nullptr) noexcept { + void *userData = nullptr) noexcept { auto const out = ImGui::InputText( - label.c_str(), text.data(), StrCap + 1, flags, callback, user_data); + label.c_str(), text.data(), StrCap + 1, flags, callback, userData); if (out) { OX_ALLOW_UNSAFE_BUFFERS_BEGIN std::ignore = text.unsafeResize(ox::strlen(text.c_str())); @@ -243,7 +252,7 @@ PopupResponse PopupControlsOk( ox::CStringViewCR ok); [[nodiscard]] -bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz = {285, 0}); +bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 sz = {285, 0}); /** * @@ -290,7 +299,7 @@ bool ListBox( std::function const &f, size_t strCnt, size_t &selIdx, - ImVec2 const &sz = {0, 0}) noexcept; + ImVec2 sz = {0, 0}) noexcept; /** * diff --git a/src/olympic/studio/modlib/src/filepickerpopup.cpp b/src/olympic/studio/modlib/src/filepickerpopup.cpp index c965c0e2..b11ffd56 100644 --- a/src/olympic/studio/modlib/src/filepickerpopup.cpp +++ b/src/olympic/studio/modlib/src/filepickerpopup.cpp @@ -2,6 +2,8 @@ * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. */ +#include + #include #include @@ -83,9 +85,10 @@ ox::Optional FilePickerPopup::draw(Context &ctx) noexcept { if (!m_open) { return out; } + auto const scale = turbine::scale(ctx.tctx); if (ig::BeginPopup(ctx.tctx, m_name, m_open, {380, 340})) { auto const vp = ImGui::GetContentRegionAvail(); - m_explorer.draw(ctx, {vp.x, vp.y - 30}); + m_explorer.draw(ctx, {vp.x, vp.y - 30 * scale}); if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK || m_explorer.opened) { out = handlePick(); } diff --git a/src/olympic/studio/modlib/src/imguiutil.cpp b/src/olympic/studio/modlib/src/imguiutil.cpp index bbff7622..72525ad5 100644 --- a/src/olympic/studio/modlib/src/imguiutil.cpp +++ b/src/olympic/studio/modlib/src/imguiutil.cpp @@ -10,7 +10,25 @@ namespace studio::ig { -ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz) noexcept { +static constexpr void scaleSz(ImVec2 &sz) noexcept { + auto const scale = dpiScale(); + sz.x *= scale; + sz.y *= scale; +} + +thread_local float g_dpiScale = 1.f; + +void setDpiScale(float scale) noexcept { + g_dpiScale = scale; +} + +[[nodiscard]] +float dpiScale() noexcept { + return g_dpiScale; +} + +ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 sz) noexcept { + scaleSz(sz); ImGui::BeginChild(id.c_str(), sz); } @@ -33,7 +51,7 @@ IDStackItem::~IDStackItem() noexcept { } -IndentStackItem::IndentStackItem(float indent) noexcept: m_indent(indent) { +IndentStackItem::IndentStackItem(float id) noexcept: m_indent(id) { ImGui::Indent(m_indent); } @@ -42,16 +60,18 @@ IndentStackItem::~IndentStackItem() noexcept { } -void centerNextWindow(turbine::Context &ctx) noexcept { - auto const sz = turbine::getScreenSize(ctx); - auto const screenW = static_cast(sz.width); - auto const screenH = static_cast(sz.height); - auto const mod = ImGui::GetWindowDpiScale() * 2; - ImGui::SetNextWindowPos(ImVec2(screenW / mod, screenH / mod), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); +void centerNextWindow(turbine::Context&) noexcept { + auto const &io = ImGui::GetIO(); + auto const sz = io.DisplaySize; + ImGui::SetNextWindowPos( + {sz.x * 0.5f, sz.y * 0.5f}, + ImGuiCond_Always, + {0.5f, 0.5f}); } bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz) noexcept { - return ImGui::Button(lbl.c_str(), btnSz); + auto const scale = dpiScale(); + return ImGui::Button(lbl.c_str(), {btnSz.x * scale, btnSz.y * scale}); } PopupResponse PopupControlsOkCancel( @@ -60,9 +80,10 @@ PopupResponse PopupControlsOkCancel( ox::CStringViewCR ok, ox::CStringViewCR cancel) { auto out = PopupResponse::None; - constexpr auto btnSz = ImVec2{50, BtnSz.y}; + auto const scale = dpiScale(); + auto const btnSz = ImVec2{50 * scale, BtnSz.y * scale}; ImGui::Separator(); - ImGui::SetCursorPosX(popupWidth - 118); + ImGui::SetCursorPosX(popupWidth - 118 * scale); if (ImGui::Button(ok.c_str(), btnSz)) { popupOpen = false; out = PopupResponse::OK; @@ -79,16 +100,18 @@ PopupResponse PopupControlsOkCancel( bool &popupOpen, ox::CStringViewCR ok, ox::CStringViewCR cancel) { - return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17, popupOpen, ok, cancel); + auto const scale = dpiScale(); + return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17 * scale, popupOpen, ok, cancel); } PopupResponse PopupControlsOk( bool &popupOpen, ox::CStringViewCR ok) { + auto const scale = dpiScale(); auto out = PopupResponse::None; - constexpr auto btnSz = ImVec2{50, BtnSz.y}; + auto const btnSz = ImVec2{50 * scale, BtnSz.y * scale}; ImGui::Separator(); - ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 42); + ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 42 * scale); if (ImGui::Button(ok.c_str(), btnSz)) { popupOpen = false; out = PopupResponse::OK; @@ -101,7 +124,8 @@ PopupResponse PopupControlsOk( return out; } -bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz) { +bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 sz) { + scaleSz(sz); constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; centerNextWindow(ctx); ImGui::OpenPopup(popupName.c_str()); @@ -110,14 +134,14 @@ bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, } bool ComboBox( - ox::CStringView const &lbl, + ox::CStringViewCR lbl, ox::SpanView const list, size_t &selectedIdx) noexcept { bool out{}; auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : ""; if (ImGui::BeginCombo(lbl.c_str(), first, 0)) { for (auto i = 0u; i < list.size(); ++i) { - auto const selected = (selectedIdx == i); + auto const selected = selectedIdx == i; if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) { selectedIdx = i; out = true; @@ -129,14 +153,14 @@ bool ComboBox( } bool ComboBox( - ox::CStringView const &lbl, + ox::CStringViewCR lbl, ox::Span const list, size_t &selectedIdx) noexcept { bool out{}; auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : ""; if (ImGui::BeginCombo(lbl.c_str(), first, 0)) { for (auto i = 0u; i < list.size(); ++i) { - auto const selected = (selectedIdx == i); + auto const selected = selectedIdx == i; if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) { selectedIdx = i; out = true; @@ -181,12 +205,14 @@ bool ListBox( std::function const &f, size_t const strCnt, size_t &selIdx, - ImVec2 const &sz) noexcept { + ImVec2 sz) noexcept { + auto const scale = dpiScale(); + sz = {sz.x * scale, sz.y * scale}; auto out = false; if (ImGui::BeginListBox(name.c_str(), sz)) { for (size_t i = 0; i < strCnt; ++i) { - auto str = f(i); - ig::IDStackItem const idStackItem2(static_cast(i)); + auto const str = f(i); + IDStackItem const idStackItem2(static_cast(i)); if (ImGui::Selectable(str.c_str(), selIdx == i)) { if (i != selIdx) { selIdx = i; @@ -200,13 +226,13 @@ bool ListBox( } bool ListBox(ox::CStringViewCR name, ox::SpanView const &list, size_t &selIdx) noexcept { - return ListBox(name, [list](size_t i) -> ox::CStringView { + return ListBox(name, [list](size_t const i) -> ox::CStringView { return list[i]; }, list.size(), selIdx); } bool ListBox(ox::CStringViewCR name, ox::SpanView const &list, size_t &selIdx) noexcept { - return ListBox(name, [list](size_t i) -> ox::CStringView { + return ListBox(name, [list](size_t const i) -> ox::CStringView { return list[i]; }, list.size(), selIdx); } diff --git a/src/olympic/studio/modlib/src/popup.cpp b/src/olympic/studio/modlib/src/popup.cpp index 8868a560..efa99b67 100644 --- a/src/olympic/studio/modlib/src/popup.cpp +++ b/src/olympic/studio/modlib/src/popup.cpp @@ -9,7 +9,7 @@ namespace studio { void Popup::drawWindow(turbine::Context &ctx, bool &open, std::function const &drawContents) { ig::centerNextWindow(ctx); - ImGui::SetNextWindowSize(static_cast(m_size)); + ImGui::SetNextWindowSize(static_cast(m_size * ig::dpiScale())); constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; if (ImGui::BeginPopupModal(m_title.c_str(), &open, modalFlags)) { drawContents(); diff --git a/src/olympic/turbine/include/turbine/turbine.hpp b/src/olympic/turbine/include/turbine/turbine.hpp index ed98b85f..17c3cfe1 100644 --- a/src/olympic/turbine/include/turbine/turbine.hpp +++ b/src/olympic/turbine/include/turbine/turbine.hpp @@ -112,4 +112,10 @@ void setShutdownHandler(Context &ctx, ShutdownHandler handler) noexcept; // sleep time is a minimum of ~16 milliseconds. void setUpdateHandler(Context &ctx, UpdateHandler) noexcept; +[[nodiscard]] +float scale(Context const &ctx) noexcept; + +[[nodiscard]] +bool isWayland() noexcept; + } diff --git a/src/olympic/turbine/src/glfw/context.hpp b/src/olympic/turbine/src/glfw/context.hpp index 44909468..943ac454 100644 --- a/src/olympic/turbine/src/glfw/context.hpp +++ b/src/olympic/turbine/src/glfw/context.hpp @@ -32,6 +32,7 @@ class Context { uint64_t draws = 0; bool running{}; ShutdownHandler shutdownHandler{}; + float scale{}; Context() noexcept = default; diff --git a/src/olympic/turbine/src/glfw/turbine.cpp b/src/olympic/turbine/src/glfw/turbine.cpp index cc4b1bad..6284bbc3 100644 --- a/src/olympic/turbine/src/glfw/turbine.cpp +++ b/src/olympic/turbine/src/glfw/turbine.cpp @@ -339,6 +339,7 @@ ox::Result> init( setMandatoryRefreshPeriod(*ctx, ticksMs(*ctx) + config::MandatoryRefreshPeriod); // init GLFW context glfwInit(); + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); glfwSetErrorCallback(handleGlfwError); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); @@ -373,6 +374,12 @@ ox::Result> init( ImGui_ImplOpenGL3_Init(); io.IniFilename = nullptr; themeImgui(); + float xscale{}, yscale{}; + glfwGetWindowContentScale(ctx->window, &xscale, &yscale); + ctx->scale = isWayland() ? 1.f : ox::max(xscale, yscale); + io.DisplayFramebufferScale = ImVec2{ctx->scale, ctx->scale}; + auto &style = ImGui::GetStyle(); + style.ScaleAllSizes(ctx->scale); #endif return ctx; } @@ -471,4 +478,12 @@ KeyEventHandler keyEventHandler(Context const &ctx) noexcept { return ctx.keyEventHandler; } +float scale(Context const &ctx) noexcept { + return ctx.scale; +} + +bool isWayland() noexcept { + return glfwGetPlatform() == GLFW_PLATFORM_WAYLAND; +} + }