From 8109d90c1b917308fb725e132c181a898a026700 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 14 Jul 2023 10:08:25 +0200 Subject: [PATCH] Apply custom theme to FLTK Inspired by modern Windows appearance, and to some extent macOS. They have flat boxes and use white, or very light, colors for interactive elements. Unfortunately we can't directly control the colors of widgets, so instead we just lighten everything that uses this box type. GNOME uses a different design, both their older and newer style. But UI look is less consistent on Linux, so hopefully our new look is decent enough there as well. --- vncviewer/fltk/theme.cxx | 193 +++++++++++++++++++++++++++++++++++++-- vncviewer/fltk/theme.h | 18 +++- 2 files changed, 204 insertions(+), 7 deletions(-) diff --git a/vncviewer/fltk/theme.cxx b/vncviewer/fltk/theme.cxx index a5c0764e..1a6345b2 100644 --- a/vncviewer/fltk/theme.cxx +++ b/vncviewer/fltk/theme.cxx @@ -36,9 +36,161 @@ #include #include #include +#include #include "theme.h" +const int RADIUS = 4; + +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__) @@ -48,9 +200,8 @@ void init_theme() // Basic text size (10pt @ 96 dpi => 13px) FL_NORMAL_SIZE = 13; - // Select a FLTK scheme and background color that looks somewhat - // close to modern systems - Fl::scheme("gtk+"); + // Select a background color that looks somewhat close to modern + // systems Fl::background(220, 220, 220); // macOS has a slightly brighter default background though @@ -58,9 +209,39 @@ void init_theme() Fl::background(240, 240, 240); #endif - // This makes the "icon" in dialogs rounded, which fits better - // with the above schemes. - fl_message_icon()->box(FL_UP_BOX); + // 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; + + 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; diff --git a/vncviewer/fltk/theme.h b/vncviewer/fltk/theme.h index aa4b3586..8793bd7c 100644 --- a/vncviewer/fltk/theme.h +++ b/vncviewer/fltk/theme.h @@ -1,4 +1,4 @@ -/* Copyright 2022 Pierre Ossman for Cendio AB +/* Copyright 2023 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 @@ -24,6 +24,22 @@ #ifndef __FLTK_THEME_H__ #define __FLTK_THEME_H__ +#include + +#define _THEME_BOX_BASE (FL_FREE_BOXTYPE+1000) + +#define THEME_UP_FRAME (Fl_Boxtype)(_THEME_BOX_BASE+0) +#define THEME_DOWN_FRAME (Fl_Boxtype)(_THEME_BOX_BASE+1) +#define THEME_THIN_UP_FRAME (Fl_Boxtype)(_THEME_BOX_BASE+2) +#define THEME_THIN_DOWN_FRAME (Fl_Boxtype)(_THEME_BOX_BASE+3) + +#define THEME_UP_BOX (Fl_Boxtype)(_THEME_BOX_BASE+4) +#define THEME_DOWN_BOX (Fl_Boxtype)(_THEME_BOX_BASE+5) +#define THEME_THIN_UP_BOX (Fl_Boxtype)(_THEME_BOX_BASE+6) +#define THEME_THIN_DOWN_BOX (Fl_Boxtype)(_THEME_BOX_BASE+7) +#define THEME_ROUND_UP_BOX (Fl_Boxtype)(_THEME_BOX_BASE+8) +#define THEME_ROUND_DOWN_BOX (Fl_Boxtype)(_THEME_BOX_BASE+9) + void init_theme(); #endif -- 2.39.5