/* Copyright 2011-2022 Pierre Ossman for Cendio AB * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifdef HAVE_CONFIG_H #include #endif #include #ifdef WIN32 #include #endif #ifdef __APPLE__ #include #endif #include #include #include #include #include "theme.h" const int RADIUS = 4; /* * fl_arc() and fl_pie() are broken on Windows, so we need to fudge the * numbers a bit */ #ifdef WIN32 static void fixed_arc(int x,int y,int w,int h,double a1,double a2) { if ((a1 != 0.0) || (a2 != 360.0)) { a1 -= 10.0; a2 += 10.0; } fl_arc(x, y, w, h, a1, a2); } static void fixed_pie(int x,int y,int w,int h,double a1,double a2) { if ((a1 != 0.0) || (a2 != 360.0)) { a1 -= 10.0; a2 += 10.0; } fl_pie(x, y, w, h, a1, a2); } #define fl_arc fixed_arc #define fl_pie fixed_pie #endif static Fl_Color light_border(Fl_Color c) { return fl_color_average(FL_BLACK, c, 0.12); } static Fl_Color dark_border(Fl_Color c) { return fl_color_average(FL_BLACK, c, 0.33); } static void theme_round_frame(bool up, int x, int y, int w, int h, int r, Fl_Color c) { if (r > h/2) r = h/2; // Top if (up) Fl::set_box_color(light_border(c)); else Fl::set_box_color(dark_border(c)); fl_xyline(x+r, y, x+w-r-1); // Top corners if (!up) Fl::set_box_color(fl_color_average(light_border(c), dark_border(c), 0.5)); fl_arc(x, y, r*2, r*2, 90.0, 180.0); fl_arc(x+w-(r*2), y, r*2, r*2, 0, 90.0); // Left and right Fl::set_box_color(light_border(c)); fl_yxline(x, y+r, y+h-r-1); fl_yxline(x+w-1, y+r, y+h-r-1); // Bottom corners if (up) Fl::set_box_color(fl_color_average(light_border(c), dark_border(c), 0.5)); fl_arc(x, y+h-(r*2), r*2, r*2, 180, 270.0); fl_arc(x+w-(r*2), y+h-(r*2), r*2, r*2, 270, 360.0); // Bottom if (up) Fl::set_box_color(dark_border(c)); else Fl::set_box_color(light_border(c)); fl_xyline(x+r, y+h-1, x+w-r-1); } static void theme_up_frame(int x, int y, int w, int h, Fl_Color c) { theme_round_frame(true, x, y, w, h, RADIUS, c); } static void theme_down_frame(int x, int y, int w, int h, Fl_Color c) { theme_round_frame(false, x, y, w, h, RADIUS, c); } static void theme_round_rect(int x, int y, int w, int h, int r, Fl_Color c, Fl_Color c2=-1) { if (r > h/2) r = h/2; if (c2 == (Fl_Color)-1) c2 = c; // Top Fl::set_box_color(c); fl_pie(x, y, r*2, r*2, 90.0, 180.0); fl_rectf(x+r, y, w-(r*2), r); fl_pie(x+w-(r*2), y, r*2, r*2, 0, 90.0); // Middle const int steps = h - r*2; for (int i = 0; i < steps; i++) { Fl::set_box_color(fl_color_average(c2, c, (double)i/steps)); fl_xyline(x, y+r+i, x+w-1); } // Bottom Fl::set_box_color(c2); fl_pie(x, y+h-(r*2), r*2, r*2, 180, 270.0); fl_rectf(x+r, y+h-r, w-(r*2), r); fl_pie(x+w-(r*2), y+h-(r*2), r*2, r*2, 270, 360.0); } static void theme_up_box(int x, int y, int w, int h, Fl_Color c) { theme_round_rect(x, y, w, h, RADIUS, fl_color_average(FL_WHITE, c, 0.75)); theme_up_frame(x, y, w, h, c); } static void theme_down_box(int x, int y, int w, int h, Fl_Color c) { theme_round_rect(x, y, w, h, RADIUS, c); theme_down_frame(x, y, w, h, c); } static void theme_thin_up_box(int x, int y, int w, int h, Fl_Color c) { theme_round_rect(x, y, w, h, RADIUS, c); theme_up_frame(x, y, w, h, c); } static void theme_thin_down_box(int x, int y, int w, int h, Fl_Color c) { theme_round_rect(x, y, w, h, RADIUS, c); theme_down_frame(x, y, w, h, c); } static void theme_round_up_box(int x, int y, int w, int h, Fl_Color c) { // Background Fl::set_box_color(c); fl_pie(x, y, w, h, 0.0, 360.0); // Outline Fl::set_box_color(light_border(c)); fl_arc(x, y, w, h, 0.0, 180.0); Fl::set_box_color(dark_border(c)); fl_arc(x, y, w, h, 180.0, 360.0); Fl::set_box_color(fl_color_average(light_border(c), dark_border(c), 0.5)); fl_arc(x, y, w, h, 225.0, 315.0); } static void theme_round_down_box(int x, int y, int w, int h, Fl_Color c) { // Background Fl::set_box_color(c); fl_pie(x, y, w, h, 0.0, 360.0); // Outline Fl::set_box_color(fl_color_average(light_border(c), dark_border(c), 0.5)); fl_arc(x, y, w, h, 0.0, 180.0); Fl::set_box_color(dark_border(c)); fl_arc(x, y, w, h, 45.0, 135.0); Fl::set_box_color(light_border(c)); fl_arc(x, y, w, h, 180.0, 360.0); } void init_theme() { #if defined(WIN32) || defined(__APPLE__) static char font_name[256]; #endif // Basic text size (10pt @ 96 dpi => 13px) FL_NORMAL_SIZE = 13; // Modern colors based on the default appearance of Windows 11, // macOS 12 and GNOME 41 // FIXME: Should get these from the system, // Fl::get_system_colors() is unfortunately not very capable // FIXME: Should also handle dark mode #if defined(WIN32) // Windows 11 Fl::foreground(26, 26, 26); Fl::background(243, 243, 243); #elif defined(__APPLE__) // FIXME: Text is rendered slightly lighter than what we specify here // for some odd reason. The target is (38, 38, 38). Fl::foreground(28, 28, 28); Fl::background(246, 246, 246); #else // GNOME Fl::foreground(46, 52, 54); Fl::background(246, 245, 244); #endif #if defined(WIN32) // Windows 11 default accent color Fl::set_color(FL_SELECTION_COLOR, 0, 103, 192); #elif defined(__APPLE__) Fl::set_color(FL_SELECTION_COLOR, 0, 122, 255); #else // GNOME Fl::set_color(FL_SELECTION_COLOR, 53, 132, 228); #endif // The arrow on Fl_Return_Button gets a invisible, so let's adjust it // to compensate for our lighter buttons Fl::set_color(FL_LIGHT3, light_border(fl_color_average(FL_WHITE, FL_BACKGROUND_COLOR, 0.5))); Fl::set_color(FL_DARK3, dark_border(fl_color_average(FL_WHITE, FL_BACKGROUND_COLOR, 0.5))); // We will override the box types later, but changing scheme affects // more things than just those, so we still want to switch from the // default Fl::scheme("gtk+"); // Define our box types const int PX = 2; const int PY = 2; // FLTK lacks a bounds check assert(THEME_ROUND_DOWN_BOX < 256); Fl::set_boxtype(THEME_UP_FRAME, theme_up_frame, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_DOWN_FRAME, theme_down_frame, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_THIN_UP_FRAME, theme_up_frame, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_THIN_DOWN_FRAME, theme_down_frame, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_UP_BOX, theme_up_box, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_DOWN_BOX, theme_down_box, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_THIN_UP_BOX, theme_thin_up_box, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_THIN_DOWN_BOX, theme_thin_down_box, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_ROUND_UP_BOX, theme_round_up_box, PX, PY, PX*2, PY*2); Fl::set_boxtype(THEME_ROUND_DOWN_BOX, theme_round_down_box, PX, PY, PX*2, PY*2); // Make them the default Fl::set_boxtype(FL_UP_FRAME, THEME_UP_FRAME); Fl::set_boxtype(FL_DOWN_FRAME, THEME_UP_FRAME); Fl::set_boxtype(FL_THIN_UP_FRAME, THEME_THIN_UP_FRAME); Fl::set_boxtype(FL_THIN_DOWN_FRAME, THEME_THIN_DOWN_FRAME); Fl::set_boxtype(FL_UP_BOX, THEME_UP_BOX); Fl::set_boxtype(FL_DOWN_BOX, THEME_DOWN_BOX); Fl::set_boxtype(FL_THIN_UP_BOX, THEME_THIN_UP_BOX); Fl::set_boxtype(FL_THIN_DOWN_BOX, THEME_THIN_DOWN_BOX); Fl::set_boxtype(_FL_ROUND_UP_BOX, THEME_ROUND_UP_BOX); Fl::set_boxtype(_FL_ROUND_DOWN_BOX, THEME_ROUND_DOWN_BOX); #if defined(WIN32) NONCLIENTMETRICS metrics; metrics.cbSize = sizeof(metrics); if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(metrics), &metrics, 0)) { strcpy(font_name, metrics.lfMessageFont.lfFaceName); Fl::set_font(FL_HELVETICA, font_name); } #elif defined(__APPLE__) CTFontRef font; CFStringRef name; font = CTFontCreateUIFontForLanguage(kCTFontSystemFontType, 0.0, NULL); if (font != NULL) { name = CTFontCopyFullName(font); if (name != NULL) { CFStringGetCString(name, font_name, sizeof(font_name), kCFStringEncodingUTF8); Fl::set_font(FL_HELVETICA, font_name); CFRelease(name); } CFRelease(font); } #else // FIXME: Get font from GTK or QT #endif }