Initial framework for dynamic color support

- Fl_Dynamic_Color enum
- Fl::dynamic_color() APIs
- Fl_System_Driver::dynamic_color() API
- FL_DYNAMIC_COLOR_CHANGED event

- Add method for querying dark mode to macOS system and screen drivers.
- Check/update mode whenever we get a layout change from macOS.

- Add detection of dark mode on macOS by using the effectiveAppearance
  property.

- Force redraw of all windows when changing the mode.

- Update Cocoa set_system_colors method to override colors when dynamic
  mode isn't off.

- Add (temporary) support for FL_DYNAMIC_COLOR environment variable to
  override default "off" mode for testing.

To do: add dark mode detection on other platforms than macOS.

Note: Branch 'darkmode' rebased, squashed, and extended
  by Albrecht-S on Dec 17, 2023.
This commit is contained in:
Michael R Sweet 2021-03-24 09:00:09 -04:00 committed by Albrecht Schlosser
parent 469d3ef3d5
commit 4ab861aae2
9 changed files with 144 additions and 9 deletions

View File

@ -407,7 +407,11 @@ enum Fl_Event { // events
/** A zoom event (ctrl/+/-/0/ or cmd/+/-/0/) was processed. /** A zoom event (ctrl/+/-/0/ or cmd/+/-/0/) was processed.
Use Fl::add_handler() to be notified of this event. Use Fl::add_handler() to be notified of this event.
*/ */
FL_ZOOM_EVENT = 27 FL_ZOOM_EVENT = 27,
/** The current dynamic color mode has changed.
Call Fl::dynamic_color() to get the new mode.
*/
FL_DYNAMIC_COLOR_CHANGED = 28
// DEV NOTE: Keep this list in sync with FL/names.h // DEV NOTE: Keep this list in sync with FL/names.h
}; };
@ -1380,4 +1384,26 @@ enum Fl_Orientation {
FL_ORIENT_SE = 0x07 ///< GUI element pointing SE (315°) FL_ORIENT_SE = 0x07 ///< GUI element pointing SE (315°)
}; };
/**
Modern operating systems provide a pair of color themes suitable for use in
light and dark ambient lighting. Automatic switching is normally provided
either using the current time of day or with ambient lighting sensors.
This enumeration lists the values for the Fl::dynamic_color() API, allowing
applications to opt in to automatic color theme switching or specify that
color switching is not desired.
\see Fl::dynamic_color()
*/
enum Fl_Dynamic_Color { // dynamic color values
/** Dynamic color themes are off/not supported/disabled */
FL_DYNAMIC_COLOR_OFF,
/** Automatically change color themes */
FL_DYNAMIC_COLOR_AUTO,
/** Use the light color theme */
FL_DYNAMIC_COLOR_LIGHT,
/** Use the dark color theme */
FL_DYNAMIC_COLOR_DARK
};
#endif #endif

23
FL/Fl.H
View File

@ -148,6 +148,8 @@ private:
public: public:
static Fl_Dynamic_Color dynamic_color_;
static Fl_Screen_Driver *screen_driver(); static Fl_Screen_Driver *screen_driver();
static Fl_System_Driver *system_driver(); static Fl_System_Driver *system_driver();
#ifdef __APPLE__ // deprecated in 1.4 - only for compatibility with 1.3 #ifdef __APPLE__ // deprecated in 1.4 - only for compatibility with 1.3
@ -392,6 +394,27 @@ public:
static void background(uchar, uchar, uchar); static void background(uchar, uchar, uchar);
static void background2(uchar, uchar, uchar); static void background2(uchar, uchar, uchar);
// dynamic color:
/** Set the dynamic color theme mode.
Most applications should just call Fl::dynamic_color(FL_DYNAMIC_COLOR_AUTO)
to use the user's preferred color theme. The default value is
FL_DYNAMIC_COLOR_OFF for compatibility with prior versions of FLTK.
When set to FL_DYNAMIC_COLOR_AUTO, FLTK will monitor for appearance/theme
changes. When the color theme changes, a FL_DYNAMIC_COLOR_CHANGED event is
sent to allow widgets to update their colors as needed.
*/
static void dynamic_color(Fl_Dynamic_Color mode);
/** Gets the current dynamic color theme.
Returns FL_DYNAMIC_COLOR_OFF if dynamic color themes are off or disabled
(the default), FL_DYNAMIC_COLOR_LIGHT if the current color theme is for use
in bright ambient light, or FL_DYNAMIC_COLOR_DARK is the current color
theme is for use in dark ambient light.
*/
static Fl_Dynamic_Color dynamic_color();
// schemes: // schemes:
static int scheme(const char *name); static int scheme(const char *name);
/** See void scheme(const char *name) */ /** See void scheme(const char *name) */

View File

