From 979a0bc487864675517fb2fab28f87dc8bf43041 Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Thu, 21 Mar 2024 17:01:02 -0400 Subject: [PATCH] Appearance: Hide most colors by default and compute them automatically. In the new "automatic" mode, the number of displayed colors is just 3, as opposed to the full 38. Much more manageable! The HSL routines added in this commit were derived from https://gist.github.com/ciembor/1494530 which is itself derived from the Wikipedia page describing HSL/HSV. Part of #15543 and #11636. Change-Id: I230a358d18c379fb0673162e0b3cbdb8d1b8d84e Reviewed-on: https://review.haiku-os.org/c/haiku/+/7479 Tested-by: Commit checker robot Reviewed-by: waddlesplash --- headers/private/shared/HSL.h | 23 +++ src/kits/shared/HSL.cpp | 85 +++++++++++ src/kits/shared/Jamfile | 1 + src/preferences/appearance/APRView.cpp | 204 ++++++++++++++++++++++--- src/preferences/appearance/APRView.h | 7 +- src/preferences/appearance/Jamfile | 4 +- 6 files changed, 296 insertions(+), 28 deletions(-) create mode 100644 headers/private/shared/HSL.h create mode 100644 src/kits/shared/HSL.cpp diff --git a/headers/private/shared/HSL.h b/headers/private/shared/HSL.h new file mode 100644 index 0000000000..bacdc99737 --- /dev/null +++ b/headers/private/shared/HSL.h @@ -0,0 +1,23 @@ +/* + * Copyright 2024, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _HSL_H +#define _HSL_H + + +#include + + +typedef struct hsl_color { + float hue, saturation, lightness; + + static hsl_color from_rgb(const rgb_color& rgb); + rgb_color to_rgb() const; + +private: + static float hue_to_rgb(float p, float q, float t); +} hsl_color; + + +#endif // _HSL_H diff --git a/src/kits/shared/HSL.cpp b/src/kits/shared/HSL.cpp new file mode 100644 index 0000000000..9f8a1318ea --- /dev/null +++ b/src/kits/shared/HSL.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2024, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include + + +hsl_color +hsl_color::from_rgb(const rgb_color& rgb) +{ + hsl_color result; + + float r = rgb.red / 255.0f; + float g = rgb.green / 255.0f; + float b = rgb.blue / 255.0f; + + float max = max_c(max_c(r, g), b); + float min = min_c(min_c(r, g), b); + + result.hue = result.saturation = result.lightness = (max + min) / 2; + + if (max == min) { + // grayscale + result.hue = result.saturation = 0; + } else { + float diff = max - min; + result.saturation + = (result.lightness > 0.5) ? (diff / (2 - max - min)) : (diff / (max + min)); + + if (max == r) + result.hue = (g - b) / diff + (g < b ? 6 : 0); + else if (max == g) + result.hue = (b - r) / diff + 2; + else if (max == b) + result.hue = (r - g) / diff + 4; + + result.hue /= 6; + } + + return result; +} + + +rgb_color +hsl_color::to_rgb() const +{ + rgb_color result; + result.alpha = 255; + + if (saturation == 0) { + // grayscale + result.red = result.green = result.blue = uint8(lightness * 255); + } else { + float q = lightness < 0.5 ? (lightness * (1 + saturation)) + : (lightness + saturation - lightness * saturation); + float p = 2 * lightness - q; + result.red = uint8(hue_to_rgb(p, q, hue + 1./3) * 255); + result.green = uint8(hue_to_rgb(p, q, hue) * 255); + result.blue = uint8(hue_to_rgb(p, q, hue - 1./3) * 255); + } + + return result; +} + + +// reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Color_conversion_formulae +// (from_rgb() and to_rgb() are derived from the same) +float +hsl_color::hue_to_rgb(float p, float q, float t) +{ + if (t < 0) + t += 1; + if (t > 1) + t -= 1; + if (t < 1./6) + return p + (q - p) * 6 * t; + if (t < 1./2) + return q; + if (t < 2./3) + return p + (q - p) * (2./3 - t) * 6; + + return p; +} diff --git a/src/kits/shared/Jamfile b/src/kits/shared/Jamfile index 8087303e7c..3c2d0a1fe2 100644 --- a/src/kits/shared/Jamfile +++ b/src/kits/shared/Jamfile @@ -37,6 +37,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { DateTimeEdit.cpp DragTrackingFilter.cpp DriverSettingsMessageAdapter.cpp + HSL.cpp HashString.cpp IconButton.cpp IconView.cpp diff --git a/src/preferences/appearance/APRView.cpp b/src/preferences/appearance/APRView.cpp index 2bab6b0ddd..df9f7a7d89 100644 --- a/src/preferences/appearance/APRView.cpp +++ b/src/preferences/appearance/APRView.cpp @@ -16,9 +16,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -37,6 +39,7 @@ #define B_TRANSLATION_CONTEXT "Colors tab" #define COLOR_DROPPED 'cldp' +#define AUTO_ADJUST_CHANGED 'madj' APRView::APRView(const char* name) @@ -47,19 +50,17 @@ APRView::APRView(const char* name) LoadSettings(); + fAutoSelectCheckBox = new BCheckBox(B_TRANSLATE("Automatically pick secondary colors"), + new BMessage(AUTO_ADJUST_CHANGED)); + fAutoSelectCheckBox->SetValue(true); + // Set up list of color attributes fAttrList = new ColorWhichListView("AttributeList"); fScrollView = new BScrollView("ScrollView", fAttrList, 0, false, true); fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); - int32 count = color_description_count(); - for (int32 i = 0; i < count; i++) { - const ColorDescription& description = *get_color_description(i); - const char* text = B_TRANSLATE_NOCOLLECT(description.text); - color_which which = description.which; - fAttrList->AddItem(new ColorWhichItem(text, which, ui_color(which))); - } + _CreateItems(); fColorPreview = new ColorPreview(new BMessage(COLOR_DROPPED), 0); fColorPreview->SetExplicitAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER, @@ -69,6 +70,7 @@ APRView::APRView(const char* name) "picker", new BMessage(UPDATE_COLOR)); BLayoutBuilder::Group<>(this, B_VERTICAL) + .Add(fAutoSelectCheckBox) .Add(fScrollView, 10.0) .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING) .Add(fColorPreview) @@ -91,6 +93,7 @@ APRView::~APRView() void APRView::AttachedToWindow() { + fAutoSelectCheckBox->SetTarget(this); fPicker->SetTarget(this); fAttrList->SetTarget(this); fColorPreview->SetTarget(this); @@ -157,6 +160,12 @@ APRView::MessageReceived(BMessage *msg) break; } + case AUTO_ADJUST_CHANGED: + { + _CreateItems(); + break; + } + default: BView::MessageReceived(msg); break; @@ -222,10 +231,55 @@ APRView::IsRevertable() void -APRView::_SetColor(color_which which, rgb_color color) +APRView::_CreateItems() { - set_ui_color(which, color); - fCurrentColors.SetColor(ui_color_name(which), color); + while (fAttrList->CountItems() > 0) + delete fAttrList->RemoveItem((int32)0); + + const bool autoSelect = fAutoSelectCheckBox->Value(); + const int32 count = color_description_count(); + for (int32 i = 0; i < count; i++) { + const ColorDescription& description = *get_color_description(i); + const color_which which = description.which; + if (autoSelect) { + if (which != B_PANEL_BACKGROUND_COLOR + && which != B_STATUS_BAR_COLOR + && which != B_WINDOW_TAB_COLOR) { + continue; + } + } + + const char* text = B_TRANSLATE_NOCOLLECT(description.text); + fAttrList->AddItem(new ColorWhichItem(text, which, ui_color(which))); + } + + fAttrList->Select(0); +} + + +void +APRView::_UpdatePreviews(const BMessage& colors) +{ + rgb_color color; + for (int32 i = color_description_count() - 1; i >= 0; i--) { + ColorWhichItem* item = static_cast(fAttrList->ItemAt(i)); + if (item == NULL) + continue; + + color = colors.GetColor(ui_color_name(item->ColorWhich()), + make_color(255, 0, 255)); + + item->SetColor(color); + fAttrList->InvalidateItem(i); + } +} + + +void +APRView::_SetUIColors(const BMessage& colors) +{ + set_ui_colors(&colors); + fCurrentColors = colors; } @@ -248,26 +302,126 @@ APRView::_SetCurrentColor(rgb_color color) void -APRView::_SetUIColors(const BMessage& colors) +APRView::_SetColor(color_which which, rgb_color color) { - set_ui_colors(&colors); - fCurrentColors = colors; + _SetOneColor(which, color); + + if (!fAutoSelectCheckBox->Value()) + return; + + // Protect against accidentally overwriting colors. + if (ui_color(which) == color) + return; + + if (which == B_PANEL_BACKGROUND_COLOR) { + const bool isDark = color.IsDark(); + + _SetOneColor(B_MENU_BACKGROUND_COLOR, color); + _SetOneColor(B_SCROLL_BAR_THUMB_COLOR, color); + + const rgb_color menuSelectedBackground + = tint_color(color, isDark ? B_LIGHTEN_2_TINT : B_DARKEN_2_TINT); + _SetOneColor(B_MENU_SELECTED_BACKGROUND_COLOR, menuSelectedBackground); + + const rgb_color controlBackground = tint_color(color, 0.25 /* lighten "> 2" */); + _SetOneColor(B_CONTROL_BACKGROUND_COLOR, controlBackground); + + const rgb_color controlBorder + = tint_color(color, isDark ? 0.4875 : 1.20 /* lighten/darken "1.5" */); + _SetOneColor(B_CONTROL_BORDER_COLOR, controlBorder); + + const rgb_color windowBorder = tint_color(color, 0.75); + _SetOneColor(B_WINDOW_BORDER_COLOR, windowBorder); + + const rgb_color inactiveWindowBorder = tint_color(color, B_LIGHTEN_1_TINT); + _SetOneColor(B_WINDOW_INACTIVE_TAB_COLOR, inactiveWindowBorder); + _SetOneColor(B_WINDOW_INACTIVE_BORDER_COLOR, inactiveWindowBorder); + + const rgb_color listSelectedBackground + = tint_color(color, isDark ? 0.77 : 1.12 /* lighten/darken "< 1" */ ); + _SetOneColor(B_LIST_SELECTED_BACKGROUND_COLOR, listSelectedBackground); + + const color_which fromDefaults[] = { + B_MENU_ITEM_TEXT_COLOR, + B_MENU_SELECTED_ITEM_TEXT_COLOR, + B_MENU_SELECTED_BORDER_COLOR, + B_PANEL_TEXT_COLOR, + B_DOCUMENT_BACKGROUND_COLOR, + B_DOCUMENT_TEXT_COLOR, + B_CONTROL_TEXT_COLOR, + B_NAVIGATION_PULSE_COLOR, + B_WINDOW_INACTIVE_TEXT_COLOR, + B_LIST_BACKGROUND_COLOR, + B_LIST_ITEM_TEXT_COLOR, + B_LIST_SELECTED_ITEM_TEXT_COLOR, + + B_SHINE_COLOR, + B_SHADOW_COLOR, + + B_LINK_TEXT_COLOR, + B_LINK_HOVER_COLOR, + B_LINK_ACTIVE_COLOR, + B_LINK_VISITED_COLOR, + }; + for (size_t i = 0; i < B_COUNT_OF(fromDefaults); i++) + _SetOneColor(fromDefaults[i], BPrivate::GetSystemColor(fromDefaults[i], isDark)); + } else if (which == B_STATUS_BAR_COLOR) { + const hsl_color statusColorHSL = hsl_color::from_rgb(color); + + hsl_color controlHighlight = statusColorHSL; + controlHighlight.saturation = max_c(0.2f, controlHighlight.saturation / 2.f); + _SetOneColor(B_CONTROL_HIGHLIGHT_COLOR, controlHighlight.to_rgb()); + + hsl_color controlMark = statusColorHSL; + controlMark.saturation = max_c(0.2f, controlMark.saturation * 0.67f); + controlMark.lightness = max_c(0.25f, controlMark.lightness * 0.55f); + _SetOneColor(B_CONTROL_MARK_COLOR, controlMark.to_rgb()); + + rgb_color keyboardNav; { + hsl_color keyboardNavHSL = statusColorHSL; + keyboardNavHSL.lightness = max_c(0.2f, keyboardNavHSL.lightness * 0.75f); + keyboardNav = keyboardNavHSL.to_rgb(); + + // Use primary color channel only. + if (keyboardNav.blue >= max_c(keyboardNav.red, keyboardNav.green)) + keyboardNav.red = keyboardNav.green = 0; + else if (keyboardNav.red >= max_c(keyboardNav.green, keyboardNav.blue)) + keyboardNav.green = keyboardNav.blue = 0; + else + keyboardNav.red = keyboardNav.blue = 0; + } + _SetOneColor(B_KEYBOARD_NAVIGATION_COLOR, keyboardNav); + } else if (which == B_WINDOW_TAB_COLOR) { + const bool isDark = color.IsDark(); + const hsl_color tabColorHSL = hsl_color::from_rgb(color); + const float tabColorSaturation + = tabColorHSL.saturation != 0 ? tabColorHSL.saturation : tabColorHSL.lightness; + + _SetOneColor(B_WINDOW_TEXT_COLOR, + BPrivate::GetSystemColor(B_WINDOW_TEXT_COLOR, isDark)); + _SetOneColor(B_TOOL_TIP_TEXT_COLOR, + BPrivate::GetSystemColor(B_TOOL_TIP_TEXT_COLOR, isDark)); + + const rgb_color toolTipBackground = tint_color(color, isDark ? 1.7 : 0.15); + _SetOneColor(B_TOOL_TIP_BACKGROUND_COLOR, toolTipBackground); + + hsl_color success = hsl_color::from_rgb(BPrivate::GetSystemColor(B_SUCCESS_COLOR, isDark)); + success.saturation = max_c(0.25f, tabColorSaturation * 0.68f); + _SetOneColor(B_SUCCESS_COLOR, success.to_rgb()); + + hsl_color failure = hsl_color::from_rgb(BPrivate::GetSystemColor(B_FAILURE_COLOR, isDark)); + failure.saturation = max_c(0.25f, tabColorSaturation); + _SetOneColor(B_FAILURE_COLOR, failure.to_rgb()); + } } void -APRView::_UpdatePreviews(const BMessage& colors) +APRView::_SetOneColor(color_which which, rgb_color color) { - rgb_color color; - for (int32 i = color_description_count() - 1; i >= 0; i--) { - ColorWhichItem* item = static_cast(fAttrList->ItemAt(i)); - if (item == NULL) - continue; + if (ui_color(which) == color) + return; - color = colors.GetColor(ui_color_name(get_color_description(i)->which), - make_color(255, 0, 255)); - - item->SetColor(color); - fAttrList->InvalidateItem(i); - } + set_ui_color(which, color); + fCurrentColors.SetColor(ui_color_name(which), color); } diff --git a/src/preferences/appearance/APRView.h b/src/preferences/appearance/APRView.h index 8aadbc87b1..df0044bf6c 100644 --- a/src/preferences/appearance/APRView.h +++ b/src/preferences/appearance/APRView.h @@ -13,6 +13,7 @@ #include +#include #include #include #include @@ -50,14 +51,18 @@ public: bool IsRevertable(); private: + void _CreateItems(); + void _UpdatePreviews(const BMessage& colors); + void _SetColor(color_which which, rgb_color color); + void _SetOneColor(color_which which, rgb_color color); void _SetCurrentColor(rgb_color color); void _SetUIColors(const BMessage& colors); - void _UpdatePreviews(const BMessage& colors); private: BColorControl* fPicker; + BCheckBox* fAutoSelectCheckBox; BListView* fAttrList; color_which fWhich; diff --git a/src/preferences/appearance/Jamfile b/src/preferences/appearance/Jamfile index 89015c047e..b097ec83e5 100644 --- a/src/preferences/appearance/Jamfile +++ b/src/preferences/appearance/Jamfile @@ -2,7 +2,7 @@ SubDir HAIKU_TOP src preferences appearance ; AddSubDirSupportedPlatforms libbe_test ; -UsePrivateHeaders app interface [ FDirName servers app ] ; +UsePrivateHeaders shared app interface [ FDirName servers app ] ; Preference Appearance : APRMain.cpp @@ -23,7 +23,7 @@ Preference Appearance : #CursorWhichItem.cpp #SysCursorAPI.cpp - : be [ TargetLibstdc++ ] [ TargetLibsupc++ ] localestub + : be [ TargetLibstdc++ ] [ TargetLibsupc++ ] localestub shared : Appearance.rdef ;