From 928e4d7594fc83a2fdcc255242827bae3cbe94c4 Mon Sep 17 00:00:00 2001 From: Skrubby Skrub In A Shrub <87662196+SkrubbySkrubInAShrub@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:35:51 +0200 Subject: [PATCH 1/5] refactor(UI): button helpers --- .../InverseSquareLighting/LightEditor.cpp | 2 +- src/Menu/SettingsTabRenderer.cpp | 3 +- src/Utils/UI.cpp | 75 ++++++++++++++++++- src/Utils/UI.h | 45 ++++++++++- src/WeatherEditor/EditorWindow.cpp | 11 +-- src/WeatherEditor/InteriorOnlyPanel.cpp | 25 +++---- src/WeatherEditor/Widget.cpp | 53 +++++-------- 7 files changed, 149 insertions(+), 65 deletions(-) diff --git a/src/Features/InverseSquareLighting/LightEditor.cpp b/src/Features/InverseSquareLighting/LightEditor.cpp index abcfdb0a11..ded6461bdb 100644 --- a/src/Features/InverseSquareLighting/LightEditor.cpp +++ b/src/Features/InverseSquareLighting/LightEditor.cpp @@ -1,4 +1,4 @@ -#include "Features/InverseSquareLighting/LightEditor.h" +#include "Features/InverseSquareLighting/LightEditor.h" #include "Features/InverseSquareLighting.h" #include "Features/LightLimitFix.h" #include "Menu.h" diff --git a/src/Menu/SettingsTabRenderer.cpp b/src/Menu/SettingsTabRenderer.cpp index a63b6e2417..c1b035d704 100644 --- a/src/Menu/SettingsTabRenderer.cpp +++ b/src/Menu/SettingsTabRenderer.cpp @@ -675,8 +675,7 @@ void SettingsTabRenderer::RenderThemesTab() if (!isPreset && currentThemeInfo && !currentThemeInfo->filePath.empty()) { ImGui::SameLine(); - auto _style = Util::ErrorButtonStyle(); - if (Util::ButtonWithFlash("Delete")) { + if (Util::ErrorButtonWithFlash("Delete")) { deleteThemePopup.message = "Are you sure you want to delete the theme '" + (currentThemeInfo->displayName.empty() ? currentThemePreset : currentThemeInfo->displayName) + diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 5600c66d40..1a526e78e2 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -511,16 +511,83 @@ namespace Util } } - StyledButtonWrapper ErrorButtonStyle() + namespace { constexpr float kHoverBrighten = 0.2f; constexpr float kActiveBrighten = 0.3f; - auto color = Menu::GetSingleton()->GetTheme().StatusPalette.Error; - auto hover = ImVec4(std::min(color.x + kHoverBrighten, 1.0f), std::min(color.y + kHoverBrighten, 1.0f), std::min(color.z + kHoverBrighten, 1.0f), color.w); - auto active = ImVec4(std::min(color.x + kActiveBrighten, 1.0f), std::min(color.y + kActiveBrighten, 1.0f), std::min(color.z + kActiveBrighten, 1.0f), color.w); + + ImVec4 BrightenButtonColor(const ImVec4& color, float amount) + { + return ImVec4( + std::min(color.x + amount, 1.0f), + std::min(color.y + amount, 1.0f), + std::min(color.z + amount, 1.0f), + color.w); + } + + ImVec4 WithAlpha(const ImVec4& color, float alpha) + { + return ImVec4(color.x, color.y, color.z, alpha); + } + } + + StyledButtonWrapper StatusButtonStyle(const ImVec4& color) + { + auto hover = BrightenButtonColor(color, kHoverBrighten); + auto active = BrightenButtonColor(color, kActiveBrighten); return StyledButtonWrapper(color, hover, active); } + StyledButtonWrapper DestructiveButtonStyle() + { + return StatusButtonStyle(Menu::GetSingleton()->GetTheme().StatusPalette.Error); + } + + bool ErrorButton(const char* label, const ImVec2& size) + { + auto _style = DestructiveButtonStyle(); + return ImGui::Button(label, size); + } + + bool ErrorButtonWithFlash(const char* label, const ImVec2& size, int flashDurationMs) + { + auto _style = DestructiveButtonStyle(); + return ButtonWithFlash(label, size, flashDurationMs); + } + + StyledButtonWrapper StatusTextButtonStyle(const ImVec4& color) + { + return StyledButtonWrapper(color, WithAlpha(color, 0.8f), WithAlpha(color, 1.0f)); + } + + StyledButtonWrapper SuccessButtonStyle() + { + return StatusTextButtonStyle(Menu::GetSingleton()->GetTheme().StatusPalette.SuccessColor); + } + + StyledButtonWrapper WarningButtonStyle() + { + return StatusTextButtonStyle(Menu::GetSingleton()->GetTheme().StatusPalette.Warning); + } + + bool SuccessButton(const char* label, const ImVec2& size) + { + auto _style = SuccessButtonStyle(); + return ImGui::Button(label, size); + } + + bool WarningButton(const char* label, const ImVec2& size) + { + auto _style = WarningButtonStyle(); + return ImGui::Button(label, size); + } + + bool ErrorTextButton(const char* label, const ImVec2& size) + { + auto _style = StatusTextButtonStyle(Menu::GetSingleton()->GetTheme().StatusPalette.Error); + return ImGui::Button(label, size); + } + StyledButtonWrapper TransparentIconButtonStyle() { constexpr float kHoverAlpha = 0.25f; diff --git a/src/Utils/UI.h b/src/Utils/UI.h index af1f450898..2cf6cfdde2 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -216,9 +216,50 @@ namespace Util }; /** - * Creates a StyledButtonWrapper using the theme's error color with auto-derived hover/active variants. + * Creates a StyledButtonWrapper using a status color with the shared hover/active brightness. + * Prefer the named helpers below for semantic UI actions. */ - StyledButtonWrapper ErrorButtonStyle(); + StyledButtonWrapper StatusButtonStyle(const ImVec4& color); + + /** Use for destructive or critical actions such as Delete, Clear, Remove, Reset, or irreversible confirms. */ + StyledButtonWrapper DestructiveButtonStyle(); + + /** + * Creates a StyledButtonWrapper using alpha-based hover/active transitions. + * Used for status text buttons where the color itself communicates intent. + * Prefer the named helpers below. + */ + StyledButtonWrapper StatusTextButtonStyle(const ImVec4& color); + + /** Use for confirmatory or positive actions such as Apply, Confirm, or Accept. */ + StyledButtonWrapper SuccessButtonStyle(); + bool SuccessButton(const char* label, const ImVec2& size = ImVec2(0, 0)); + + /** Use for cautionary or reversible actions such as Revert, Reset, or Undo. */ + StyledButtonWrapper WarningButtonStyle(); + bool WarningButton(const char* label, const ImVec2& size = ImVec2(0, 0)); + + /** + * Alpha-based error-color button — use in toolbar rows alongside SuccessButton/WarningButton + * for visual consistency. For standalone destructive actions (delete icons, close buttons), + * prefer ErrorButton which uses the brightness-based DestructiveButtonStyle. + */ + bool ErrorTextButton(const char* label, const ImVec2& size = ImVec2(0, 0)); + bool ErrorButton(const char* label, const ImVec2& size = ImVec2(0, 0)); + template + bool ErrorImageButton( + const char* id, + TextureID textureId, + const ImVec2& imageSize, + const ImVec2& uv0 = ImVec2(0, 0), + const ImVec2& uv1 = ImVec2(1, 1), + const ImVec4& bgCol = ImVec4(0, 0, 0, 0), + const ImVec4& tintCol = ImVec4(1, 1, 1, 1)) + { + auto _style = DestructiveButtonStyle(); + return ImGui::ImageButton(id, textureId, imageSize, uv0, uv1, bgCol, tintCol); + } + bool ErrorButtonWithFlash(const char* label, const ImVec2& size = ImVec2(0, 0), int flashDurationMs = 200); /** * Creates a transparent button with theme text color hover. Caller must push/pop FrameBorderSize=0 separately. diff --git a/src/WeatherEditor/EditorWindow.cpp b/src/WeatherEditor/EditorWindow.cpp index e792a2f968..65cd17cbc4 100644 --- a/src/WeatherEditor/EditorWindow.cpp +++ b/src/WeatherEditor/EditorWindow.cpp @@ -465,11 +465,10 @@ void EditorWindow::ShowObjectsWindow() auto* menu = globals::menu; if (menu && menu->uiIcons.deleteSettings.texture) { const float iconSize = ImGui::GetFrameHeight() * 0.85f; - auto _style = Util::ErrorButtonStyle(); ImGui::SetNextItemAllowOverlap(); char idBuf[32]; snprintf(idBuf, sizeof(idBuf), "##jsondel_%s", widget->GetFormID().c_str()); - if (ImGui::ImageButton(idBuf, menu->uiIcons.deleteSettings.texture, { iconSize, iconSize })) { + if (Util::ErrorImageButton(idBuf, menu->uiIcons.deleteSettings.texture, { iconSize, iconSize })) { pendingDeleteWidget = widget; pendingDeletePopupRequested = true; } @@ -1248,12 +1247,8 @@ void EditorWindow::RenderUI() // Close button ImGui::SetCursorScreenPos(ImVec2(xButtonX, cursorY)); - { - auto _style = Util::ErrorButtonStyle(); - if (ImGui::Button("X", ImVec2(closeButtonSize, closeButtonSize))) { - open = false; - } - } + if (Util::ErrorButton("X", ImVec2(closeButtonSize, closeButtonSize))) + open = false; Util::AddTooltip("Close Weather Editor (Esc)"); ImGui::PopClipRect(); // End bottom-border clip rect diff --git a/src/WeatherEditor/InteriorOnlyPanel.cpp b/src/WeatherEditor/InteriorOnlyPanel.cpp index 4d531b4a38..0c40d0d5d1 100644 --- a/src/WeatherEditor/InteriorOnlyPanel.cpp +++ b/src/WeatherEditor/InteriorOnlyPanel.cpp @@ -198,20 +198,17 @@ namespace InteriorOnlyPanel // Delete button ImGui::SameLine(); - { - auto styledButton = Util::ErrorButtonStyle(); - if (ImGui::Button("X", ImVec2(C::SCENE_DELETE_BUTTON_WIDTH * scale, 0))) { - if (entry.source == EntrySource::Overwrite) { - pendingDeleteIndex = index; - deleteSingleOverwritePopup.message = std::format( - "Delete overwrite file '{}'?\nThis will permanently remove the file from disk.", - entry.sourceFilename); - deleteSingleOverwritePopup.Request(); - } else { - manager->RemoveSetting(kSceneType, index); - ImGui::PopID(); - return; - } + if (Util::ErrorButton("X", ImVec2(C::SCENE_DELETE_BUTTON_WIDTH * scale, 0))) { + if (entry.source == EntrySource::Overwrite) { + pendingDeleteIndex = index; + deleteSingleOverwritePopup.message = std::format( + "Delete overwrite file '{}'?\nThis will permanently remove the file from disk.", + entry.sourceFilename); + deleteSingleOverwritePopup.Request(); + } else { + manager->RemoveSetting(kSceneType, index); + ImGui::PopID(); + return; } } if (auto _tt = Util::HoverTooltipWrapper()) diff --git a/src/WeatherEditor/Widget.cpp b/src/WeatherEditor/Widget.cpp index 7885bb3e19..5bcc9e6ed2 100644 --- a/src/WeatherEditor/Widget.cpp +++ b/src/WeatherEditor/Widget.cpp @@ -378,11 +378,8 @@ void Widget::DrawWidgetHeader(const char* searchId, bool showApply, bool showSav if (HasSavedFile() && menu->uiIcons.deleteSettings.texture) { ImGui::SameLine(); - { - auto _style = Util::ErrorButtonStyle(); - if (ImGui::ImageButton((std::string(searchId) + "_Delete").c_str(), menu->uiIcons.deleteSettings.texture, buttonSize)) - ImGui::OpenPopup("DeleteConfirmation"); - } + if (Util::ErrorImageButton((std::string(searchId) + "_Delete").c_str(), menu->uiIcons.deleteSettings.texture, buttonSize)) + ImGui::OpenPopup("DeleteConfirmation"); Util::AddTooltip("Delete saved file"); } } @@ -391,57 +388,45 @@ void Widget::DrawWidgetHeader(const char* searchId, bool showApply, bool showSav ImGui::PopStyleColor(2); ImGui::PopStyleVar(2); } else { - const float buttonHeight = ImGui::GetFrameHeight(); if (!menu) { drawSearchBar(); drawForceWeatherButton(); ImGui::Separator(); return; } - const auto& palette = menu->GetTheme().StatusPalette; - drawSearchBar(); drawForceWeatherButton(); - auto styledTextButton = [&](const char* label, const ImVec4& color, const char* tooltip, auto callback) { - ImGui::SameLine(); - ImVec2 size = ImGui::CalcTextSize(label); - size.x += ImGui::GetStyle().FramePadding.x * 2.0f; - size.y = buttonHeight; - auto hover = color; - hover.w = 0.8f; - auto active = color; - active.w = 1.0f; - { - auto styledButton = Util::StyledButtonWrapper(color, hover, active); - if (ImGui::Button(label, size)) - callback(); - } - Util::AddTooltip(tooltip); - }; - auto textButton = [&](const char* label, const char* tooltip, auto callback) { ImGui::SameLine(); - ImVec2 size = ImGui::CalcTextSize(label); - size.x += ImGui::GetStyle().FramePadding.x * 2.0f; - size.y = buttonHeight; - if (Util::ButtonWithFlash(label, size)) + if (Util::ButtonWithFlash(label)) callback(); Util::AddTooltip(tooltip); }; // Apply button - if (showApply && (!editorWindow->settings.autoApplyChanges || RequiresManualApply())) - styledTextButton("Apply", palette.SuccessColor, "Apply changes to the game", [&]() { ApplyChanges(); }); + if (showApply && (!editorWindow->settings.autoApplyChanges || RequiresManualApply())) { + ImGui::SameLine(); + if (Util::SuccessButton("Apply")) + ApplyChanges(); + Util::AddTooltip("Apply changes to the game"); + } // Save/Load/Revert/Delete group if (showSaveLoadRevert) { textButton("Save", "Save to file", [&]() { Save(); }); textButton("Load", "Load saved file (or reset to vanilla if no file)", [&]() { Load(); }); - styledTextButton("Revert", palette.Warning, "Revert to original game values", [&]() { RevertChanges(); }); + ImGui::SameLine(); + if (Util::WarningButton("Revert")) + RevertChanges(); + Util::AddTooltip("Revert to original game values"); - if (HasSavedFile()) - styledTextButton("Delete", palette.Error, "Delete saved file", [&]() { ImGui::OpenPopup("DeleteConfirmation"); }); + if (HasSavedFile()) { + ImGui::SameLine(); + if (Util::ErrorTextButton("Delete")) + ImGui::OpenPopup("DeleteConfirmation"); + Util::AddTooltip("Delete saved file"); + } } drawUnsavedIndicator(); From 16b75f555e6c56dd5897b81d2ca0db7edeeab173 Mon Sep 17 00:00:00 2001 From: Skrubby Skrub In A Shrub <87662196+SkrubbySkrubInAShrub@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:45:56 +0200 Subject: [PATCH 2/5] fix: nitpick --- src/Menu/ThemeManager.h | 4 ++++ src/Utils/UI.cpp | 7 ++----- src/Utils/UI.h | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Menu/ThemeManager.h b/src/Menu/ThemeManager.h index 601a2149d1..3e709b5f08 100644 --- a/src/Menu/ThemeManager.h +++ b/src/Menu/ThemeManager.h @@ -210,6 +210,10 @@ class ThemeManager static constexpr float OVERLAP_FADEIN_SPEED = 8.0f; // Fade-in speed (units/sec) static constexpr float OVERLAP_FADEOUT_SPEED = 4.0f; // Fade-out speed (units/sec) static constexpr float OVERLAP_ALPHA_EPSILON = 0.005f; // Below this alpha is clamped to zero + + // Status button brightness offsets (brightness-based hover/active style) + static constexpr float BUTTON_HOVER_BRIGHTEN = 0.2f; + static constexpr float BUTTON_ACTIVE_BRIGHTEN = 0.3f; }; static ThemeManager* GetSingleton() diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 1a526e78e2..c27c45bbb4 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -513,9 +513,6 @@ namespace Util namespace { - constexpr float kHoverBrighten = 0.2f; - constexpr float kActiveBrighten = 0.3f; - ImVec4 BrightenButtonColor(const ImVec4& color, float amount) { return ImVec4( @@ -533,8 +530,8 @@ namespace Util StyledButtonWrapper StatusButtonStyle(const ImVec4& color) { - auto hover = BrightenButtonColor(color, kHoverBrighten); - auto active = BrightenButtonColor(color, kActiveBrighten); + auto hover = BrightenButtonColor(color, ThemeManager::Constants::BUTTON_HOVER_BRIGHTEN); + auto active = BrightenButtonColor(color, ThemeManager::Constants::BUTTON_ACTIVE_BRIGHTEN); return StyledButtonWrapper(color, hover, active); } diff --git a/src/Utils/UI.h b/src/Utils/UI.h index 2cf6cfdde2..bf7b109279 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -221,7 +221,7 @@ namespace Util */ StyledButtonWrapper StatusButtonStyle(const ImVec4& color); - /** Use for destructive or critical actions such as Delete, Clear, Remove, Reset, or irreversible confirms. */ + /** Use for destructive or critical actions such as Delete, Clear, Remove, or irreversible confirms (including irreversible resets). */ StyledButtonWrapper DestructiveButtonStyle(); /** @@ -235,7 +235,7 @@ namespace Util StyledButtonWrapper SuccessButtonStyle(); bool SuccessButton(const char* label, const ImVec2& size = ImVec2(0, 0)); - /** Use for cautionary or reversible actions such as Revert, Reset, or Undo. */ + /** Use for cautionary or reversible actions such as Revert, Undo, or Reset (reversible/to-saved). */ StyledButtonWrapper WarningButtonStyle(); bool WarningButton(const char* label, const ImVec2& size = ImVec2(0, 0)); From 9afd67e07be3ba4359ed89b2c4c345009cc73b48 Mon Sep 17 00:00:00 2001 From: Skrubby Skrub In A Shrub <87662196+SkrubbySkrubInAShrub@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:57:55 +0200 Subject: [PATCH 3/5] fix: wild edit --- src/Features/InverseSquareLighting/LightEditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/InverseSquareLighting/LightEditor.cpp b/src/Features/InverseSquareLighting/LightEditor.cpp index ded6461bdb..abcfdb0a11 100644 --- a/src/Features/InverseSquareLighting/LightEditor.cpp +++ b/src/Features/InverseSquareLighting/LightEditor.cpp @@ -1,4 +1,4 @@ -#include "Features/InverseSquareLighting/LightEditor.h" +#include "Features/InverseSquareLighting/LightEditor.h" #include "Features/InverseSquareLighting.h" #include "Features/LightLimitFix.h" #include "Menu.h" From d73b43eb439d286e0457fbac50642ee32e284074 Mon Sep 17 00:00:00 2001 From: Skrubby Skrub In A Shrub <87662196+SkrubbySkrubInAShrub@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:00:04 +0200 Subject: [PATCH 4/5] fix: nitpicks --- src/Utils/UI.cpp | 24 ++++++++++++++---------- src/Utils/UI.h | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index c27c45bbb4..f4e4600979 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -526,6 +526,13 @@ namespace Util { return ImVec4(color.x, color.y, color.z, alpha); } + + template + bool InvokeStyledButton(StyleFn styleProvider, ButtonFn buttonCall) + { + auto _style = styleProvider(); + return buttonCall(); + } } StyledButtonWrapper StatusButtonStyle(const ImVec4& color) @@ -542,14 +549,12 @@ namespace Util bool ErrorButton(const char* label, const ImVec2& size) { - auto _style = DestructiveButtonStyle(); - return ImGui::Button(label, size); + return InvokeStyledButton(DestructiveButtonStyle, [&] { return ImGui::Button(label, size); }); } bool ErrorButtonWithFlash(const char* label, const ImVec2& size, int flashDurationMs) { - auto _style = DestructiveButtonStyle(); - return ButtonWithFlash(label, size, flashDurationMs); + return InvokeStyledButton(DestructiveButtonStyle, [&] { return ButtonWithFlash(label, size, flashDurationMs); }); } StyledButtonWrapper StatusTextButtonStyle(const ImVec4& color) @@ -569,20 +574,19 @@ namespace Util bool SuccessButton(const char* label, const ImVec2& size) { - auto _style = SuccessButtonStyle(); - return ImGui::Button(label, size); + return InvokeStyledButton(SuccessButtonStyle, [&] { return ImGui::Button(label, size); }); } bool WarningButton(const char* label, const ImVec2& size) { - auto _style = WarningButtonStyle(); - return ImGui::Button(label, size); + return InvokeStyledButton(WarningButtonStyle, [&] { return ImGui::Button(label, size); }); } bool ErrorTextButton(const char* label, const ImVec2& size) { - auto _style = StatusTextButtonStyle(Menu::GetSingleton()->GetTheme().StatusPalette.Error); - return ImGui::Button(label, size); + return InvokeStyledButton( + [] { return StatusTextButtonStyle(Menu::GetSingleton()->GetTheme().StatusPalette.Error); }, + [&] { return ImGui::Button(label, size); }); } StyledButtonWrapper TransparentIconButtonStyle() diff --git a/src/Utils/UI.h b/src/Utils/UI.h index bf7b109279..eb8914cb19 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -246,6 +246,7 @@ namespace Util */ bool ErrorTextButton(const char* label, const ImVec2& size = ImVec2(0, 0)); bool ErrorButton(const char* label, const ImVec2& size = ImVec2(0, 0)); + /** id must be unique per ImGui element to prevent ID collisions. */ template bool ErrorImageButton( const char* id, From d9a7293cf57305aa02e9e1e05976125e42be321b Mon Sep 17 00:00:00 2001 From: Skrubby Skrub In A Shrub <87662196+SkrubbySkrubInAShrub@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:40:09 +0200 Subject: [PATCH 5/5] refactor: address Soda comments --- src/Menu/ThemeManager.h | 6 +++++- src/Utils/UI.cpp | 32 +++++++++++++++++++------------- src/Utils/UI.h | 28 ++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/Menu/ThemeManager.h b/src/Menu/ThemeManager.h index 3e709b5f08..ea9ed43208 100644 --- a/src/Menu/ThemeManager.h +++ b/src/Menu/ThemeManager.h @@ -211,9 +211,13 @@ class ThemeManager static constexpr float OVERLAP_FADEOUT_SPEED = 4.0f; // Fade-out speed (units/sec) static constexpr float OVERLAP_ALPHA_EPSILON = 0.005f; // Below this alpha is clamped to zero - // Status button brightness offsets (brightness-based hover/active style) + // Status button brightness adjustment offsets. Bright colors are darkened by the same amounts for contrast. + static constexpr float BUTTON_MIN_COLOR_CHANNEL = 0.0f; + static constexpr float BUTTON_MAX_COLOR_CHANNEL = 1.0f; static constexpr float BUTTON_HOVER_BRIGHTEN = 0.2f; static constexpr float BUTTON_ACTIVE_BRIGHTEN = 0.3f; + static constexpr float BUTTON_STATUS_TEXT_HOVER_ALPHA = 0.8f; + static constexpr float BUTTON_STATUS_TEXT_ACTIVE_ALPHA = 1.0f; }; static ThemeManager* GetSingleton() diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index f4e4600979..cd997fc1ec 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -511,14 +511,18 @@ namespace Util } } - namespace + namespace ButtonHelpers { - ImVec4 BrightenButtonColor(const ImVec4& color, float amount) + ImVec4 AdjustButtonColor(const ImVec4& color, float amount) { + const float maxChannel = std::max({ color.x, color.y, color.z }); + const float minChannel = ThemeManager::Constants::BUTTON_MIN_COLOR_CHANNEL; + const float maxColorChannel = ThemeManager::Constants::BUTTON_MAX_COLOR_CHANNEL; + const float adjustment = maxChannel <= (maxColorChannel - amount) ? amount : -amount; return ImVec4( - std::min(color.x + amount, 1.0f), - std::min(color.y + amount, 1.0f), - std::min(color.z + amount, 1.0f), + std::clamp(color.x + adjustment, minChannel, maxColorChannel), + std::clamp(color.y + adjustment, minChannel, maxColorChannel), + std::clamp(color.z + adjustment, minChannel, maxColorChannel), color.w); } @@ -537,8 +541,8 @@ namespace Util StyledButtonWrapper StatusButtonStyle(const ImVec4& color) { - auto hover = BrightenButtonColor(color, ThemeManager::Constants::BUTTON_HOVER_BRIGHTEN); - auto active = BrightenButtonColor(color, ThemeManager::Constants::BUTTON_ACTIVE_BRIGHTEN); + auto hover = ButtonHelpers::AdjustButtonColor(color, ThemeManager::Constants::BUTTON_HOVER_BRIGHTEN); + auto active = ButtonHelpers::AdjustButtonColor(color, ThemeManager::Constants::BUTTON_ACTIVE_BRIGHTEN); return StyledButtonWrapper(color, hover, active); } @@ -549,17 +553,19 @@ namespace Util bool ErrorButton(const char* label, const ImVec2& size) { - return InvokeStyledButton(DestructiveButtonStyle, [&] { return ImGui::Button(label, size); }); + return ButtonHelpers::InvokeStyledButton(DestructiveButtonStyle, [&] { return ImGui::Button(label, size); }); } bool ErrorButtonWithFlash(const char* label, const ImVec2& size, int flashDurationMs) { - return InvokeStyledButton(DestructiveButtonStyle, [&] { return ButtonWithFlash(label, size, flashDurationMs); }); + return ButtonHelpers::InvokeStyledButton(DestructiveButtonStyle, [&] { return ButtonWithFlash(label, size, flashDurationMs); }); } StyledButtonWrapper StatusTextButtonStyle(const ImVec4& color) { - return StyledButtonWrapper(color, WithAlpha(color, 0.8f), WithAlpha(color, 1.0f)); + return StyledButtonWrapper(color, + ButtonHelpers::WithAlpha(color, ThemeManager::Constants::BUTTON_STATUS_TEXT_HOVER_ALPHA), + ButtonHelpers::WithAlpha(color, ThemeManager::Constants::BUTTON_STATUS_TEXT_ACTIVE_ALPHA)); } StyledButtonWrapper SuccessButtonStyle() @@ -574,17 +580,17 @@ namespace Util bool SuccessButton(const char* label, const ImVec2& size) { - return InvokeStyledButton(SuccessButtonStyle, [&] { return ImGui::Button(label, size); }); + return ButtonHelpers::InvokeStyledButton(SuccessButtonStyle, [&] { return ImGui::Button(label, size); }); } bool WarningButton(const char* label, const ImVec2& size) { - return InvokeStyledButton(WarningButtonStyle, [&] { return ImGui::Button(label, size); }); + return ButtonHelpers::InvokeStyledButton(WarningButtonStyle, [&] { return ImGui::Button(label, size); }); } bool ErrorTextButton(const char* label, const ImVec2& size) { - return InvokeStyledButton( + return ButtonHelpers::InvokeStyledButton( [] { return StatusTextButtonStyle(Menu::GetSingleton()->GetTheme().StatusPalette.Error); }, [&] { return ImGui::Button(label, size); }); } diff --git a/src/Utils/UI.h b/src/Utils/UI.h index eb8914cb19..f077ffacd0 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -216,12 +216,15 @@ namespace Util }; /** - * Creates a StyledButtonWrapper using a status color with the shared hover/active brightness. - * Prefer the named helpers below for semantic UI actions. + * Creates a StyledButtonWrapper using a status color with shared hover/active adjustment. + * Use when a caller needs a custom status color instead of one of the semantic helpers below. */ StyledButtonWrapper StatusButtonStyle(const ImVec4& color); - /** Use for destructive or critical actions such as Delete, Clear, Remove, or irreversible confirms (including irreversible resets). */ + /** + * Style for destructive or critical actions such as Delete, Clear, Remove, or irreversible confirms. + * Uses the theme error color as the button fill and adjusts hover/active brightness for contrast. + */ StyledButtonWrapper DestructiveButtonStyle(); /** @@ -231,12 +234,16 @@ namespace Util */ StyledButtonWrapper StatusTextButtonStyle(const ImVec4& color); - /** Use for confirmatory or positive actions such as Apply, Confirm, or Accept. */ + /** Style for confirmatory or positive actions such as Apply, Confirm, or Accept. */ StyledButtonWrapper SuccessButtonStyle(); + + /** Draws a theme success-colored button for confirmatory or positive actions. */ bool SuccessButton(const char* label, const ImVec2& size = ImVec2(0, 0)); - /** Use for cautionary or reversible actions such as Revert, Undo, or Reset (reversible/to-saved). */ + /** Style for cautionary or reversible actions such as Revert, Undo, or Reset to saved values. */ StyledButtonWrapper WarningButtonStyle(); + + /** Draws a theme warning-colored button for cautionary or reversible actions. */ bool WarningButton(const char* label, const ImVec2& size = ImVec2(0, 0)); /** @@ -245,8 +252,15 @@ namespace Util * prefer ErrorButton which uses the brightness-based DestructiveButtonStyle. */ bool ErrorTextButton(const char* label, const ImVec2& size = ImVec2(0, 0)); + + /** Draws a destructive theme error-colored button for delete, clear, remove, or irreversible actions. */ bool ErrorButton(const char* label, const ImVec2& size = ImVec2(0, 0)); - /** id must be unique per ImGui element to prevent ID collisions. */ + + /** + * Draws a destructive icon/image button using the theme error color for button chrome. + * Use for destructive image-only controls such as delete icons. + * id must be unique per ImGui element to prevent ID collisions. + */ template bool ErrorImageButton( const char* id, @@ -260,6 +274,8 @@ namespace Util auto _style = DestructiveButtonStyle(); return ImGui::ImageButton(id, textureId, imageSize, uv0, uv1, bgCol, tintCol); } + + /** Draws a destructive button with ButtonWithFlash click feedback. */ bool ErrorButtonWithFlash(const char* label, const ImVec2& size = ImVec2(0, 0), int flashDurationMs = 200); /**