@ -73,7 +73,7 @@ const char * const fl_eventnames[] =
"FL_FULLSCREEN", "FL_FULLSCREEN",
"FL_ZOOM_GESTURE", "FL_ZOOM_GESTURE",
"FL_ZOOM_EVENT", "FL_ZOOM_EVENT",
"FL_EVENT_28", // not yet defined, just in case it /will/ be defined ... "FL_DYNAMIC_COLOR_CHANGED",
"FL_EVENT_29", // not yet defined, just in case it /will/ be defined ... "FL_EVENT_29", // not yet defined, just in case it /will/ be defined ...
"FL_EVENT_30" // not yet defined, just in case it /will/ be defined ... "FL_EVENT_30" // not yet defined, just in case it /will/ be defined ...
}; };

View File

@ -222,6 +222,7 @@ public:
virtual const char *alt_name() { return "Alt"; } virtual const char *alt_name() { return "Alt"; }
virtual const char *control_name() { return "Ctrl"; } virtual const char *control_name() { return "Ctrl"; }
virtual Fl_Sys_Menu_Bar_Driver *sys_menu_bar_driver() { return NULL; } virtual Fl_Sys_Menu_Bar_Driver *sys_menu_bar_driver() { return NULL; }
virtual Fl_Dynamic_Color dynamic_color() { return FL_DYNAMIC_COLOR_LIGHT; }
virtual void lock_ring() {} virtual void lock_ring() {}
virtual void unlock_ring() {} virtual void unlock_ring() {}
virtual double wait(double); // must FL_OVERRIDE virtual double wait(double); // must FL_OVERRIDE

View File

@ -580,6 +580,7 @@ void Fl_Cocoa_Screen_Driver::breakMacEventLoop()
- (BOOL)process_keydown:(NSEvent*)theEvent; - (BOOL)process_keydown:(NSEvent*)theEvent;
- (id)initWithFrame:(NSRect)frameRect; - (id)initWithFrame:(NSRect)frameRect;
- (void)drawRect:(NSRect)rect; - (void)drawRect:(NSRect)rect;
- (void)layout;
- (BOOL)acceptsFirstResponder; - (BOOL)acceptsFirstResponder;
- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent; - (BOOL)acceptsFirstMouse:(NSEvent*)theEvent;
- (void)resetCursorRects; - (void)resetCursorRects;
@ -2415,6 +2416,38 @@ static FLTextInputContext* fltextinputcontext_instance = nil;
fl_unlock_function(); fl_unlock_function();
} }
- (void)layout
{
// Check to see whether the system appearance has changed...
if (Fl::dynamic_color_ == FL_DYNAMIC_COLOR_AUTO) {
Fl_Dynamic_Color mode = FL_DYNAMIC_COLOR_LIGHT;
if (@available(macOS 10.14, *)) {
NSAppearanceName temp = [self.effectiveAppearance bestMatchFromAppearancesWithNames:@[
NSAppearanceNameAqua,
NSAppearanceNameDarkAqua
]];
if ([temp isEqualToString:NSAppearanceNameDarkAqua])
mode = FL_DYNAMIC_COLOR_DARK;
}
Fl_Darwin_System_Driver *s = (Fl_Darwin_System_Driver *)Fl::system_driver();
if (s->dynamic_color() != mode) {
// Change the color mode and force all windows to redraw...
s->dynamic_color(mode);
Fl::get_system_colors();
Fl_Window *window;
for (window = Fl::first_window(); window; window = Fl::next_window(window))
window->redraw();
}
}
[super layout];
}
- (BOOL)acceptsFirstResponder - (BOOL)acceptsFirstResponder
{ {
return [[self window] parentWindow] ? NO : YES; // 10.2 return [[self window] parentWindow] ? NO : YES; // 10.2

View File

@ -106,6 +106,27 @@ void Fl::get_system_colors()
Fl::screen_driver()->get_system_colors(); Fl::screen_driver()->get_system_colors();
} }
#ifndef FL_DOXYGEN
Fl_Dynamic_Color Fl::dynamic_color_ = FL_DYNAMIC_COLOR_OFF;
#endif // !FL_DOXYGEN
void Fl::dynamic_color(Fl_Dynamic_Color mode)
{
dynamic_color_ = mode;
}
Fl_Dynamic_Color Fl::dynamic_color()
{
// TODO: Remove this
if (getenv("FL_DYNAMIC_COLOR") && dynamic_color_ == FL_DYNAMIC_COLOR_OFF)
dynamic_color_ = FL_DYNAMIC_COLOR_AUTO;
if (dynamic_color_ == FL_DYNAMIC_COLOR_AUTO)
return Fl::system_driver()->dynamic_color();
else
return dynamic_color_;
}
//// Simple implementation of 2.0 Fl::scheme() interface... //// Simple implementation of 2.0 Fl::scheme() interface...
#define D1 BORDER_WIDTH #define D1 BORDER_WIDTH

View File

