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 ;