* Copyright 2002-2025 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* DarkWyrm, darkwyrm@earthlink.net
* Rene Gollent, rene@gollent.com
* John Scipione, jscipione@gmail.com
* Joseph Groover <looncraz@looncraz.net>
*/
#include <stdio.h>
#include <Alert.h>
#include <Catalog.h>
#include <ColorItem.h>
#include <ColorListView.h>
#include <DefaultColors.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <HSL.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <Messenger.h>
#include <Path.h>
#include <SpaceLayoutItem.h>
#include "AppearanceWindow.h"
#include "Colors.h"
#include "ColorsView.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Colors tab"
#define COLOR_DROPPED 'cldp'
#define AUTO_ADJUST_CHANGED 'madj'
#define UPDATE_COLOR 'upcl'
#define ATTRIBUTE_CHOSEN 'atch'
using BPrivate::BColorItem;
using BPrivate::BColorListView;
using BPrivate::BColorPreview;
ColorsView::ColorsView(const char* name)
:
BView(name, B_WILL_DRAW)
{
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
LoadSettings();
fAutoSelectCheckBox = new BCheckBox(B_TRANSLATE("Automatically pick secondary colors"),
new BMessage(AUTO_ADJUST_CHANGED));
fAutoSelectCheckBox->SetValue(true);
fAttrList = new BColorListView("AttributeList");
fScrollView = new BScrollView("ScrollView", fAttrList, 0, false, true);
fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
_CreateItems();
fColorPreview = new BColorPreview("color preview", "", new BMessage(COLOR_DROPPED));
fPicker = new BColorControl(B_ORIGIN, B_CELLS_32x8, 8.0,
"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)
.AddGlue()
.Add(fPicker)
.End()
.SetInsets(B_USE_WINDOW_SPACING);
fColorPreview->Parent()->SetExplicitMaxSize(BSize(B_SIZE_UNSET, fPicker->Bounds().Height()));
fAttrList->SetSelectionMessage(new BMessage(ATTRIBUTE_CHOSEN));
}
ColorsView::~ColorsView()
{
}
void
ColorsView::AttachedToWindow()
{
fAutoSelectCheckBox->SetTarget(this);
fPicker->SetTarget(this);
fAttrList->SetTarget(this);
fColorPreview->SetTarget(this);
fAttrList->Select(0);
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
}
void
ColorsView::MessageReceived(BMessage* message)
{
if (message->WasDropped()) {
char* name;
type_code type;
rgb_color* color;
ssize_t size;
if (message->GetInfo(B_RGB_COLOR_TYPE, 0, &name, &type) == B_OK
&& message->FindData(name, type, (const void**)&color, &size) == B_OK) {
BPoint dropLoc = message->DropPoint();
if (fAttrList->Bounds().Contains(fAttrList->ConvertFromScreen(dropLoc))) {
int32 index = fAttrList->IndexOf(fAttrList->ConvertFromScreen(dropLoc));
bool selected = index == fAttrList->CurrentSelection();
if (index < 0 || index >= fAttrList->CountItems() || selected)
_SetCurrentColor(*color);
else
_SetColor(index, *color);
Window()->PostMessage(kMsgUpdate);
} else if (fColorPreview->Bounds().Contains(fColorPreview->ConvertFromScreen(dropLoc))
|| fPicker->Bounds().Contains(fPicker->ConvertFromScreen(dropLoc))) {
_SetCurrentColor(*color);
Window()->PostMessage(kMsgUpdate);
}
}
}
switch (message->what) {
case UPDATE_COLOR:
{
rgb_color color = fPicker->ValueAsColor();
_SetCurrentColor(color);
Window()->PostMessage(kMsgUpdate);
break;
}
case ATTRIBUTE_CHOSEN:
{
int32 index = fAttrList->CurrentSelection();
if (index < 0 || index >= fAttrList->CountItems())
break;
BColorItem* item = dynamic_cast<BColorItem*>(fAttrList->ItemAt(index));
if (item != NULL) {
fWhich = item->ColorWhich();
rgb_color color = ui_color(fWhich);
_SetCurrentColor(color);
}
break;
}
case AUTO_ADJUST_CHANGED:
{
_CreateItems();
break;
}
default:
BView::MessageReceived(message);
break;
}
}
void
ColorsView::LoadSettings()
{
get_default_colors(&fDefaultColors);
get_current_colors(&fCurrentColors);
fPrevColors = fCurrentColors;
}
void
ColorsView::SetDefaults()
{
_SetUIColors(fDefaultColors);
_UpdatePreviews(fDefaultColors);
rgb_color color = fDefaultColors.GetColor(ui_color_name(fWhich),
make_color(255, 0, 255));
fPicker->SetValue(color);
fColorPreview->SetColor(color);
fColorPreview->Invalidate();
Window()->PostMessage(kMsgUpdate);
}
void
ColorsView::Revert()
{
_SetUIColors(fPrevColors);
_UpdatePreviews(fPrevColors);
rgb_color color = fPrevColors.GetColor(ui_color_name(fWhich), make_color(255, 0, 255));
fPicker->SetValue(color);
fColorPreview->SetColor(color);
fColorPreview->Invalidate();
Window()->PostMessage(kMsgUpdate);
}
bool
ColorsView::IsDefaultable()
{
return !fDefaultColors.HasSameData(fCurrentColors);
}
bool
ColorsView::IsRevertable()
{
return !fPrevColors.HasSameData(fCurrentColors);
}
void
ColorsView::_CreateItems()
{
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 BColorItem(text, which, ui_color(which)));
}
if (Window() != NULL)
fAttrList->Select(0);
}
void
ColorsView::_UpdatePreviews(const BMessage& colors)
{
rgb_color color;
for (int32 i = color_description_count() - 1; i >= 0; i--) {
BColorItem* item = static_cast<BColorItem*>(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
ColorsView::_SetUIColors(const BMessage& colors)
{
set_ui_colors(&colors);
fCurrentColors = colors;
}
void
ColorsView::_SetCurrentColor(rgb_color color)
{
_SetColor(fAttrList->CurrentSelection(), color);
fPicker->SetValue(color);
fColorPreview->SetColor(color);
}
void
ColorsView::_SetColor(int32 index, rgb_color color)
{
BColorItem* item = dynamic_cast<BColorItem*>(fAttrList->ItemAt(index));
if (item != NULL) {
item->SetColor(color);
fAttrList->InvalidateItem(index);
_SetColor(item->ColorWhich(), color);
}
}
void
ColorsView::_SetColor(color_which which, rgb_color color)
{
fCurrentColors.SetColor(ui_color_name(which), color);
if (!fAutoSelectCheckBox->Value()) {
if (ui_color(which) != color)
set_ui_color(which, color);
return;
}
if (ui_color(which) == color)
return;
if (which == B_PANEL_BACKGROUND_COLOR) {
const bool isDark = color.IsDark();
fCurrentColors.SetColor(ui_color_name(B_MENU_BACKGROUND_COLOR), color);
const rgb_color menuSelectedBackground
= tint_color(color, isDark ? 0.8 : B_DARKEN_2_TINT);
fCurrentColors.SetColor(ui_color_name(B_MENU_SELECTED_BACKGROUND_COLOR),
menuSelectedBackground);
const rgb_color controlBackground = tint_color(color,
0.84 );
fCurrentColors.SetColor(ui_color_name(B_CONTROL_BACKGROUND_COLOR), controlBackground);
fCurrentColors.SetColor(ui_color_name(B_SCROLL_BAR_THUMB_COLOR), controlBackground);
const rgb_color controlBorder
= tint_color(color, isDark ? 0.4875 : 1.20 );
fCurrentColors.SetColor(ui_color_name(B_CONTROL_BORDER_COLOR), controlBorder);
const rgb_color windowBorder = tint_color(color, 0.75);
fCurrentColors.SetColor(ui_color_name(B_WINDOW_BORDER_COLOR), windowBorder);
const rgb_color inactiveWindowBorder = tint_color(color, B_LIGHTEN_1_TINT);
fCurrentColors.SetColor(ui_color_name(B_WINDOW_INACTIVE_TAB_COLOR),
inactiveWindowBorder);
fCurrentColors.SetColor(ui_color_name(B_WINDOW_INACTIVE_BORDER_COLOR),
inactiveWindowBorder);
const rgb_color listSelectedBackground
= tint_color(color, isDark ? 0.77 : 1.12 );
fCurrentColors.SetColor(ui_color_name(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++)
fCurrentColors.SetColor(ui_color_name(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);
fCurrentColors.SetColor(ui_color_name(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);
fCurrentColors.SetColor(ui_color_name(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();
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;
}
fCurrentColors.SetColor(ui_color_name(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;
fCurrentColors.SetColor(ui_color_name(B_WINDOW_TEXT_COLOR),
BPrivate::GetSystemColor(B_WINDOW_TEXT_COLOR, isDark));
fCurrentColors.SetColor(ui_color_name(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);
fCurrentColors.SetColor(ui_color_name(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);
fCurrentColors.SetColor(ui_color_name(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);
fCurrentColors.SetColor(ui_color_name(B_FAILURE_COLOR), failure.to_rgb());
}
set_ui_colors(&fCurrentColors);
}