diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index 1e60de12d5..feb6f10352 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -2,8 +2,10 @@ #include "InverseSquareLighting.h" #include "LinearLighting.h" +#include "Menu/ThemeManager.h" #include "Shadercache.h" #include "State.h" +#include "Util.h" static constexpr uint CLUSTER_MAX_LIGHTS = 128; static constexpr uint MAX_LIGHTS = 1024; @@ -53,7 +55,8 @@ void LightLimitFix::DrawOverlay() { if (!settings.EnableLightsVisualisation) return; - ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Always); + const float pos = ThemeManager::Constants::OVERLAY_WINDOW_POSITION * Util::GetUIScale(); + ImGui::SetNextWindowPos(ImVec2(pos, pos), ImGuiCond_Always); ImGui::Begin("##LLFDebug", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings); ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "DEBUG FEATURE - LIGHT LIMIT VISUALISATION ENABLED"); ImGui::End(); diff --git a/src/Features/PerformanceOverlay.cpp b/src/Features/PerformanceOverlay.cpp index 93f5daba55..a4f27fffae 100644 --- a/src/Features/PerformanceOverlay.cpp +++ b/src/Features/PerformanceOverlay.cpp @@ -310,12 +310,15 @@ void PerformanceOverlay::DrawOverlay() ImGui::GetStyleColorVec4(ImGuiCol_WindowBg).z, this->settings.BackgroundOpacity)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, this->settings.ShowBorder ? 1.0f : 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, this->settings.ShowBorder ? ImGui::GetStyle().WindowBorderSize : 0.0f); + + const float scale = Util::GetUIScale(); // Set initial position if not already set if (!this->settings.PositionSet) { - ImGui::SetNextWindowPos(ImVec2(PerformanceOverlay::Settings::kDefaultWindowPadding, PerformanceOverlay::Settings::kDefaultWindowPadding)); - this->settings.Position = ImVec2(PerformanceOverlay::Settings::kDefaultWindowPadding, PerformanceOverlay::Settings::kDefaultWindowPadding); + const float defaultPad = Settings::kDefaultWindowPadding * scale; + ImGui::SetNextWindowPos(ImVec2(defaultPad, defaultPad)); + this->settings.Position = ImVec2(defaultPad, defaultPad); this->settings.PositionSet = true; } else { ImGui::SetNextWindowPos(this->settings.Position, ImGuiCond_FirstUseEver); @@ -336,19 +339,16 @@ void PerformanceOverlay::DrawOverlay() fpsText = std::format("Raw FPS: {:.1f} ({:.2f} ms)", this->state.smoothFps, this->state.smoothFrameTimeMs); } float fpsWidth = ImGui::CalcTextSize(fpsText.c_str()).x; - minWidth = std::max(minWidth, fpsWidth + PerformanceOverlay::Settings::kLabelPadding); // Add padding for labels + minWidth = std::max(minWidth, fpsWidth + Settings::kLabelPadding * scale); } if (this->settings.ShowDrawCalls) { - // Draw calls table needs significant width for all columns - minWidth = std::max(minWidth, PerformanceOverlay::Settings::kDrawCallsTableWidth * this->settings.TextSize); + minWidth = std::max(minWidth, Settings::kDrawCallsTableWidth * scale * this->settings.TextSize); } if (this->settings.ShowVRAM && menu->GetDXGIAdapter3()) { - // VRAM section needs width for the progress bar and text - minWidth = std::max(minWidth, PerformanceOverlay::Settings::kVRAMSectionWidth * this->settings.TextSize); + minWidth = std::max(minWidth, Settings::kVRAMSectionWidth * scale * this->settings.TextSize); } - // Add some padding for window borders and spacing - minWidth += PerformanceOverlay::Settings::kWindowBorderPadding; + minWidth += Settings::kWindowBorderPadding * scale; // Set minimum width, but allow auto-resize for larger content ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_FirstUseEver); @@ -368,7 +368,7 @@ void PerformanceOverlay::DrawOverlay() this->settings.Position = currentPos; } - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 1.0f)); // Tighter spacing + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f * scale, 1.0f * scale)); ImGui::SetWindowFontScale(this->settings.TextSize); // Update graph values @@ -456,7 +456,7 @@ void PerformanceOverlay::DrawFPS() static_cast(this->state.frameTimeHistory.GetHeadIdx()), overlay_text, this->state.smoothedMinFrameTime, this->state.smoothedMaxFrameTime, - ImVec2(graphWidth, 50.0f * this->settings.TextSize)); + ImVec2(graphWidth, 50.0f * Util::GetUIScale() * this->settings.TextSize)); ImGui::PopStyleColor(); @@ -553,7 +553,7 @@ void PerformanceOverlay::DrawPostFGFrameTimeGraph() static_cast(state.postFGFrameTimeHistory.GetHeadIdx()), overlay_text, state.smoothedMinFrameTime, state.smoothedMaxFrameTime, - ImVec2(graphWidth, 50.0f * settings.TextSize)); + ImVec2(graphWidth, 50.0f * Util::GetUIScale() * settings.TextSize)); ImGui::PopStyleColor(); diff --git a/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp b/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp index d257c856cf..fa3cde2c35 100644 --- a/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp +++ b/src/Features/PerformanceOverlay/ABTesting/ABTesting.cpp @@ -1,6 +1,7 @@ #include "ABTesting.h" #include "Features/PerformanceOverlay.h" #include "Menu.h" +#include "Menu/ThemeManager.h" #include "State.h" #include "Utils/FileSystem.h" #include "Utils/UI.h" @@ -235,9 +236,10 @@ void ABTestingManager::DrawOverlayUI() float seconds = (currentTime.QuadPart - lastTestSwitch.QuadPart) / static_cast(timingFrequency.QuadPart); auto remaining = static_cast(testInterval) - seconds; - // Match CS menu background alpha (0.85f from FullPalette[ImGuiCol_ChildBg]) + // Scale position for resolution + const float pos = ThemeManager::Constants::OVERLAY_WINDOW_POSITION * Util::GetUIScale(); ImGui::SetNextWindowBgAlpha(0.85f); - ImGui::SetNextWindowPos(ImVec2(10, 10)); + ImGui::SetNextWindowPos(ImVec2(pos, pos)); if (!ImGui::Begin("A/B Testing", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) { ImGui::End(); return; diff --git a/src/Features/UnifiedWater.cpp b/src/Features/UnifiedWater.cpp index 15980fa334..085be841c6 100644 --- a/src/Features/UnifiedWater.cpp +++ b/src/Features/UnifiedWater.cpp @@ -2,7 +2,9 @@ #include "Menu.h" #include "Menu/ThemeManager.h" -#include "ShaderCache.h" +#include "Util.h" + +#include NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( UnifiedWater::Settings, @@ -52,8 +54,25 @@ void UnifiedWater::DrawOverlay() if (!waterCache || !waterCache->IsBuildRunning() && !waterCache->HasBuildFailed()) return; - const auto shaderCache = globals::shaderCache; - const float vOffset = shaderCache->IsCompiling() || shaderCache->GetFailedTasks() > 0 && !shaderCache->IsHideErrors() ? 120.0f : 0.0f; + const float scale = Util::GetUIScale(); + const float pos = ThemeManager::Constants::OVERLAY_WINDOW_POSITION * scale; + const auto& style = ImGui::GetStyle(); + + // Stack below shader compilation window if it's visible this frame + float vOffset = 0.0f; + if (auto* shaderWin = ImGui::FindWindowByName("ShaderCompilationInfo")) { + if (shaderWin->Active) { + vOffset = (shaderWin->Pos.y + shaderWin->Size.y) - pos + style.ItemSpacing.y; + } + } + // Also stack below shader blocking overlay if visible + if (auto* blockingWin = ImGui::FindWindowByName("ShaderBlockingInfo")) { + if (blockingWin->Active) { + float blockingBottom = (blockingWin->Pos.y + blockingWin->Size.y) - pos + style.ItemSpacing.y; + if (blockingBottom > vOffset) + vOffset = blockingBottom; + } + } const auto snapshot = waterCache->GetBuildProgressSnapshot(); @@ -64,7 +83,7 @@ void UnifiedWater::DrawOverlay() auto percent = static_cast(snapshot.completed) / static_cast(snapshot.total); auto progressOverlay = fmt::format("{}/{} ({:2.1f}%)", snapshot.completed, snapshot.total, 100 * percent); - ImGui::SetNextWindowPos(ImVec2(ThemeManager::Constants::OVERLAY_WINDOW_POSITION, ThemeManager::Constants::OVERLAY_WINDOW_POSITION + vOffset)); + ImGui::SetNextWindowPos(ImVec2(pos, pos + vOffset)); if (!ImGui::Begin("UWCacheCreationInfo", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) { ImGui::End(); return; @@ -74,7 +93,7 @@ void UnifiedWater::DrawOverlay() ImGui::End(); } else if (waterCache->HasBuildFailed()) { - ImGui::SetNextWindowPos(ImVec2(ThemeManager::Constants::OVERLAY_WINDOW_POSITION, ThemeManager::Constants::OVERLAY_WINDOW_POSITION + vOffset)); + ImGui::SetNextWindowPos(ImVec2(pos, pos + vOffset)); if (!ImGui::Begin("UWCacheCreationInfo", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) { ImGui::End(); return; diff --git a/src/Features/VR/SettingsUI.cpp b/src/Features/VR/SettingsUI.cpp index d4f0973250..5be4fc156c 100644 --- a/src/Features/VR/SettingsUI.cpp +++ b/src/Features/VR/SettingsUI.cpp @@ -99,7 +99,8 @@ void VR::DrawOverlay() int secondsLeft = int(std::ceil(autoHideSeconds - elapsed)); ImGuiIO& io = ImGui::GetIO(); - ImVec2 overlaySize(520, 0); + const float scale = Util::GetUIScale(); + ImVec2 overlaySize(520 * scale, 0); ImVec2 overlayPos = ImVec2((io.DisplaySize.x - overlaySize.x) * 0.5f, (io.DisplaySize.y * 0.35f)); ImGui::SetNextWindowPos(overlayPos, ImGuiCond_Always); ImGui::SetNextWindowSize(overlaySize, ImGuiCond_Always); @@ -107,7 +108,7 @@ void VR::DrawOverlay() ImGui::Begin("HowToUseOverlay", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav); - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 500.0f); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 500.0f * scale); ImGui::TextWrapped("How to Use VR Community Shaders Menu:"); ImGui::Separator(); ImGui::TextWrapped("You must open the Main Menu or Tween Menu before VR controls work."); @@ -123,7 +124,7 @@ void VR::DrawOverlay() Util::DrawButtonCombo(settings.VRMenuCloseKeys, true); ImGui::Spacing(); - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 500.0f); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 500.0f * scale); ImGui::TextWrapped("Grip + Thumbstick: Adjust overlay depth (closer/farther)"); ImGui::Spacing(); ImGui::TextWrapped("Tip: Disable this VR overlay by setting Attach Mode to 'None' in VR settings."); @@ -998,7 +999,7 @@ void VR::DrawSettings() // Combo recording popup if (this->isCapturingCombo) { ImGui::OpenPopup("Record Combo"); - ImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(400 * Util::GetUIScale(), 200 * Util::GetUIScale()), ImGuiCond_FirstUseEver); if (ImGui::BeginPopupModal("Record Combo", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { auto GetButtonName = [](uint32_t key) -> const char* { switch (key) { diff --git a/src/Features/WeatherEditor.cpp b/src/Features/WeatherEditor.cpp index 0e73452b1e..1da583b7e2 100644 --- a/src/Features/WeatherEditor.cpp +++ b/src/Features/WeatherEditor.cpp @@ -278,15 +278,17 @@ void WeatherEditor::RenderWeatherDetailsWindow(bool* open) return; // Set initial position if not already set + const float scale = Util::GetUIScale(); if (!WeatherDetailsWindow.PositionSet) { - ImGui::SetNextWindowPos(ImVec2(50.0f, 50.0f)); - WeatherDetailsWindow.Position = ImVec2(50.0f, 50.0f); + const float pos = 50.0f * scale; + ImGui::SetNextWindowPos(ImVec2(pos, pos)); + WeatherDetailsWindow.Position = ImVec2(pos, pos); WeatherDetailsWindow.PositionSet = true; } else { ImGui::SetNextWindowPos(WeatherDetailsWindow.Position, ImGuiCond_FirstUseEver); } - ImGui::SetNextWindowSize(ImVec2(600, 800), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(600 * scale, 800 * scale), ImGuiCond_FirstUseEver); if (ImGui::Begin("Weather Details##Popup", open, ImGuiWindowFlags_None)) { // Remember window position for next frame ImVec2 currentPos = ImGui::GetWindowPos(); diff --git a/src/Menu.cpp b/src/Menu.cpp index 82f5db5d9c..b85abe8571 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -707,7 +707,7 @@ void Menu::DrawSettings() globalScale = ThemeManager::Constants::DEFAULT_GLOBAL_SCALE; // Ensure built-in themes stay at 0.0 } - const float uiScale = exp2(globalScale); // Get current UI scale + const float uiScale = exp2(globalScale); // User's manual GlobalScale for header icons // Check if we can show icons - require setting enabled and at least some icons loaded (for undocked) // For docked mode, always show icons if textures are available bool canShowIcons = settings.Theme.ShowActionIcons && @@ -722,7 +722,7 @@ void Menu::DrawSettings() // Main content starts here - no additional separator needed as it's already handled in the conditions above float footer_height = settings.Theme.ShowFooter ? - (ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 3 + 3.0f) : + (ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 3) : 0.0f; // Static storage for menu state - must persist across frames diff --git a/src/Menu/AdvancedSettingsRenderer.cpp b/src/Menu/AdvancedSettingsRenderer.cpp index b769a59cbe..55f4bc2f0b 100644 --- a/src/Menu/AdvancedSettingsRenderer.cpp +++ b/src/Menu/AdvancedSettingsRenderer.cpp @@ -235,8 +235,9 @@ void AdvancedSettingsRenderer::RenderShaderDebugSection() // Show blocked shader status as a regular section if (!shaderCache->blockedKey.empty()) { // Create a visually distinct box for the blocked shader info with rounded corners and border - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); + const float scale = Util::GetUIScale(); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f * scale); + ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, ImGui::GetStyle().WindowBorderSize); ImVec4 blockedBgColor = Util::Colors::GetError(); blockedBgColor.w = 0.15f; // Semi-transparent background ImGui::PushStyleColor(ImGuiCol_ChildBg, blockedBgColor); diff --git a/src/Menu/FeatureListRenderer.cpp b/src/Menu/FeatureListRenderer.cpp index 5aad803f3f..978984eb7d 100644 --- a/src/Menu/FeatureListRenderer.cpp +++ b/src/Menu/FeatureListRenderer.cpp @@ -179,8 +179,8 @@ namespace ImGui::SetWindowFontScale(1.0f); } - // Reset cursor to after the title block (reduced spacing for tighter layout) - ImGui::SetCursorScreenPos(ImVec2(startPos.x, startPos.y + titleSize.y + 2.0f)); + // Reset cursor to after the title block + ImGui::SetCursorScreenPos(ImVec2(startPos.x, startPos.y + titleSize.y + ImGui::GetStyle().ItemSpacing.y * 0.25f)); } // Draw description if provided (wrapped to content width) @@ -838,19 +838,17 @@ void FeatureListRenderer::DrawMenuVisitor::RenderRestoreDefaultsButton(Feature* return; } - // Position button in screen coordinates so it stays fixed in viewport when scrolling + // Position button in bottom-right corner, accounting for full button frame size + const auto& style = ImGui::GetStyle(); ImVec2 windowPos = ImGui::GetWindowPos(); ImVec2 windowSize = ImGui::GetWindowSize(); - float scrollbarWidth = ImGui::GetScrollMaxY() > 0 ? ImGui::GetStyle().ScrollbarSize : 0.0f; - + float scrollbarWidth = ImGui::GetScrollMaxY() > 0 ? style.ScrollbarSize : 0.0f; float iconDimension = ImGui::GetFrameHeight() * 1.2f; - ImVec2 iconSize = ImVec2(iconDimension, iconDimension); - - float padding = ThemeManager::Constants::OVERLAY_WINDOW_POSITION; - ImVec2 buttonPos = ImVec2( - windowPos.x + windowSize.x - iconSize.x - padding - scrollbarWidth, - windowPos.y + windowSize.y - iconSize.y - padding); - ImGui::SetCursorScreenPos(buttonPos); + ImVec2 iconSize(iconDimension, iconDimension); + ImVec2 frameSize(iconSize.x + style.FramePadding.x * 2, iconSize.y + style.FramePadding.y * 2); + ImGui::SetCursorScreenPos(ImVec2( + windowPos.x + windowSize.x - frameSize.x - style.WindowPadding.x - scrollbarWidth, + windowPos.y + windowSize.y - frameSize.y - style.WindowPadding.y)); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.3f)); diff --git a/src/Menu/HomePageRenderer.cpp b/src/Menu/HomePageRenderer.cpp index 2f59b3a0b8..eb34046068 100644 --- a/src/Menu/HomePageRenderer.cpp +++ b/src/Menu/HomePageRenderer.cpp @@ -43,7 +43,8 @@ void HomePageRenderer::RenderHomePage() void HomePageRenderer::RenderWelcomeSection() { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 8)); + float scale = Util::GetUIScale(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8 * scale, 8 * scale)); // Main title - centered with safe font handling ImGuiIO& io = ImGui::GetIO(); @@ -132,14 +133,14 @@ void HomePageRenderer::RenderWelcomeSection() ImGui::SetTooltip("Join Community Shaders Discord Server"); } } else { - // Fallback centered button when Discord icon is not available - float buttonWidth = 200.0f; + // Fallback button when Discord icon is not available + float buttonWidth = QUICK_LINKS_BUTTON_WIDTH * scale; ImGui::SetCursorPosX((windowSize.x - buttonWidth) * 0.5f); if (ImGui::Button("Join Discord Server", ImVec2(buttonWidth, 0))) { ShellExecuteA(NULL, "open", DISCORD_URL, NULL, NULL, SW_SHOWNORMAL); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Join Community Shaders Discord Server - Icon not found, using fallback button"); + ImGui::SetTooltip("Join Community Shaders Discord Server"); } } @@ -382,40 +383,30 @@ void HomePageRenderer::RenderFirstTimeSetupDialog() io.WantCaptureKeyboard = true; io.MouseDrawCursor = true; // Show ImGui cursor - // Center the window properly with rounded corners and thin border + float uiScale = Util::GetUIScale(); ImVec2 center = ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f); ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); - // Set a minimum width for better layout, but allow auto-sizing for height - ImGui::SetNextWindowSizeConstraints(ImVec2(500, 0), ImVec2(600, FLT_MAX)); + ImGui::SetNextWindowSizeConstraints(ImVec2(DIALOG_MIN_WIDTH * uiScale, 0), ImVec2(DIALOG_MAX_WIDTH * uiScale, FLT_MAX)); ImGui::SetNextWindowFocus(); - // Style for rounded window with thin border - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, DIALOG_CORNER_ROUNDING * uiScale); ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize; if (!ImGui::Begin("##FirstTimeSetup", nullptr, flags)) { - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(); ImGui::End(); return; } - // Draw fullscreen fade on the dialog's own draw list (renders at dialog's z-position, - // covering all windows beneath, with dialog content drawn on top) + // Fullscreen fade on the dialog's draw list — covers all windows beneath at the dialog's z-position auto* drawList = ImGui::GetWindowDrawList(); drawList->PushClipRectFullScreen(); drawList->AddRectFilled(ImVec2(0, 0), io.DisplaySize, IM_COL32(0, 0, 0, MODAL_OVERLAY_ALPHA)); drawList->PopClipRect(); - // Set absolute font size for better readability in this welcome dialog - float targetFontSize = 27.0f; - float currentFontSize = io.FontDefault ? io.FontDefault->FontSize : io.FontGlobalScale * 13.0f; - float fontScale = targetFontSize / currentFontSize; - ImGui::SetWindowFontScale(fontScale); - auto menu = Menu::GetSingleton(); // Render CS logo as background watermark with proper aspect ratio @@ -428,7 +419,7 @@ void HomePageRenderer::RenderFirstTimeSetupDialog() float aspectRatio = textureSize.x / textureSize.y; // Set desired height and calculate width to maintain aspect ratio - float logoHeight = LOGO_WATERMARK_HEIGHT; + float logoHeight = LOGO_WATERMARK_HEIGHT * uiScale; float logoWidth = logoHeight * aspectRatio; ImVec2 logoMin(windowPos.x + (windowSize.x - logoWidth) * 0.5f, @@ -465,7 +456,7 @@ void HomePageRenderer::RenderFirstTimeSetupDialog() centerText(versionLine1); ImGui::Text("%s", versionLine1); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 4.0f); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - DIALOG_LINE_TIGHTEN * uiScale); centerText(versionLine2); ImGui::Text("%s", versionLine2); @@ -482,7 +473,7 @@ void HomePageRenderer::RenderFirstTimeSetupDialog() bool isCapturing = menu->settingToggleKey; // Increase font size for hotkey text - bigger when capturing - ImGui::SetWindowFontScale(fontScale * (isCapturing ? HOTKEY_TEXT_SCALE_CAPTURING : HOTKEY_TEXT_SCALE)); + ImGui::SetWindowFontScale(isCapturing ? HOTKEY_TEXT_SCALE_CAPTURING : HOTKEY_TEXT_SCALE); // Format hotkey with brackets to make it look like a button std::string hotkeyStr; @@ -523,8 +514,7 @@ void HomePageRenderer::RenderFirstTimeSetupDialog() ImGui::TextColored(hotkeyColor, "%s", hotkeyStr.c_str()); - // Reset font scale - ImGui::SetWindowFontScale(fontScale); + ImGui::SetWindowFontScale(1.0f); // Handle click to start hotkey capture if (clicked && !isCapturing) { @@ -558,15 +548,14 @@ void HomePageRenderer::RenderFirstTimeSetupDialog() // Help text with breathing animation const char* helpText = "Press Escape or Enter to continue"; - ImGui::SetWindowFontScale(fontScale * HELP_TEXT_SCALE); + ImGui::SetWindowFontScale(HELP_TEXT_SCALE); centerText(helpText); Util::DrawBreathingText(helpText); - // Reset font scale - ImGui::SetWindowFontScale(fontScale); + ImGui::SetWindowFontScale(1.0f); ImGui::End(); - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(); } bool HomePageRenderer::ShouldShowFirstTimeSetup() diff --git a/src/Menu/HomePageRenderer.h b/src/Menu/HomePageRenderer.h index 5aee082bf9..7f06b46baa 100644 --- a/src/Menu/HomePageRenderer.h +++ b/src/Menu/HomePageRenderer.h @@ -14,11 +14,17 @@ class HomePageRenderer static constexpr float HOTKEY_HOVER_DIM_FACTOR = 0.7f; static constexpr float HELP_TEXT_SCALE = 1.35f; static constexpr float QUICK_LINKS_BUTTON_WIDTH = 180.0f; - static constexpr float LOGO_WATERMARK_HEIGHT = 200.0f; + static constexpr float LOGO_WATERMARK_HEIGHT = 156.0f; static constexpr uint8_t MODAL_OVERLAY_ALPHA = 160; + // First-time setup dialog layout (1080p baseline, scaled by GetUIScale) + static constexpr float DIALOG_MIN_WIDTH = 390.0f; + static constexpr float DIALOG_MAX_WIDTH = 470.0f; + static constexpr float DIALOG_CORNER_ROUNDING = 6.0f; + static constexpr float DIALOG_LINE_TIGHTEN = 3.0f; + // Discord banner scaling constants - static constexpr float DISCORD_BANNER_TARGET_WIDTH_RATIO = 0.85f; // 25% of window width + static constexpr float DISCORD_BANNER_TARGET_WIDTH_RATIO = 0.85f; static constexpr float DISCORD_BANNER_MIN_WIDTH = 150.0f; static constexpr float DISCORD_BANNER_MAX_WIDTH = 1200.0f; static constexpr float DISCORD_BANNER_PADDING_MARGIN = 40.0f; diff --git a/src/Menu/MenuHeaderRenderer.cpp b/src/Menu/MenuHeaderRenderer.cpp index e56049f602..5b8318361e 100644 --- a/src/Menu/MenuHeaderRenderer.cpp +++ b/src/Menu/MenuHeaderRenderer.cpp @@ -92,7 +92,7 @@ void MenuHeaderRenderer::RenderHeader(bool isDocked, bool showLogo, bool canShow if (showLogo) { float logoAspectRatio = uiIcons.logo.size.x / uiIcons.logo.size.y; - contentWidth = (logoSize * logoAspectRatio) + 8.0f; // Logo width + spacing + contentWidth = (logoSize * logoAspectRatio) + ImGui::GetStyle().ItemSpacing.x; } // Calculate text width diff --git a/src/Menu/OverlayRenderer.cpp b/src/Menu/OverlayRenderer.cpp index 30bc0a5b60..503ec06569 100644 --- a/src/Menu/OverlayRenderer.cpp +++ b/src/Menu/OverlayRenderer.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "Feature.h" @@ -137,6 +138,9 @@ void OverlayRenderer::RenderShaderCompilationStatus(const std::functionGetCurrentFailedCount(); auto hide = shaderCache->IsHideErrors(); + const float scale = Util::GetUIScale(); + const float pos = ThemeManager::Constants::OVERLAY_WINDOW_POSITION * scale; + uint64_t totalShaders = shaderCache->GetTotalTasks(); uint64_t compiledShaders = shaderCache->GetCompletedTasks(); @@ -153,7 +157,7 @@ void OverlayRenderer::RenderShaderCompilationStatus(const std::functionIsCompiling()) { - ImGui::SetNextWindowPos(ImVec2(ThemeManager::Constants::OVERLAY_WINDOW_POSITION, ThemeManager::Constants::OVERLAY_WINDOW_POSITION)); + ImGui::SetNextWindowPos(ImVec2(pos, pos)); if (!ImGui::Begin("ShaderCompilationInfo", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) { ImGui::End(); return; @@ -174,7 +178,7 @@ void OverlayRenderer::RenderShaderCompilationStatus(const std::functionActive) { + yPos = shaderWin->Pos.y + shaderWin->Size.y + ImGui::GetStyle().ItemSpacing.y; + } + } + // Also stack below water cache overlay if visible + if (auto* waterWin = ImGui::FindWindowByName("UWCacheCreationInfo")) { + if (waterWin->Active && waterWin->Pos.y + waterWin->Size.y > yPos) { + yPos = waterWin->Pos.y + waterWin->Size.y + ImGui::GetStyle().ItemSpacing.y; + } + } + ImGui::SetNextWindowPos(ImVec2(pos, yPos)); if (!ImGui::Begin("ShaderBlockingInfo", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings)) { ImGui::End(); return; diff --git a/src/Menu/SettingsTabRenderer.cpp b/src/Menu/SettingsTabRenderer.cpp index 15698ac53c..638122b1bc 100644 --- a/src/Menu/SettingsTabRenderer.cpp +++ b/src/Menu/SettingsTabRenderer.cpp @@ -677,7 +677,10 @@ void SettingsTabRenderer::RenderThemesTab() ImGui::Text("Human-readable name shown in the dropdown"); } - ImGui::InputTextMultiline("Description", newThemeDescription, sizeof(newThemeDescription), ImVec2(400, 80)); + { + float scale = Util::GetUIScale(); + ImGui::InputTextMultiline("Description", newThemeDescription, sizeof(newThemeDescription), ImVec2(400 * scale, 80 * scale)); + } if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("Optional description for the theme"); } @@ -1034,12 +1037,13 @@ void SettingsTabRenderer::RenderColorsTab() float frameHeight = ImGui::GetFrameHeight(); // Custom style for filter with icon space - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(iconSpace, 6.0f)); + float scale = Util::GetUIScale(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(iconSpace, 6.0f * scale)); colorFilter.Draw("Filter colors", availableWidth); ImGui::PopStyleVar(); // Draw search icon - ImVec2 iconPos = ImVec2(cursorPos.x + 8.0f, cursorPos.y + (frameHeight - iconSize) * 0.5f); + ImVec2 iconPos = ImVec2(cursorPos.x + 8.0f * scale, cursorPos.y + (frameHeight - iconSize) * 0.5f); ImDrawList* drawList = ImGui::GetWindowDrawList(); ImVec2 center = ImVec2(iconPos.x + iconSize * 0.46f, iconPos.y + iconSize * 0.5f); float radius = iconSize * 0.3f; diff --git a/src/Menu/ThemeManager.cpp b/src/Menu/ThemeManager.cpp index ff440800d7..2266482ceb 100644 --- a/src/Menu/ThemeManager.cpp +++ b/src/Menu/ThemeManager.cpp @@ -95,7 +95,31 @@ void ThemeManager::SetupImGuiStyle(const Menu& menu) globalScale = Constants::DEFAULT_GLOBAL_SCALE; // Ensure built-in themes stay at 0.0 } - styleCopy.ScaleAllSizes(exp2(globalScale)); + // Scale style sizes by GlobalScale and font-size ratio (theme values target 1080p baseline) + float fontScale = 1.0f; + auto& io = ImGui::GetIO(); + if (io.FontDefault) { + constexpr float kBaselineFontSize = Constants::DEFAULT_SCREEN_HEIGHT * Constants::DEFAULT_FONT_RATIO; + fontScale = io.FontDefault->FontSize / kBaselineFontSize; + } + const float scaleFactor = fontScale * exp2(globalScale); + styleCopy.ScaleAllSizes(scaleFactor); + + // ScaleAllSizes skips border and separator sizes — scale them manually, flooring non-zero values at 1px + auto scaleSize = [scaleFactor](float value) -> float { + if (value <= 0.0f) + return 0.0f; + return ImMax(1.0f, ImTrunc(value * scaleFactor)); + }; + styleCopy.WindowBorderSize = scaleSize(themeSettings.Style.WindowBorderSize); + styleCopy.ChildBorderSize = scaleSize(themeSettings.Style.ChildBorderSize); + styleCopy.PopupBorderSize = scaleSize(themeSettings.Style.PopupBorderSize); + styleCopy.FrameBorderSize = scaleSize(themeSettings.Style.FrameBorderSize); + styleCopy.TabBorderSize = scaleSize(themeSettings.Style.TabBorderSize); + styleCopy.TabBarBorderSize = scaleSize(themeSettings.Style.TabBarBorderSize); + styleCopy.SeparatorTextBorderSize = scaleSize(themeSettings.Style.SeparatorTextBorderSize); + styleCopy.DockingSeparatorSize = scaleSize(themeSettings.Style.DockingSeparatorSize); + styleCopy.MouseCursorScale = 1.f; style = styleCopy; style.HoverDelayNormal = themeSettings.TooltipHoverDelay; diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 98127d5d58..b2135c299e 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -2040,7 +2040,7 @@ namespace Util buttonText = "Recording... (Esc to cancel)"; } - if (ImGui::Button(buttonText.c_str(), ImVec2(240, 0))) { + if (ImGui::Button(buttonText.c_str(), ImVec2(0, 0))) { isRecording = false; } @@ -2054,7 +2054,7 @@ namespace Util // Display current binding with unique button ID std::string keyString = Util::Input::KeyIdToString(combo); std::string btnLabel = keyString + "##" + recordingLabel; - if (ImGui::Button(btnLabel.c_str(), ImVec2(240, 0))) { + if (ImGui::Button(btnLabel.c_str(), ImVec2(0, 0))) { isRecording = true; } diff --git a/src/Utils/UI.h b/src/Utils/UI.h index 2a650065f5..254161db42 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -63,6 +63,14 @@ namespace Util // Text rendering constants constexpr float DefaultHeaderTextScale = 1.5f; // Larger scale for header text to improve readability + // Baseline font size for UI layout scaling (1080p dynamic font: DEFAULT_SCREEN_HEIGHT * DEFAULT_FONT_RATIO). + // Theme style values and pixel constants are designed for this size. + constexpr float kBaselineFontSize = 21.0f; + + /// Returns a scale factor relative to the baseline font size, accounting for resolution and GlobalScale. + /// Use to scale hardcoded pixel sizes so layouts adapt to any font size. + inline float GetUIScale() { return ImGui::GetFontSize() / kBaselineFontSize; } + /** * Usage: * if (auto _tt = Util::HoverTooltipWrapper()){ diff --git a/src/WeatherEditor/EditorWindow.cpp b/src/WeatherEditor/EditorWindow.cpp index 0da586a278..ef4029390d 100644 --- a/src/WeatherEditor/EditorWindow.cpp +++ b/src/WeatherEditor/EditorWindow.cpp @@ -52,41 +52,28 @@ void DrawIconStar(ImVec2 center, float radius, ImU32 color, bool filled) void DrawIconCircle(ImVec2 center, float radius, ImU32 color, bool filled) { auto* drawList = ImGui::GetWindowDrawList(); - if (filled) { + if (filled) drawList->AddCircleFilled(center, radius, color, 16); - } else { - drawList->AddCircle(center, radius, color, 16, 1.5f); - } + else + drawList->AddCircle(center, radius, color, 16, 1.5f * Util::GetUIScale()); } void DrawIconWave(ImVec2 center, float width, ImU32 color, bool filled) { auto* drawList = ImGui::GetWindowDrawList(); + const float thickness = (filled ? 3.0f : 1.5f) * Util::GetUIScale(); const int segments = 8; const float amplitude = width * 0.15f; const float waveWidth = width * 0.8f; const float segmentWidth = waveWidth / segments; - ImVec2 start(center.x - waveWidth * 0.5f, center.y); - if (filled) { - // Draw filled wave using multiple horizontal lines - for (int i = 0; i < segments; i++) { - float x1 = start.x + i * segmentWidth; - float x2 = start.x + (i + 1) * segmentWidth; - float y1 = start.y + sinf(i * 3.14159f / 2.0f) * amplitude; - float y2 = start.y + sinf((i + 1) * 3.14159f / 2.0f) * amplitude; - drawList->AddLine(ImVec2(x1, y1), ImVec2(x2, y2), color, 3.0f); - } - } else { - // Draw outline wave - for (int i = 0; i < segments; i++) { - float x1 = start.x + i * segmentWidth; - float x2 = start.x + (i + 1) * segmentWidth; - float y1 = start.y + sinf(i * 3.14159f / 2.0f) * amplitude; - float y2 = start.y + sinf((i + 1) * 3.14159f / 2.0f) * amplitude; - drawList->AddLine(ImVec2(x1, y1), ImVec2(x2, y2), color, 1.5f); - } + for (int i = 0; i < segments; i++) { + float x1 = start.x + i * segmentWidth; + float x2 = start.x + (i + 1) * segmentWidth; + float y1 = start.y + sinf(i * 3.14159f / 2.0f) * amplitude; + float y2 = start.y + sinf((i + 1) * 3.14159f / 2.0f) * amplitude; + drawList->AddLine(ImVec2(x1, y1), ImVec2(x2, y2), color, thickness); } } @@ -268,16 +255,18 @@ void EditorWindow::ShowObjectsWindow() const float comboW = ImGui::CalcTextSize("Editor ID").x + style.FramePadding.x * 2.0f + ImGui::GetFrameHeight(); const float helpW = ImGui::CalcTextSize("(?)").x; const float iconW = ImGui::GetFrameHeight(); + const float scale = Util::GetUIScale(); + const float spacerW = 10.0f * scale; // Fixed width is the sum of every item that follows the search bar on the same row. // Each SameLine() contributes style.ItemSpacing.x; widths are listed explicitly // so adding or removing a widget only requires updating its own expression. const float fixedW = style.ItemSpacing.x + comboW + // combo style.ItemSpacing.x + helpW + // help marker - style.ItemSpacing.x + 10.0f + // spacer before favorites + style.ItemSpacing.x + spacerW + // spacer before favorites style.ItemSpacing.x + iconW + // fav icon style.ItemSpacing.x + ImGui::CalcTextSize("Favorites").x + // "Favorites" label - style.ItemSpacing.x + 10.0f + // spacer before flagged + style.ItemSpacing.x + spacerW + // spacer before flagged style.ItemSpacing.x + iconW + // flag icon style.ItemSpacing.x + ImGui::CalcTextSize("Flagged").x; // "Flagged" label ImGui::SetNextItemWidth(std::max(50.0f, ImGui::GetContentRegionAvail().x - fixedW)); @@ -292,9 +281,10 @@ void EditorWindow::ShowObjectsWindow() ImGui::SameLine(); Util::HelpMarker("Filter the object list by the selected column.\nAll: searches Editor ID, Form ID, File, and Status.\nStatus: hides items with no status marker when the search box is non-empty.\nCtrl+F: Focus search\nEnter: Open selected"); - // Quick filter buttons on same row + // Quick filter buttons + const ImVec2 filterSpacer(spacerW, 0.0f); ImGui::SameLine(); - ImGui::Dummy(ImVec2(10.0f, 0.0f)); // Spacer + ImGui::Dummy(filterSpacer); ImGui::SameLine(); if (IconButton("##filterFavorites", m_showOnlyFavorites, "star")) { m_showOnlyFavorites = !m_showOnlyFavorites; @@ -303,7 +293,7 @@ void EditorWindow::ShowObjectsWindow() ImGui::Text("Favorites"); ImGui::SameLine(); - ImGui::Dummy(ImVec2(10.0f, 0.0f)); // Spacer + ImGui::Dummy(filterSpacer); ImGui::SameLine(); if (IconButton("##filterFlagged", m_showOnlyFlagged, "circle")) { m_showOnlyFlagged = !m_showOnlyFlagged; @@ -370,12 +360,12 @@ void EditorWindow::ShowObjectsWindow() // Create a table for the right column with "Name" and "ID" headers. Different weights to prevent truncation. if (ImGui::BeginTable("DetailsTable", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Sortable)) { - ImGui::TableSetupColumn("Fav", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 38.0f, ColFav); // Favorite indicator - ImGui::TableSetupColumn("Editor ID", ImGuiTableColumnFlags_WidthStretch, 3.5f, ColEditorID); // Largest - weather/template names - ImGui::TableSetupColumn("Form ID", ImGuiTableColumnFlags_WidthFixed, 90.0f, ColFormID); // Fixed - 8 hex chars - ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch, 2.0f, ColFile); // Medium - plugin names - ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch, 1.5f, ColStatus); // Smaller - status text - ImGui::TableSetupColumn("json", ImGuiTableColumnFlags_WidthFixed, 55.0f, ColJson); // JSON file / delete + ImGui::TableSetupColumn("Fav", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoSort, 38.0f * scale, ColFav); // Favorite indicator + ImGui::TableSetupColumn("Editor ID", ImGuiTableColumnFlags_WidthStretch, 3.5f, ColEditorID); // Largest - weather/template names + ImGui::TableSetupColumn("Form ID", ImGuiTableColumnFlags_WidthFixed, 90.0f * scale, ColFormID); // Fixed - 8 hex chars + ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_WidthStretch, 2.0f, ColFile); // Medium - plugin names + ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch, 1.5f, ColStatus); // Smaller - status text + ImGui::TableSetupColumn("json", ImGuiTableColumnFlags_WidthFixed, 55.0f * scale, ColJson); // JSON file / delete ImGui::TableHeadersRow(); @@ -1183,29 +1173,30 @@ void EditorWindow::RenderUI() ImGui::PopStyleColor(); } - // Time slider anchored to the right of the menu bar - // Close button on the right side + // Time slider and close button float menuBarHeight = ImGui::GetFrameHeight(); - float closeButtonSize = menuBarHeight * 0.9f; // 10% smaller than menu bar + float closeButtonSize = menuBarHeight * 0.9f; + const float scale = Util::GetUIScale(); + const float closeButtonMargin = 10.0f * scale; - // Time slider anchored to the right of the menu bar + // Time slider anchored to the right { const float& itemSpacing = ImGui::GetStyle().ItemSpacing.x; char periodBuf[64]; std::snprintf(periodBuf, sizeof(periodBuf), "(%s)", TOD::GetPeriodName(TOD::GetActivePeriod())); float periodWidth = ImGui::CalcTextSize(periodBuf).x; - const float sliderStartX = ImGui::GetWindowWidth() - closeButtonSize - 10.0f - itemSpacing - kMenuBarSliderWidth; + const float sliderStartX = ImGui::GetWindowWidth() - closeButtonSize - closeButtonMargin - itemSpacing - kMenuBarSliderWidth * scale; auto calendar = globals::game::calendar ? globals::game::calendar : RE::Calendar::GetSingleton(); if (calendar && calendar->gameHour) { ImGui::SameLine(sliderStartX - itemSpacing - periodWidth); ImGui::TextUnformatted(periodBuf); ImGui::SameLine(sliderStartX); - ImGui::SetNextItemWidth(kMenuBarSliderWidth); + ImGui::SetNextItemWidth(kMenuBarSliderWidth * scale); DrawGameHourSlider("##MenuBarSlider", "Time: %.2f"); } } - ImGui::SameLine(ImGui::GetWindowWidth() - closeButtonSize - 10.0f); + ImGui::SameLine(ImGui::GetWindowWidth() - closeButtonSize - closeButtonMargin); auto errorColor = Menu::GetSingleton()->GetSettings().Theme.StatusPalette.Error; auto errorHoverColor = errorColor; errorHoverColor.x = std::min(1.0f, errorColor.x * 1.2f); @@ -1499,7 +1490,7 @@ void EditorWindow::ShowSettingsWindow() if (ImGui::BeginTable("FlagsTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Colour", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 60.0f * Util::GetUIScale()); auto& recordMarkers = settings.recordMarkers; @@ -1784,8 +1775,9 @@ void EditorWindow::DrawTimeControls() if (!calendar || !calendar->gameHour || !calendar->timeScale) return; - // Row 1: Pause/Resume + Game Time - if (ImGui::Button(timePaused ? "Resume Time" : "Pause Time", ImVec2(120, 0))) + const float scale = Util::GetUIScale(); + float buttonWidth = 120.0f * scale; + if (ImGui::Button(timePaused ? "Resume Time" : "Pause Time", ImVec2(buttonWidth, 0))) TogglePause(); if (auto _tt = Util::HoverTooltipWrapper()) ImGui::Text("Pause or resume game time progression"); @@ -1801,7 +1793,7 @@ void EditorWindow::DrawTimeControls() timeScaleSlider = calendar->timeScale->value; // Row 2: Reset Speed + TimeScale slider + speed label - if (ImGui::Button("Reset Speed", ImVec2(120, 0))) + if (ImGui::Button("Reset Speed", ImVec2(buttonWidth, 0))) ResetTimeScale(); if (auto _tt = Util::HoverTooltipWrapper()) ImGui::Text("Reset time speed to vanilla (%.1fx)", kVanillaTimeScale); @@ -1936,7 +1928,8 @@ void EditorWindow::RenderNotifications() } float currentTime = static_cast(ImGui::GetTime()); - float yOffset = 10.0f; + const float scale = Util::GetUIScale(); + float yOffset = 10.0f * scale; // Remove expired notifications notifications.erase( @@ -1956,11 +1949,11 @@ void EditorWindow::RenderNotifications() } // Position in top-left corner - ImGui::SetNextWindowPos(ImVec2(10.0f, yOffset), ImGuiCond_Always); + ImGui::SetNextWindowPos(ImVec2(10.0f * scale, yOffset), ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.8f * alpha); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15.0f, 10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f * scale); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15.0f * scale, 10.0f * scale)); if (ImGui::Begin(std::format("##Notification{}", (void*)¬if).c_str(), nullptr, @@ -1971,7 +1964,7 @@ void EditorWindow::RenderNotifications() ImGui::TextUnformatted(notif.message.c_str()); ImGui::PopStyleColor(); - yOffset += ImGui::GetWindowSize().y + 5.0f; + yOffset += ImGui::GetWindowSize().y + 5.0f * scale; } ImGui::End(); diff --git a/src/WeatherEditor/InteriorOnlyPanel.cpp b/src/WeatherEditor/InteriorOnlyPanel.cpp index 11920707cb..4d531b4a38 100644 --- a/src/WeatherEditor/InteriorOnlyPanel.cpp +++ b/src/WeatherEditor/InteriorOnlyPanel.cpp @@ -4,6 +4,7 @@ #include "../Menu.h" #include "../Menu/ThemeManager.h" #include "../SceneSettingsManager.h" +#include "../Utils/UI.h" #include "EditorWindow.h" namespace InteriorOnlyPanel @@ -144,6 +145,8 @@ namespace InteriorOnlyPanel if (readOnly) ImGui::BeginDisabled(); + const float scale = Util::GetUIScale(); + switch (type) { case SceneSettingsManager::SettingType::Boolean: { @@ -160,7 +163,7 @@ namespace InteriorOnlyPanel case SceneSettingsManager::SettingType::Float: { float val = entry.value.get(); - ImGui::SetNextItemWidth(C::SCENE_VALUE_INPUT_WIDTH); + ImGui::SetNextItemWidth(C::SCENE_VALUE_INPUT_WIDTH * scale); if (ImGui::InputFloat("##val", &val, 0.01f, 0.1f, "%.3f")) manager->UpdateEntryValue(kSceneType, index, val, true); if (ImGui::IsItemDeactivatedAfterEdit()) @@ -170,7 +173,7 @@ namespace InteriorOnlyPanel case SceneSettingsManager::SettingType::Integer: { int val = entry.value.get(); - ImGui::SetNextItemWidth(C::SCENE_VALUE_INPUT_WIDTH); + ImGui::SetNextItemWidth(C::SCENE_VALUE_INPUT_WIDTH * scale); if (ImGui::InputInt("##val", &val)) manager->UpdateEntryValue(kSceneType, index, val, true); if (ImGui::IsItemDeactivatedAfterEdit()) @@ -197,7 +200,7 @@ namespace InteriorOnlyPanel ImGui::SameLine(); { auto styledButton = Util::ErrorButtonStyle(); - if (ImGui::Button("X", ImVec2(C::SCENE_DELETE_BUTTON_WIDTH, 0))) { + if (ImGui::Button("X", ImVec2(C::SCENE_DELETE_BUTTON_WIDTH * scale, 0))) { if (entry.source == EntrySource::Overwrite) { pendingDeleteIndex = index; deleteSingleOverwritePopup.message = std::format( diff --git a/src/WeatherEditor/PaletteWindow.cpp b/src/WeatherEditor/PaletteWindow.cpp index 132be6f55b..d2ea752187 100644 --- a/src/WeatherEditor/PaletteWindow.cpp +++ b/src/WeatherEditor/PaletteWindow.cpp @@ -1,5 +1,6 @@ #include "PaletteWindow.h" #include "EditorWindow.h" +#include "Utils/UI.h" // Forward declaration from EditorWindow.cpp void DrawIconStar(ImVec2 center, float radius, ImU32 color, bool filled); @@ -9,8 +10,9 @@ void PaletteWindow::Draw() if (!open) return; - ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - 620, ImGui::GetIO().DisplaySize.y - 420), ImGuiCond_FirstUseEver); + const float scale = Util::GetUIScale(); + ImGui::SetNextWindowSize(ImVec2(600 * scale, 400 * scale), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - 620 * scale, ImGui::GetIO().DisplaySize.y - 420 * scale), ImGuiCond_FirstUseEver); if (ImGui::Begin("Palette", &open, ImGuiWindowFlags_NoFocusOnAppearing)) { if (ImGui::BeginTabBar("PaletteTabs")) { if (ImGui::BeginTabItem("Colours")) { @@ -31,8 +33,9 @@ void PaletteWindow::Draw() void PaletteWindow::DrawColorsTab() { - const float buttonSize = 32.0f; - const float spacing = 8.0f; + const float scale = Util::GetUIScale(); + const float buttonSize = 32.0f * scale; + const float spacing = 8.0f * scale; // Favorites section at top ImGui::SeparatorText("Favourites"); diff --git a/src/WeatherEditor/Weather/ImageSpaceWidget.cpp b/src/WeatherEditor/Weather/ImageSpaceWidget.cpp index 9521e4cee2..a8fd1d9e29 100644 --- a/src/WeatherEditor/Weather/ImageSpaceWidget.cpp +++ b/src/WeatherEditor/Weather/ImageSpaceWidget.cpp @@ -40,8 +40,7 @@ void ImageSpaceWidget::DrawWidget() } BeginScrollableContent("##ISScroll"); { - // Draw all settings in a unified table - if (PropertyDrawer::BeginTable("ImageSpaceSettings", 200.0f)) { + if (PropertyDrawer::BeginTable("ImageSpaceSettings")) { bool changed = false; const char* search = searchBuffer[0] ? searchBuffer : nullptr; diff --git a/src/WeatherEditor/Weather/WeatherWidget.cpp b/src/WeatherEditor/Weather/WeatherWidget.cpp index 6745c38edf..57ef7714e9 100644 --- a/src/WeatherEditor/Weather/WeatherWidget.cpp +++ b/src/WeatherEditor/Weather/WeatherWidget.cpp @@ -55,6 +55,7 @@ WeatherWidget::~WeatherWidget() void WeatherWidget::DrawWidget() { WeatherUtils::SetCurrentWidget(this); + const float scale = Util::GetUIScale(); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { // Draw header with search and all buttons @@ -69,7 +70,7 @@ void WeatherWidget::DrawWidget() if (searchBuffer[0] != '\0' && !searchResults.empty()) { // Find the search input position for dropdown placement ImGui::SetNextWindowPos(ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y)); - ImGui::SetNextWindowSize(ImVec2(200.0f * 1.5f, 0)); + ImGui::SetNextWindowSize(ImVec2(300.0f * Util::GetUIScale(), 0)); ImGui::SetNextWindowFocus(); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.16f, 0.16f, 0.16f, 1.0f)); @@ -242,6 +243,9 @@ void WeatherWidget::DrawWidget() bool recordChanged = false; bool hasParent = editorWindow->settings.enableInheritFromParent && HasParent(); WeatherWidget* parentWidget = hasParent ? GetParent() : nullptr; + const float todLabelOffset = (hasParent ? 120.0f : 100.0f) * scale; + const float formLabelOffset = (hasParent ? 170.0f : 150.0f) * scale; + const float pickerWidth = 225.0f * scale; // ImageSpace Records (per time of day) if (ImGui::CollapsingHeader("ImageSpace", ImGuiTreeNodeFlags_DefaultOpen)) { @@ -266,8 +270,8 @@ void WeatherWidget::DrawWidget() } ImGui::Text("%s:", label.c_str()); - ImGui::SameLine(hasParent ? 120.0f : 100.0f); - if (WeatherUtils::DrawFormPickerCached("##ImageSpace", weather->imageSpaces[i], editorWindow->imageSpaceWidgets, false, true)) { + ImGui::SameLine(todLabelOffset); + if (WeatherUtils::DrawFormPickerCached("##ImageSpace", weather->imageSpaces[i], editorWindow->imageSpaceWidgets, false, true, pickerWidth)) { recordChanged = true; } // Add "Open" button if (weather->imageSpaces[i]) { @@ -313,8 +317,8 @@ void WeatherWidget::DrawWidget() } ImGui::Text("%s:", label.c_str()); - ImGui::SameLine(hasParent ? 120.0f : 100.0f); - if (WeatherUtils::DrawFormPickerCached("##VolumetricLighting", weather->volumetricLighting[i], editorWindow->volumetricLightingWidgets, false, true)) { + ImGui::SameLine(todLabelOffset); + if (WeatherUtils::DrawFormPickerCached("##VolumetricLighting", weather->volumetricLighting[i], editorWindow->volumetricLightingWidgets, false, true, pickerWidth)) { recordChanged = true; } // Add "Open" button if (weather->volumetricLighting[i]) { @@ -355,8 +359,8 @@ void WeatherWidget::DrawWidget() } ImGui::Text("Particle Shader:"); - ImGui::SameLine(hasParent ? 170.0f : 150.0f); - if (WeatherUtils::DrawFormPickerCached("##Precipitation", weather->precipitationData, editorWindow->precipitationWidgets, false, true)) { + ImGui::SameLine(formLabelOffset); + if (WeatherUtils::DrawFormPickerCached("##Precipitation", weather->precipitationData, editorWindow->precipitationWidgets, false, true, pickerWidth)) { recordChanged = true; } // Add "Open" button if (weather->precipitationData) { @@ -395,8 +399,8 @@ void WeatherWidget::DrawWidget() } ImGui::Text("Reference Effect:"); - ImGui::SameLine(hasParent ? 170.0f : 150.0f); - if (WeatherUtils::DrawFormPickerCached("##ReferenceEffect", weather->referenceEffect, editorWindow->referenceEffectWidgets, false, true)) { + ImGui::SameLine(formLabelOffset); + if (WeatherUtils::DrawFormPickerCached("##ReferenceEffect", weather->referenceEffect, editorWindow->referenceEffectWidgets, false, true, pickerWidth)) { recordChanged = true; } // Add "Open" button if (weather->referenceEffect) { @@ -1040,15 +1044,17 @@ void WeatherWidget::DrawCloudSettings() if (layerEnabled) { const ImVec2 badgeSize = ImGui::CalcTextSize(kEnabledBadge); const float headerHeight = ImGui::GetFrameHeight(); + const float badgePadding = ImGui::GetStyle().FramePadding.x; const ImVec2 badgePos = { - ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x - badgeSize.x, + ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x - badgeSize.x - badgePadding, headerScreenY + (headerHeight - badgeSize.y) * 0.5f }; ImGui::GetWindowDrawList()->AddText(badgePos, ImGui::GetColorU32(ImGuiCol_CheckMark), kEnabledBadge); } if (layerOpen) { - ImGui::Indent(10.0f); + const float scale = Util::GetUIScale(); + ImGui::Indent(10.0f * scale); ImGui::Spacing(); // Begin horizontal layout for enable checkbox and sliders on left, texture on right @@ -1074,15 +1080,17 @@ void WeatherWidget::DrawCloudSettings() ImGui::EndGroup(); - // Draw texture in upper right if available + // Draw texture centered in remaining space to the right of the controls if (!settings.clouds[i].texturePath.empty()) { auto* texture = GetCloudTexture(i); if (texture) { - ImGui::SameLine(0.0f, 20.0f); + float textureSize = 128.0f * scale; + float groupEndX = ImGui::GetItemRectMax().x; + float availRight = ImGui::GetContentRegionMax().x + ImGui::GetWindowPos().x - groupEndX; + float offset = std::max(20.0f * scale, (availRight - textureSize) * 0.5f); + ImGui::SameLine(0.0f, offset); ImGui::BeginGroup(); - float textureSize = 128.0f; ImGui::Image((void*)texture, ImVec2(textureSize, textureSize)); - // Small grey subtext below image, clamped to texture width ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + textureSize); ImGui::TextWrapped("%s", settings.clouds[i].texturePath.c_str()); @@ -1094,7 +1102,7 @@ void WeatherWidget::DrawCloudSettings() ImGui::Spacing(); ImGui::Spacing(); - if (TOD::BeginTODTable((layer + "_TOD_Table").c_str())) { + if (TOD::BeginTODTable((layer + "_TOD_Table").c_str(), 120.0f * scale)) { TOD::RenderTODHeader(); TOD::DrawTODSeparator(); @@ -1130,7 +1138,7 @@ void WeatherWidget::DrawCloudSettings() } ImGui::Spacing(); - ImGui::Unindent(10.0f); + ImGui::Unindent(10.0f * scale); } } if (enableChanged) { @@ -1156,10 +1164,11 @@ void WeatherWidget::DrawFogSettings() bool changed = false; + const float scale = Util::GetUIScale(); if (ImGui::BeginTable("FogTable", 3, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Parameter", ImGuiTableColumnFlags_WidthFixed, 200.0f); - ImGui::TableSetupColumn("Day", ImGuiTableColumnFlags_WidthFixed, 250.0f); - ImGui::TableSetupColumn("Night", ImGuiTableColumnFlags_WidthFixed, 250.0f); + ImGui::TableSetupColumn("Parameter", ImGuiTableColumnFlags_WidthFixed, 200.0f * scale); + ImGui::TableSetupColumn("Day", ImGuiTableColumnFlags_WidthFixed, 250.0f * scale); + ImGui::TableSetupColumn("Night", ImGuiTableColumnFlags_WidthFixed, 250.0f * scale); // Header row ImGui::TableNextRow(); @@ -1183,7 +1192,7 @@ void WeatherWidget::DrawFogSettings() if (hasParent) { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f * scale, 2.0f * scale)); ImGui::Checkbox("##FogNear", &settings.inheritFlags["Fog_Near"]); if (settings.inheritFlags["Fog_Near"]) { settings.fogProperties["Day Near"] = parentWidget->settings.fogProperties["Day Near"]; @@ -1210,7 +1219,7 @@ void WeatherWidget::DrawFogSettings() if (hasParent) { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f * scale, 2.0f * scale)); ImGui::Checkbox("##FogFar", &settings.inheritFlags["Fog_Far"]); if (settings.inheritFlags["Fog_Far"]) { settings.fogProperties["Day Far"] = parentWidget->settings.fogProperties["Day Far"]; @@ -1237,7 +1246,7 @@ void WeatherWidget::DrawFogSettings() if (hasParent) { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f * scale, 2.0f * scale)); ImGui::Checkbox("##FogPower", &settings.inheritFlags["Fog_Power"]); if (settings.inheritFlags["Fog_Power"]) { settings.fogProperties["Day Power"] = parentWidget->settings.fogProperties["Day Power"]; @@ -1264,7 +1273,7 @@ void WeatherWidget::DrawFogSettings() if (hasParent) { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f * scale, 2.0f * scale)); ImGui::Checkbox("##FogMax", &settings.inheritFlags["Fog_Max"]); if (settings.inheritFlags["Fog_Max"]) { settings.fogProperties["Day Max"] = parentWidget->settings.fogProperties["Day Max"]; diff --git a/src/WeatherEditor/WeatherUtils.cpp b/src/WeatherEditor/WeatherUtils.cpp index 29df56f6e2..fcb9110600 100644 --- a/src/WeatherEditor/WeatherUtils.cpp +++ b/src/WeatherEditor/WeatherUtils.cpp @@ -398,6 +398,16 @@ namespace TOD // Static debounced tracker for TOD slider rows static DebouncedTracker s_todSliderTracker; + static void DrawCenteredLabel(const char* label) + { + float colWidth = ImGui::GetColumnWidth(); + float textWidth = ImGui::CalcTextSize(label).x; + float offset = (colWidth - textWidth) * 0.5f; + if (offset > 0.0f) + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset); + ImGui::Text("%s", label); + } + bool DrawTODSliderRow(const char* label, float values[4], float minValue, float maxValue, const char* format) { const double debounceDelay = 2.0; @@ -409,11 +419,11 @@ namespace TOD ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("%s", label); + DrawCenteredLabel(label); ImGui::TableSetColumnIndex(1); float totalWidth = ImGui::GetContentRegionAvail().x; - float sliderWidth = (totalWidth - 3 * 8.0f) / 4.0f; + float sliderWidth = (totalWidth - 3 * ImGui::GetStyle().ItemSpacing.x) / 4.0f; for (int i = 0; i < Count; ++i) { if (i > 0) @@ -468,7 +478,7 @@ namespace TOD if (!anyActive) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f); - ImGui::Text("%s", label); + DrawCenteredLabel(label); if (!anyActive) ImGui::PopStyleVar(); @@ -591,11 +601,12 @@ namespace TOD ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("%s", label); + DrawCenteredLabel(label); ImGui::TableSetColumnIndex(1); float totalWidth = ImGui::GetContentRegionAvail().x; - float checkboxWidth = 20.0f; + const float scale = Util::GetUIScale(); + float checkboxWidth = 20.0f * scale; float spacing = ImGui::GetStyle().ItemSpacing.x; float sliderWidth = (totalWidth - (static_cast(Count) - 1) * spacing - (parentValues ? static_cast(Count) * checkboxWidth : 0)) / static_cast(Count); @@ -607,7 +618,7 @@ namespace TOD // Per-column inherit checkbox if (parentValues) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1 * scale, 1 * scale)); ImGui::SetNextItemWidth(checkboxWidth); std::string inheritId = std::string("##inherit_") + label + std::to_string(i); if (ImGui::Checkbox(inheritId.c_str(), &inheritFlags[i])) { @@ -619,7 +630,7 @@ namespace TOD if (ImGui::IsItemHovered()) ImGui::SetTooltip("Inherit from parent"); ImGui::PopStyleVar(); - ImGui::SameLine(0, 2); + ImGui::SameLine(0, 2 * scale); } // Slider (disabled if inheriting) @@ -674,6 +685,7 @@ namespace TOD bool DrawTODColorRow(const char* label, float3 colors[4], bool& inheritFlag, const float3 parentColors[4]) { + const float scale = Util::GetUIScale(); float factors[4]; GetTimeOfDayFactors(factors); bool changed = false; @@ -692,14 +704,14 @@ namespace TOD ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f); // Draw label text - ImGui::Text("%s", label); + DrawCenteredLabel(label); // Draw inherit checkbox right under the label if (parentColors) { ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.2f, 0.2f, 0.2f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2 * scale, 2 * scale)); std::string inheritId = std::string("##inherit_") + label; if (ImGui::Checkbox(inheritId.c_str(), &inheritFlag)) { @@ -847,7 +859,7 @@ namespace TOD ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("%s", label); + DrawCenteredLabel(label); ImGui::TableSetColumnIndex(1); float totalWidth = ImGui::GetContentRegionAvail().x; @@ -882,6 +894,7 @@ namespace TOD bool DrawTODFloatRow(const char* label, float values[4], bool& inheritFlag, const float parentValues[4], float minValue, float maxValue, const char* format) { + const float scale = Util::GetUIScale(); float factors[4]; GetTimeOfDayFactors(factors); bool changed = false; @@ -889,14 +902,14 @@ namespace TOD ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("%s", label); + DrawCenteredLabel(label); // Draw inherit checkbox if (parentValues) { ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.2f, 0.2f, 0.2f, 1.0f)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2 * scale, 2 * scale)); std::string inheritId = std::string("##inherit_") + label; if (ImGui::Checkbox(inheritId.c_str(), &inheritFlag)) { @@ -952,11 +965,11 @@ namespace TOD ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::Text("%s", label); + DrawCenteredLabel(label); ImGui::TableSetColumnIndex(1); float totalWidth = ImGui::GetContentRegionAvail().x; - float sliderWidth = (totalWidth - 3 * 8.0f) / 4.0f; + float sliderWidth = (totalWidth - 3 * ImGui::GetStyle().ItemSpacing.x) / 4.0f; for (int i = 0; i < Count; ++i) { if (i > 0) @@ -983,10 +996,12 @@ namespace TOD return changed; } - bool BeginTODTable(const char* tableId) + bool BeginTODTable(const char* tableId, float paramColumnWidth) { + if (paramColumnWidth <= 0.0f) + paramColumnWidth = 200.0f; if (ImGui::BeginTable(tableId, 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Parameter", ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn("Parameter", ImGuiTableColumnFlags_WidthFixed, paramColumnWidth); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); return true; } @@ -1010,14 +1025,15 @@ namespace TOD bool BeginWidgetSearchBar(char* searchBuffer, size_t bufferSize, bool& searchActive) { - // Check for Ctrl+F to activate search + const float scale = Util::GetUIScale(); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImGui::IsKeyPressed(ImGuiKey_F, false) && ImGui::GetIO().KeyCtrl) { searchActive = true; } ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.2f, 0.25f, 0.3f, 1.0f)); - ImGui::SetNextItemWidth(-100.0f); + ImGui::SetNextItemWidth(-100.0f * scale); if (searchActive) { ImGui::SetKeyboardFocusHere(); @@ -1030,7 +1046,7 @@ bool BeginWidgetSearchBar(char* searchBuffer, size_t bufferSize, bool& searchAct // Clear button ImGui::SameLine(); - if (Util::ButtonWithFlash("Clear", ImVec2(90, 0))) { + if (Util::ButtonWithFlash("Clear", ImVec2(90 * scale, 0))) { searchBuffer[0] = '\0'; } @@ -1053,7 +1069,7 @@ namespace PropertyDrawer bool BeginTable(const char* tableId, float labelWidth) { if (ImGui::BeginTable(tableId, 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Parameter", ImGuiTableColumnFlags_WidthFixed, labelWidth); + ImGui::TableSetupColumn("Parameter", ImGuiTableColumnFlags_WidthFixed, labelWidth * Util::GetUIScale()); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); return true; } diff --git a/src/WeatherEditor/WeatherUtils.h b/src/WeatherEditor/WeatherUtils.h index 774f6f0add..06b216e628 100644 --- a/src/WeatherEditor/WeatherUtils.h +++ b/src/WeatherEditor/WeatherUtils.h @@ -263,7 +263,8 @@ namespace TOD // Helper to begin a TOD table (2 columns: Parameter | Values) // Returns true if table was created successfully - bool BeginTODTable(const char* tableId); + // paramColumnWidth: fixed width for the label column (0 = default 200px) + bool BeginTODTable(const char* tableId, float paramColumnWidth = 0.0f); // End the TOD table void EndTODTable(); diff --git a/src/WeatherEditor/Widget.cpp b/src/WeatherEditor/Widget.cpp index 958630d553..cec715611b 100644 --- a/src/WeatherEditor/Widget.cpp +++ b/src/WeatherEditor/Widget.cpp @@ -219,7 +219,8 @@ void Widget::DrawDeleteConfirmationModal(const char* popupId) ImGui::Separator(); ImGui::Spacing(); - const float buttonWidth = 120.0f; + const float scale = Util::GetUIScale(); + const float buttonWidth = 120.0f * scale; const float spacing = ImGui::GetStyle().ItemSpacing.x; const float totalWidth = (buttonWidth * 2) + spacing; const float cursorX = (ImGui::GetWindowWidth() - totalWidth) / 2.0f; @@ -268,9 +269,10 @@ void Widget::DrawWidgetHeader(const char* searchId, bool showApply, bool showSav auto editorWindow = EditorWindow::GetSingleton(); auto menu = globals::menu; bool useIcons = !editorWindow->settings.useTextButtons && menu && menu->GetSettings().Theme.ShowActionIcons; + const float scale = Util::GetUIScale(); auto drawSearchBar = [&]() { - ImGui::SetNextItemWidth(200.0f); + ImGui::SetNextItemWidth(200.0f * scale); if (ImGui::InputTextWithHint(searchId, "Search settings (Ctrl+F)", searchBuffer, sizeof(searchBuffer))) searchActive = searchBuffer[0] != '\0'; if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_F, false)) @@ -316,7 +318,7 @@ void Widget::DrawWidgetHeader(const char* searchId, bool showApply, bool showSav const float iconSize = ImGui::GetFrameHeight() * 0.85f; const ImVec2 buttonSize(iconSize, iconSize); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, ImGui::GetStyle().ItemSpacing.y)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f * scale, ImGui::GetStyle().ItemSpacing.y)); drawSearchBar(); drawForceWeatherButton(iconSize);