@ -201,7 +201,11 @@ static void set_selection_color(uchar r, uchar g, uchar b)
} }
// MacOS X currently supports two color schemes - Blue and Graphite. // macOS supports two basic color schemes - Light and Dark - with accent and
// highlight colors. Older versions limited the accent colors to Blue and
// Graphite but now you can pick from a rainbow of colors plus the old Graphite
// gray.
//
// Since we aren't emulating the Aqua interface (even if Apple would // Since we aren't emulating the Aqua interface (even if Apple would
// let us), we use some defaults that are similar to both. The // let us), we use some defaults that are similar to both. The
// Fl::scheme("plastic") color/box scheme provides a usable Aqua-like // Fl::scheme("plastic") color/box scheme provides a usable Aqua-like
@ -212,13 +216,30 @@ void Fl_Cocoa_Screen_Driver::get_system_colors()
Fl_Screen_Driver::get_system_colors(); Fl_Screen_Driver::get_system_colors();
if (!bg2_set) Fl::background2(0xff, 0xff, 0xff); Fl_Dynamic_Color mode = Fl::dynamic_color();
if (!fg_set) Fl::foreground(0, 0, 0);
if (!bg_set) Fl::background(0xd8, 0xd8, 0xd8); if (!bg2_set || mode != FL_DYNAMIC_COLOR_OFF) {
if (mode == FL_DYNAMIC_COLOR_DARK)
Fl::background2(23, 23, 23);
else
Fl::background2(0xff, 0xff, 0xff);
}
if (!fg_set || mode != FL_DYNAMIC_COLOR_OFF) {
if (mode == FL_DYNAMIC_COLOR_DARK)
Fl::foreground(223, 223, 223);
else
Fl::foreground(0, 0, 0);
}
if (!bg_set || mode != FL_DYNAMIC_COLOR_OFF) {
if (mode == FL_DYNAMIC_COLOR_DARK)
Fl::background(50, 50, 50);
else
Fl::background(0xd8, 0xd8, 0xd8);
}
#if 0 #if 0
// this would be the correct code, but it does not run on all versions // this would be the correct code, but it does not run on all versions
// of OS X. Also, setting a bright selection color would require // of macOS. Also, setting a bright selection color would require
// some updates in Fl_Adjuster and Fl_Help_Browser // some updates in Fl_Adjuster and Fl_Help_Browser
OSStatus err; OSStatus err;
RGBColor c; RGBColor c;
@ -229,6 +250,7 @@ void Fl_Cocoa_Screen_Driver::get_system_colors()
set_selection_color(c.red, c.green, c.blue); set_selection_color(c.red, c.green, c.blue);
#else #else
set_selection_color(0x00, 0x00, 0x80); set_selection_color(0x00, 0x00, 0x80);
//set_selection_color(0, 87, 207);
#endif #endif
} }

View File

@ -40,6 +40,8 @@
class Fl_Darwin_System_Driver : public Fl_Posix_System_Driver class Fl_Darwin_System_Driver : public Fl_Posix_System_Driver
{ {
Fl_Dynamic_Color dynamic_color_;
public: public:
Fl_Darwin_System_Driver(); Fl_Darwin_System_Driver();
int single_arg(const char *arg) FL_OVERRIDE; int single_arg(const char *arg) FL_OVERRIDE;
@ -82,6 +84,8 @@ public:
Fl_Sys_Menu_Bar_Driver *sys_menu_bar_driver() FL_OVERRIDE; Fl_Sys_Menu_Bar_Driver *sys_menu_bar_driver() FL_OVERRIDE;
double wait(double time_to_wait) FL_OVERRIDE; double wait(double time_to_wait) FL_OVERRIDE;
int ready() FL_OVERRIDE; int ready() FL_OVERRIDE;
Fl_Dynamic_Color dynamic_color() FL_OVERRIDE;
void dynamic_color(Fl_Dynamic_Color mode) { dynamic_color_ = mode; }
}; };
#endif // FL_DARWIN_SYSTEM_DRIVER_H #endif // FL_DARWIN_SYSTEM_DRIVER_H

View File

@ -51,8 +51,9 @@ const char *Fl_Darwin_System_Driver::control_name() {
Fl_Darwin_System_Driver::Fl_Darwin_System_Driver() : Fl_Posix_System_Driver() { Fl_Darwin_System_Driver::Fl_Darwin_System_Driver() : Fl_Posix_System_Driver() {
if (fl_mac_os_version == 0) fl_mac_os_version = calc_mac_os_version(); if (fl_mac_os_version == 0) fl_mac_os_version = calc_mac_os_version();
command_key = FL_META; command_key = FL_META;
control_key = FL_CTRL; control_key = FL_CTRL;
dynamic_color_ = FL_DYNAMIC_COLOR_LIGHT;
} }
int Fl_Darwin_System_Driver::single_arg(const char *arg) { int Fl_Darwin_System_Driver::single_arg(const char *arg) {
@ -404,3 +405,7 @@ Fl_Pixmap *Fl_Darwin_System_Driver::tree_closepixmap() {
int Fl_Darwin_System_Driver::tree_connector_style() { int Fl_Darwin_System_Driver::tree_connector_style() {
return FL_TREE_CONNECTOR_NONE; return FL_TREE_CONNECTOR_NONE;
} }
Fl_Dynamic_Color Fl_Darwin_System_Driver::dynamic_color() {
return dynamic_color_;
}