From b2744e16897496e7292deba6ba147195dcfd123f Mon Sep 17 00:00:00 2001 From: Mirek Fidler Date: Thu, 16 Jul 2026 10:23:22 +0200 Subject: [PATCH] GetSingleColor --- uppsrc/CtrlLib/Ch.cpp | 6 + uppsrc/CtrlLib/Ch.h | 2 + uppsrc/CtrlLib/ChGtk3.cpp | 1196 ++++++++++++++++++------------------- uppsrc/Draw/ImageChOp.cpp | 8 + uppsrc/Draw/ImageOp.h | 1 + 5 files changed, 615 insertions(+), 598 deletions(-) diff --git a/uppsrc/CtrlLib/Ch.cpp b/uppsrc/CtrlLib/Ch.cpp index 1b624b643..dab780449 100644 --- a/uppsrc/CtrlLib/Ch.cpp +++ b/uppsrc/CtrlLib/Ch.cpp @@ -349,6 +349,12 @@ Image Hot3(const Image& m) return WithHotSpots(m, sz.cx / 3, sz.cy / 3, sz.cx - sz.cx / 3, sz.cy - sz.cy / 3); } +Value ImageOrColor(const Image& m) +{ + Color c = GetSingleColor(m); + return IsNull(c) ? (Value)m : (Value)c; +} + Image ChHot(const Image& m, int n) { return WithHotSpots(m, DPI(n), DPI(n), 0, 0); diff --git a/uppsrc/CtrlLib/Ch.h b/uppsrc/CtrlLib/Ch.h index 8d15b2abb..20c5dfe0d 100644 --- a/uppsrc/CtrlLib/Ch.h +++ b/uppsrc/CtrlLib/Ch.h @@ -40,6 +40,8 @@ Color GetInk(const Image& m); // the color that is most different from AvgColor int GetRoundness(const Image& m); void FixButton(Image& button); // fix button if it is too close to face color +Value ImageOrColor(const Image& m); + Image WithRect(Image m, int x, int y, int cx, int cy, Color c); Image WithLeftLine(const Image& m, Color c, int w = DPI(1)); Image WithRightLine(const Image& m, Color c, int w = DPI(1)); diff --git a/uppsrc/CtrlLib/ChGtk3.cpp b/uppsrc/CtrlLib/ChGtk3.cpp index bdab0cb7e..23cf3a8f4 100644 --- a/uppsrc/CtrlLib/ChGtk3.cpp +++ b/uppsrc/CtrlLib/ChGtk3.cpp @@ -1,598 +1,598 @@ -#include "CtrlLib.h" - -#ifdef GUI_GTK - -namespace Upp { - -int GtkSettingsInt(const char *name) -{ - gint h = Null; - g_object_get(gtk_settings_get_default(), name, &h, NULL); - return h; -} - -int GtkSettingsBool(const char *name) -{ - gboolean h = false; - g_object_get(gtk_settings_get_default(), name, &h, NULL); - return h; -} - -String GtkSettingsString(const char *name) -{ - const char *h = ""; - g_object_get(gtk_settings_get_default(), name, &h, NULL); - return h; -} - -void SetupFont() -{ - int fontface = Font::ARIAL; - int fontheight = 13; - bool bold = false; - bool italic = false; - - String font_name = GtkSettingsString("gtk-font-name"); - - double xdpi = gdk_screen_get_resolution(gdk_display_get_default_screen(gdk_display_get_default())); - - const char *q = strrchr(font_name, ' '); - if(q) { - int h = atoi(q); - if(h) - fontheight = h; - String face(font_name, q); - fontface = Font::FindFaceNameIndex(face); - - if(fontface == 0) { - for(;;) { - const char *q = strrchr(face, ' '); - if(!q) break; - const char *s = q + 1; - if(stricmp(s, "Bold") == 0 || stricmp(s, "Heavy") == 0) - bold = true; - else - if(stricmp(s, "Italic") == 0 || stricmp(s, "Oblique") == 0) - italic = true; - else - if(stricmp(s, "Regular") == 0 || stricmp(s, "Light") || stricmp(s, "Medium")) - ; - else - continue; - face = String(~face, q); - } - fontface = Font::FindFaceNameIndex(face); - if(fontface == 0) { - if(ToUpper(face[0]) == 'M') - fontface = Font::COURIER; - else - if(ToUpper(face[0]) == 'S' && ToUpper(face[1]) == 'e') - fontface = Font::ROMAN; - else - fontface = Font::ARIAL; - } - } - } - Font gui_font = Font(fontface, fround(Ctrl::SCL(fontheight) * xdpi / 72)).Bold(bold).Italic(italic); - Font::SetDefaultFont(gui_font); -} - -static Image sCurrentImage; -static GtkBorder sMargin; - -Image CairoImage(int cx, int cy, Event draw) -{ - Image m[2]; - for(int i = 0; i < 2; i++) { - ImageDraw iw(cx, cy); - iw.DrawRect(0, 0, cx, cy, i ? Black() : White()); - cairo_t *cr = iw; -#if GTK_CHECK_VERSION(3, 20, 0) - cairo_surface_set_device_scale(cairo_get_target(cr), Ctrl::SCL(1), Ctrl::SCL(1)); -#endif - draw(cr); - m[i] = iw; - } - sCurrentImage = RecreateAlpha(m[0], m[1]); - return sCurrentImage; -} - -Image CairoImage(GtkStyleContext *ctx, int cx = 40, int cy = 32) -{ - return CairoImage(cx, cy, [&](cairo_t *cr) { - double sx = cx / Ctrl::SCL(1); - double sy = cy / Ctrl::SCL(1); - gtk_render_background(ctx, cr, 0, 0, sx, sy); - gtk_render_frame(ctx, cr, 0, 0, sx, sy); - }); -} - -Color GetInkColor(GtkStyleContext *ctx, dword flags) -{ - GdkRGBA color; - gtk_style_context_get_color(ctx, (GtkStateFlags)flags, &color); - RGBA rgba; - rgba.r = int(255 * color.red); - rgba.g = int(255 * color.green); - rgba.b = int(255 * color.blue); - rgba.a = int(255 * color.alpha); - rgba = Premultiply(rgba); - RGBA t = SColorPaper(); - AlphaBlend(&t, &rgba, 1); - return t; -} - -#if GTK_CHECK_VERSION(3, 20, 0) - -static GtkStyleContext *sCtx = NULL; -static GtkStateFlags sFlags; - -void Gtk_Free() -{ - if(sCtx) - g_object_ref_sink(sCtx); - sCtx = NULL; -} - -void Gtk_State(int state, dword flags = 0) -{ - sFlags = static_cast(decode(state, CTRL_HOT, GTK_STATE_FLAG_PRELIGHT, - CTRL_PRESSED, GTK_STATE_FLAG_ACTIVE, - CTRL_DISABLED, GTK_STATE_FLAG_INSENSITIVE, - GTK_STATE_FLAG_NORMAL) | flags) ; - gtk_style_context_set_state(sCtx, sFlags); -} - -static Size sCurrentSize; - -void Gtk_New(const char *name, int state = 0, dword flags = 0) -{ - Gtk_Free(); - sCtx = NULL; - for(const String& element : Split(name, ' ')) { - GtkWidgetPath *path = sCtx ? gtk_widget_path_copy(gtk_style_context_get_path(sCtx)) - : gtk_widget_path_new(); - Vector s = Split(element, '.'); - if(s.GetCount()) { - gtk_widget_path_append_type(path, G_TYPE_NONE); - gtk_widget_path_iter_set_object_name(path, -1, s[0]); - } - for(int i = 1; i < s.GetCount(); i++) - gtk_widget_path_iter_add_class(path, -1, s[i]); - - GtkStyleContext *context2 = gtk_style_context_new(); - gtk_style_context_set_path(context2, path); - gtk_style_context_set_parent(context2, sCtx); - gtk_widget_path_unref(path); - Gtk_Free(); - sCtx = context2; - } - ASSERT(sCtx); - gtk_style_context_set_scale(sCtx, GetDPIScaleRatio()); - Gtk_State(state, flags); - - GtkBorder border, padding; - int min_width, min_height; - - GtkStateFlags f = gtk_style_context_get_state(sCtx); - - gtk_style_context_get_margin (sCtx, f, &sMargin); - - gtk_style_context_get_border (sCtx, f, &border); - gtk_style_context_get_padding (sCtx, f, &padding); - gtk_style_context_get(sCtx, f, "min-width", &min_width, "min-height", &min_height, NULL); - - min_width += border.left + border.right + padding.left + padding.right; - min_height += border.top + border.bottom + padding.top + padding.bottom; - - sCurrentSize.cx = min_width; - sCurrentSize.cy = min_height; -} - -Size GtkSize() -{ - return sCurrentSize; -} - -void GtkSize(Size& sz) -{ - sz.cx = max(sz.cx, sCurrentSize.cx); - sz.cy = max(sz.cy, sCurrentSize.cy); -} - -Color GetInkColor() -{ - return GetInkColor(sCtx, sFlags); -} - -void SOImages(int imli, dword flags) -{ - int n = GetStdFontCy() - 2; - if(n < 14) // sometimes it gets too small relative to text.. - n = 14; - for(int st = 0; st < 4; st++) { - Gtk_State(st, flags); - CtrlsImg::Set(imli++, CairoImage(n, n, [&](cairo_t *cr) { - double sz = n / Ctrl::SCL(1); - gtk_render_background(sCtx, cr, 0, 0, sz, sz); - gtk_render_frame(sCtx, cr, 0, 0, sz, sz); - gtk_render_check(sCtx, cr, 0, 0, sz, sz); - })); - } -} - -Image CairoImage(int cx = 40, int cy = 32) -{ - return CairoImage(sCtx, cx, cy); -} - -Color GetBackgroundColor() -{ - Image m = CairoImage(30, 30); - Image b = CreateImage(m.GetSize(), SColorPaper()); - Over(b, m); - return AvgColor(b); -} - -Color GetInkColorBk() -{ - Color ink = GetInkColor(); - Color bk = GetBackgroundColor(); - if(Diff(ink, bk) < 100) { - Color dk = SColorText(); - Color wh = SColorPaper(); - if(IsDark(wh)) - Swap(dk, wh); - return IsDark(bk) ? wh : dk; - } - return ink; -} - -Image Gtk_Icon(const char *icon_name, int size) -{ - GdkPixbuf *pixbuf = gtk_icon_theme_load_icon_for_scale(gtk_icon_theme_get_default(), icon_name, - size, 2, (GtkIconLookupFlags)0, NULL); - if(pixbuf) { - int cx = gdk_pixbuf_get_width(pixbuf) * Ctrl::SCL(1); - int cy = gdk_pixbuf_get_height(pixbuf) * Ctrl::SCL(1); - - Image m = CairoImage(cx, cy, [&](cairo_t *cr) { - gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); - cairo_paint(cr); - }); - - Size sz = m.GetSize(); - - g_object_ref_sink(pixbuf); - - return sz.cy > size && sz.cy ? Rescale(m, sz.cx * size / sz.cy, size) : m; - } - return Null; -} - -Image Gtk_IconAdjusted(const char *icon_name, int size) -{ // checks that icons is visible over SColorFace, fixes it if not (by painting it gray) - Image m = Gtk_Icon(icon_name, size); - if(Diff(AvgColor(m), SColorFace()) > 80) - return m; - Color ink = Gray(); - if(Diff(ink, SColorFace()) > 100) - ink = GrayColor(IsDarkTheme() ? 200 : 50); - ImagePainter iw(m.GetSize()); - iw.Clear(RGBAZero()); - iw.DrawImage(0, 0, m, ink); - m = iw; - return m; -} - -void Gtk_OverrideDialogIcon(int i, const char *s) -{ - auto icon = Gtk_Icon(s, DPI(48)); - if (icon.IsEmpty()) { - return; - } - - CtrlImg::Set(i, icon); -} - -void ChHostSkin() -{ - SetupFont(); - - Gtk_New("label.view"); - SColorPaper_Write(GetBackgroundColor()); - SColorText_Write(GetInkColor()); - Gtk_New("window.background"); - SColorFace_Write(GetBackgroundColor()); - Gtk_New("entry"); - Gtk_State(CTRL_DISABLED); - SColorDisabled_Write(GetInkColor()); - if(Diff(SColorText(), SColorDisabled()) < 30 || Diff(SColorFace(), SColorDisabled()) < 70) - SColorDisabled_Write(Gray()); - Gtk_New("entry selection"); - SColorHighlight_Write(GetBackgroundColor()); - SColorHighlightText_Write(GetInkColor()); - Gtk_New("label.view"); - SColorLabel_Write(GetInkColor()); - - Color tooltip_ink, tooltip_paper; - Gtk_New("tooltip.background"); - tooltip_paper = GetBackgroundColor(); - Gtk_New("tooltip.color"); - tooltip_ink = GetInkColor(); - if(Diff(tooltip_paper, tooltip_ink) < 100) { // ink color too close to background color, fix it - tooltip_paper = IsDark(SColorText()) ? LtYellow() : GrayColor(79); - tooltip_ink = SColorText();; - } - SColorInfo_Write(tooltip_paper); - SColorInfoText_Write(tooltip_ink); - - ChBaseSkin(); - - Gtk_New("radiobutton radio"); - SOImages(CtrlsImg::I_S0, GTK_STATE_FLAG_NORMAL); - SOImages(CtrlsImg::I_S1, GTK_STATE_FLAG_CHECKED); - Gtk_New("checkbutton check"); - SOImages(CtrlsImg::I_O0, GTK_STATE_FLAG_NORMAL); - SOImages(CtrlsImg::I_O1, GTK_STATE_FLAG_CHECKED); - SOImages(CtrlsImg::I_O2, GTK_STATE_FLAG_INCONSISTENT); - - CtrlImg::Set(CtrlImg::I_MenuCheck0, CtrlsImg::O0()); - CtrlImg::Set(CtrlImg::I_MenuCheck1, CtrlsImg::O1()); - CtrlImg::Set(CtrlImg::I_MenuRadio0, CtrlsImg::S0()); - CtrlImg::Set(CtrlImg::I_MenuRadio1, CtrlsImg::S1()); - - for(int i = 0; i < 6; i++) - CtrlsImg::Set(CtrlsImg::I_DA + i, CtrlsImg::Get(CtrlsImg::I_kDA + i)); - - Image cancel_image; - - { - Gtk_New("button"); - Color ink; - Color text[4]; - Image button[4]; - for(int pass = 0; pass < 2; pass++) { - Button::Style& s = pass ? Button::StyleOk().Write() : Button::StyleNormal().Write(); - s.focusmargin = DPI(4); - for(int i = 0; i < 4; i++) { - Gtk_State(i); - Image m = Hot3(CairoImage()); - FixButton(m); - s.look[i] = m; - Color ink = i == CTRL_DISABLED ? GetInkColor() : GetInkColorBk(); - s.monocolor[i] = s.textcolor[i] = ink; - if(pass == 0) { - button[i] = WithHotSpots(CairoImage(100, 100), DPI(4), DPI(4), 0, 0); - text[i] = ink; - } - FixButton(button[i]); - } - s.ok = Gtk_IconAdjusted("gtk-ok", DPI(16)); - s.cancel = cancel_image = Gtk_IconAdjusted("gtk-cancel", DPI(16)); - s.exit = Gtk_IconAdjusted("gtk-quit", DPI(16)); - } - - if(ChSynthetic(button, text)) - RoundStyleArrows(); - - { - auto& s = ToolButton::StyleDefault().Write(); - s.look[CTRL_NORMAL] = Image(); - s.look[CTRL_HOT] = button[CTRL_HOT]; - s.look[CTRL_PRESSED] = button[CTRL_PRESSED]; - s.look[CTRL_DISABLED] = Image(); - s.look[CTRL_CHECKED] = button[CTRL_PRESSED]; - s.look[CTRL_HOTCHECKED] = button[CTRL_HOT]; - } - } - - if (!gtk_check_version(3, 10, 0)) { - Gtk_OverrideDialogIcon(CtrlImg::I_information, "dialog-information"); - Gtk_OverrideDialogIcon(CtrlImg::I_question, "dialog-question"); - Gtk_OverrideDialogIcon(CtrlImg::I_exclamation, "dialog-warning"); - Gtk_OverrideDialogIcon(CtrlImg::I_error, "dialog-error"); - } else { - // gtk-dialog-* icons deprecated since 3.10 version (2013-09-23) in favor of dialog-* - // https://docs.gtk.org/gtk3/const.STOCK_DIALOG_INFO.html - Gtk_OverrideDialogIcon(CtrlImg::I_information, "gtk-dialog-info"); - Gtk_OverrideDialogIcon(CtrlImg::I_question, "gtk-dialog-question"); - Gtk_OverrideDialogIcon(CtrlImg::I_exclamation, "gtk-dialog-warning"); - Gtk_OverrideDialogIcon(CtrlImg::I_error, "gtk-dialog-error"); - } - - YesButtonImage_Write(Gtk_IconAdjusted("gtk-yes", DPI(16))); - Image no_image = Gtk_IconAdjusted("gtk-no", DPI(16)); - if(Difference(no_image, cancel_image) > 20) // it is ugly when No and Cancel buttons have the same icon - NoButtonImage_Write(no_image); - AbortButtonImage_Write(Gtk_IconAdjusted("gtk-stop", DPI(16))); - RetryButtonImage_Write(Gtk_IconAdjusted("gtk-refresh", DPI(16))); - - for(int i = 0; i < 4; i++) { - ScrollBar::Style& s = ScrollBar::StyleDefault().Write(); - s.arrowsize = 0; - s.thumbmin = DPI(24); - int g = IsDarkTheme() ? get_i(i, 80, 100, 70, 70) - : get_i(i, 192, 200, 128, 128); - s.vthumb[i] = MakeRoundScrollbarThumb(DPI(16), DPI(4), GrayColor(g)); - s.hthumb[i] = RotateClockwise(s.vthumb[i]); - s.hupper[i] = s.hlower[i] = - s.vupper[i] = s.vlower[i] = Blend(SColorFace(), SWhite(), 50); - } - -#if 0 // let us play it safe.... - { - ScrollBar::Style& s = ScrollBar::StyleDefault().Write(); - gboolean stepper; - gint minslider; - GtkWidget *proto = (GtkWidget *)gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, NULL); // to get style params - gtk_widget_style_get(proto, "has-backward-stepper", &stepper, "min-slider-length", &minslider, NULL); - g_object_ref_sink(proto); - if(!stepper) - s.arrowsize = 0; - Gtk_New("scrollbar.horizontal.bottom"); - Size sz = GtkSize(); - Gtk_New("scrollbar.horizontal.bottom contents"); - GtkSize(sz); - Gtk_New("scrollbar.horizontal.bottom contents trough"); - GtkSize(sz); - Gtk_New("scrollbar.horizontal.bottom contents trough slider"); - GtkSize(sz); - - s.barsize = s.thumbwidth = Ctrl::SCL(sz.cy); - s.thumbmin = max(minslider, 2 * s.barsize); - - sz.cx = 4 * sz.cy; - - for(int status = CTRL_NORMAL; status <= CTRL_DISABLED; status++) { - Gtk_New("scrollbar.horizontal.bottom", status); - Image m = CairoImage(sz.cx, sz.cy); - Gtk_New("scrollbar.horizontal.bottom contents", status); - Over(m, CairoImage(sz.cx, sz.cy)); - Gtk_New("scrollbar.horizontal.bottom contents trough", status); - Over(m, CairoImage(sz.cx, sz.cy)); - s.hupper[status] = s.hlower[status] = WithHotSpot(m, CH_SCROLLBAR_IMAGE, 0);; - s.vupper[status] = s.vlower[status] = WithHotSpot(RotateAntiClockwise(m), CH_SCROLLBAR_IMAGE, 0); // we have problems getting this right for vertical - Gtk_New("scrollbar.horizontal.bottom contents trough slider", status); - Image thumb = CairoImage(sz.cx, sz.cy- sMargin.top - sMargin.bottom); - s.hthumb[status] = WithHotSpot(thumb, CH_SCROLLBAR_IMAGE, 0); - s.vthumb[status] = WithHotSpot(RotateClockwise(thumb), CH_SCROLLBAR_IMAGE, 0); - } - } -#endif - { - MenuBar::Style& s = MenuBar::StyleDefault().Write(); - s.pullshift.y = 0; - - Gtk_New("menu"); - Image m = CairoImage(128, 64); - ImageDraw im(m.GetSize()); - im.DrawRect(m.GetSize(), SColorFace()); // fix dark corners in some themes - im.DrawImage(0, 0, m); - m = im; - s.pullshift.y = 0; - int mg = DPI(2); - s.popupframe = WithHotSpot(m, mg, mg); - Size sz = m.GetSize(); - Image body; - s.popupbody = body = Crop(m, mg, mg, sz.cx - 2 * mg, sz.cy - 2 * mg); - s.leftgap = DPI(16) + Zx(6); - SColorMenu_Write(GetBackgroundColor()); - SColorMenuText_Write(s.menutext); - - Gtk_New("menu menuitem"); - s.menutext = GetInkColor(); - Color c = AvgColor(body); - if(Diff(c, s.menutext) < 100) // menutext color too close to background color, fix it - s.menutext = IsDark(c) ? White() : Black(); - Gtk_State(CTRL_HOT); - s.itemtext = GetInkColor(); - Image item; - s.item = item = Hot3(CairoImage(32, 16)); - Over(body, item); - c = AvgColor(Crop(body, item.GetSize())); - if(Diff(c, s.itemtext) < 100) // itemtext color too close to highlight color, fix it - s.itemtext = IsDark(c) ? White() : Black(); - - m = CreateImage(Size(DPI(32), DPI(16)), SColorFace()); - Gtk_New("frame"); - Over(m, CairoImage(DPI(32), DPI(16))); - // PNGEncoder().SaveFile("/home/cxl/frame.png", m); - // Gtk_New("frame border"); - // Over(m, CairoImage(DPI(32), DPI(16))); - Gtk_New("menubar"); - Over(m, CairoImage(DPI(32), DPI(16))); - s.look = Hot3(m); - Color dk = SColorText(); - Color wh = SColorPaper(); - if(IsDark(wh)) - Swap(dk, wh); - s.topitemtext[0] = IsDark(AvgColor(m)) ? wh : dk; - s.topitem[1] = s.topitem[0] = Null; - s.topitemtext[1] = s.topitemtext[0]; - Gtk_New("menubar menuitem", CTRL_HOT); - s.topitem[0] = Null; - Image topitem = Hot3(CairoImage(DPI(32), DPI(16))); - s.topitem[2] = topitem; - s.topitemtext[2] = GetInkColor(); - Over(m, topitem); - c = AvgColor(m); - if(Diff(c, s.topitemtext[2]) < 100) - s.topitemtext[2] = IsDark(c) ? White() : Black(); - } - - SwapOKCancel_Write(Environment().Get("KDE_FULL_SESSION", String()) != "true"); - - Gtk_Free(); -} - -Image GtkThemeIcon(const char *name, int rsz) -{ - return Gtk_Icon(name, rsz); -} - -#else - -Image Gtk_Icon(const char *icon_name, int size) -{ - GdkPixbuf *pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), icon_name, - size, (GtkIconLookupFlags)0, NULL); - if(pixbuf) { - int cx = gdk_pixbuf_get_width(pixbuf); - int cy = gdk_pixbuf_get_height(pixbuf); - - Image m = CairoImage(cx, cy, [&](cairo_t *cr) { - gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); - cairo_paint(cr); - }); - - Size sz = m.GetSize(); - - g_object_ref_sink(pixbuf); - - return sz.cy > size && sz.cy ? Rescale(m, sz.cx * size / sz.cy, size) : m; - } - return Null; -} - -Image GtkThemeIcon(const char *name, int rsz) -{ - return Gtk_Icon(name, rsz); -} - -void ChHostSkin() -{ - SetupFont(); -#if 0 - static Color paper; - ONCELOCK { - GtkStyleContext *ctx = gtk_widget_get_style_context((GtkWidget *)gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); - gtk_style_context_set_state(ctx, GTK_STATE_FLAG_NORMAL); - paper = AvgColor(CairoImage(ctx)); - } - if(IsDark(paper)) { - SColorText_Write(White()); - SColorPaper_Write(Black()); - SColorMenuText_Write(White()); - SColorFace_Write(GrayColor(60)); - SColorMenu_Write(GrayColor(70)); - SColorHighlight_Write(GrayColor(120)); - SColorInfo_Write(GrayColor(79)); - SColorInfoText_Write(SColorText()); - } - else -#endif - { - SColorFace_Write(Color(242, 241, 240)); - SColorMenu_Write(Color(242, 241, 240)); - SColorHighlight_Write(Color(50, 50, 250)); - } - - ChStdSkin(); -} - -#endif - -}; - -#endif +#include "CtrlLib.h" + +#ifdef GUI_GTK + +namespace Upp { + +int GtkSettingsInt(const char *name) +{ + gint h = Null; + g_object_get(gtk_settings_get_default(), name, &h, NULL); + return h; +} + +int GtkSettingsBool(const char *name) +{ + gboolean h = false; + g_object_get(gtk_settings_get_default(), name, &h, NULL); + return h; +} + +String GtkSettingsString(const char *name) +{ + const char *h = ""; + g_object_get(gtk_settings_get_default(), name, &h, NULL); + return h; +} + +void SetupFont() +{ + int fontface = Font::ARIAL; + int fontheight = 13; + bool bold = false; + bool italic = false; + + String font_name = GtkSettingsString("gtk-font-name"); + + double xdpi = gdk_screen_get_resolution(gdk_display_get_default_screen(gdk_display_get_default())); + + const char *q = strrchr(font_name, ' '); + if(q) { + int h = atoi(q); + if(h) + fontheight = h; + String face(font_name, q); + fontface = Font::FindFaceNameIndex(face); + + if(fontface == 0) { + for(;;) { + const char *q = strrchr(face, ' '); + if(!q) break; + const char *s = q + 1; + if(stricmp(s, "Bold") == 0 || stricmp(s, "Heavy") == 0) + bold = true; + else + if(stricmp(s, "Italic") == 0 || stricmp(s, "Oblique") == 0) + italic = true; + else + if(stricmp(s, "Regular") == 0 || stricmp(s, "Light") || stricmp(s, "Medium")) + ; + else + continue; + face = String(~face, q); + } + fontface = Font::FindFaceNameIndex(face); + if(fontface == 0) { + if(ToUpper(face[0]) == 'M') + fontface = Font::COURIER; + else + if(ToUpper(face[0]) == 'S' && ToUpper(face[1]) == 'e') + fontface = Font::ROMAN; + else + fontface = Font::ARIAL; + } + } + } + Font gui_font = Font(fontface, fround(Ctrl::SCL(fontheight) * xdpi / 72)).Bold(bold).Italic(italic); + Font::SetDefaultFont(gui_font); +} + +static Image sCurrentImage; +static GtkBorder sMargin; + +Image CairoImage(int cx, int cy, Event draw) +{ + Image m[2]; + for(int i = 0; i < 2; i++) { + ImageDraw iw(cx, cy); + iw.DrawRect(0, 0, cx, cy, i ? Black() : White()); + cairo_t *cr = iw; +#if GTK_CHECK_VERSION(3, 20, 0) + cairo_surface_set_device_scale(cairo_get_target(cr), Ctrl::SCL(1), Ctrl::SCL(1)); +#endif + draw(cr); + m[i] = iw; + } + sCurrentImage = RecreateAlpha(m[0], m[1]); + return sCurrentImage; +} + +Image CairoImage(GtkStyleContext *ctx, int cx = 40, int cy = 32) +{ + return CairoImage(cx, cy, [&](cairo_t *cr) { + double sx = cx / Ctrl::SCL(1); + double sy = cy / Ctrl::SCL(1); + gtk_render_background(ctx, cr, 0, 0, sx, sy); + gtk_render_frame(ctx, cr, 0, 0, sx, sy); + }); +} + +Color GetInkColor(GtkStyleContext *ctx, dword flags) +{ + GdkRGBA color; + gtk_style_context_get_color(ctx, (GtkStateFlags)flags, &color); + RGBA rgba; + rgba.r = int(255 * color.red); + rgba.g = int(255 * color.green); + rgba.b = int(255 * color.blue); + rgba.a = int(255 * color.alpha); + rgba = Premultiply(rgba); + RGBA t = SColorPaper(); + AlphaBlend(&t, &rgba, 1); + return t; +} + +#if GTK_CHECK_VERSION(3, 20, 0) + +static GtkStyleContext *sCtx = NULL; +static GtkStateFlags sFlags; + +void Gtk_Free() +{ + if(sCtx) + g_object_ref_sink(sCtx); + sCtx = NULL; +} + +void Gtk_State(int state, dword flags = 0) +{ + sFlags = static_cast(decode(state, CTRL_HOT, GTK_STATE_FLAG_PRELIGHT, + CTRL_PRESSED, GTK_STATE_FLAG_ACTIVE, + CTRL_DISABLED, GTK_STATE_FLAG_INSENSITIVE, + GTK_STATE_FLAG_NORMAL) | flags) ; + gtk_style_context_set_state(sCtx, sFlags); +} + +static Size sCurrentSize; + +void Gtk_New(const char *name, int state = 0, dword flags = 0) +{ + Gtk_Free(); + sCtx = NULL; + for(const String& element : Split(name, ' ')) { + GtkWidgetPath *path = sCtx ? gtk_widget_path_copy(gtk_style_context_get_path(sCtx)) + : gtk_widget_path_new(); + Vector s = Split(element, '.'); + if(s.GetCount()) { + gtk_widget_path_append_type(path, G_TYPE_NONE); + gtk_widget_path_iter_set_object_name(path, -1, s[0]); + } + for(int i = 1; i < s.GetCount(); i++) + gtk_widget_path_iter_add_class(path, -1, s[i]); + + GtkStyleContext *context2 = gtk_style_context_new(); + gtk_style_context_set_path(context2, path); + gtk_style_context_set_parent(context2, sCtx); + gtk_widget_path_unref(path); + Gtk_Free(); + sCtx = context2; + } + ASSERT(sCtx); + gtk_style_context_set_scale(sCtx, GetDPIScaleRatio()); + Gtk_State(state, flags); + + GtkBorder border, padding; + int min_width, min_height; + + GtkStateFlags f = gtk_style_context_get_state(sCtx); + + gtk_style_context_get_margin (sCtx, f, &sMargin); + + gtk_style_context_get_border (sCtx, f, &border); + gtk_style_context_get_padding (sCtx, f, &padding); + gtk_style_context_get(sCtx, f, "min-width", &min_width, "min-height", &min_height, NULL); + + min_width += border.left + border.right + padding.left + padding.right; + min_height += border.top + border.bottom + padding.top + padding.bottom; + + sCurrentSize.cx = min_width; + sCurrentSize.cy = min_height; +} + +Size GtkSize() +{ + return sCurrentSize; +} + +void GtkSize(Size& sz) +{ + sz.cx = max(sz.cx, sCurrentSize.cx); + sz.cy = max(sz.cy, sCurrentSize.cy); +} + +Color GetInkColor() +{ + return GetInkColor(sCtx, sFlags); +} + +void SOImages(int imli, dword flags) +{ + int n = GetStdFontCy() - 2; + if(n < 14) // sometimes it gets too small relative to text.. + n = 14; + for(int st = 0; st < 4; st++) { + Gtk_State(st, flags); + CtrlsImg::Set(imli++, CairoImage(n, n, [&](cairo_t *cr) { + double sz = n / Ctrl::SCL(1); + gtk_render_background(sCtx, cr, 0, 0, sz, sz); + gtk_render_frame(sCtx, cr, 0, 0, sz, sz); + gtk_render_check(sCtx, cr, 0, 0, sz, sz); + })); + } +} + +Image CairoImage(int cx = 40, int cy = 32) +{ + return CairoImage(sCtx, cx, cy); +} + +Color GetBackgroundColor() +{ + Image m = CairoImage(30, 30); + Image b = CreateImage(m.GetSize(), SColorPaper()); + Over(b, m); + return AvgColor(b); +} + +Color GetInkColorBk() +{ + Color ink = GetInkColor(); + Color bk = GetBackgroundColor(); + if(Diff(ink, bk) < 100) { + Color dk = SColorText(); + Color wh = SColorPaper(); + if(IsDark(wh)) + Swap(dk, wh); + return IsDark(bk) ? wh : dk; + } + return ink; +} + +Image Gtk_Icon(const char *icon_name, int size) +{ + GdkPixbuf *pixbuf = gtk_icon_theme_load_icon_for_scale(gtk_icon_theme_get_default(), icon_name, + size, 2, (GtkIconLookupFlags)0, NULL); + if(pixbuf) { + int cx = gdk_pixbuf_get_width(pixbuf) * Ctrl::SCL(1); + int cy = gdk_pixbuf_get_height(pixbuf) * Ctrl::SCL(1); + + Image m = CairoImage(cx, cy, [&](cairo_t *cr) { + gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); + cairo_paint(cr); + }); + + Size sz = m.GetSize(); + + g_object_ref_sink(pixbuf); + + return sz.cy > size && sz.cy ? Rescale(m, sz.cx * size / sz.cy, size) : m; + } + return Null; +} + +Image Gtk_IconAdjusted(const char *icon_name, int size) +{ // checks that icons is visible over SColorFace, fixes it if not (by painting it gray) + Image m = Gtk_Icon(icon_name, size); + if(Diff(AvgColor(m), SColorFace()) > 80) + return m; + Color ink = Gray(); + if(Diff(ink, SColorFace()) > 100) + ink = GrayColor(IsDarkTheme() ? 200 : 50); + ImagePainter iw(m.GetSize()); + iw.Clear(RGBAZero()); + iw.DrawImage(0, 0, m, ink); + m = iw; + return m; +} + +void Gtk_OverrideDialogIcon(int i, const char *s) +{ + auto icon = Gtk_Icon(s, DPI(48)); + if (icon.IsEmpty()) { + return; + } + + CtrlImg::Set(i, icon); +} + +void ChHostSkin() +{ + SetupFont(); + + Gtk_New("label.view"); + SColorPaper_Write(GetBackgroundColor()); + SColorText_Write(GetInkColor()); + Gtk_New("window.background"); + SColorFace_Write(GetBackgroundColor()); + Gtk_New("entry"); + Gtk_State(CTRL_DISABLED); + SColorDisabled_Write(GetInkColor()); + if(Diff(SColorText(), SColorDisabled()) < 30 || Diff(SColorFace(), SColorDisabled()) < 70) + SColorDisabled_Write(Gray()); + Gtk_New("entry selection"); + SColorHighlight_Write(GetBackgroundColor()); + SColorHighlightText_Write(GetInkColor()); + Gtk_New("label.view"); + SColorLabel_Write(GetInkColor()); + + Color tooltip_ink, tooltip_paper; + Gtk_New("tooltip.background"); + tooltip_paper = GetBackgroundColor(); + Gtk_New("tooltip.color"); + tooltip_ink = GetInkColor(); + if(Diff(tooltip_paper, tooltip_ink) < 100) { // ink color too close to background color, fix it + tooltip_paper = IsDark(SColorText()) ? LtYellow() : GrayColor(79); + tooltip_ink = SColorText();; + } + SColorInfo_Write(tooltip_paper); + SColorInfoText_Write(tooltip_ink); + + ChBaseSkin(); + + Gtk_New("radiobutton radio"); + SOImages(CtrlsImg::I_S0, GTK_STATE_FLAG_NORMAL); + SOImages(CtrlsImg::I_S1, GTK_STATE_FLAG_CHECKED); + Gtk_New("checkbutton check"); + SOImages(CtrlsImg::I_O0, GTK_STATE_FLAG_NORMAL); + SOImages(CtrlsImg::I_O1, GTK_STATE_FLAG_CHECKED); + SOImages(CtrlsImg::I_O2, GTK_STATE_FLAG_INCONSISTENT); + + CtrlImg::Set(CtrlImg::I_MenuCheck0, CtrlsImg::O0()); + CtrlImg::Set(CtrlImg::I_MenuCheck1, CtrlsImg::O1()); + CtrlImg::Set(CtrlImg::I_MenuRadio0, CtrlsImg::S0()); + CtrlImg::Set(CtrlImg::I_MenuRadio1, CtrlsImg::S1()); + + for(int i = 0; i < 6; i++) + CtrlsImg::Set(CtrlsImg::I_DA + i, CtrlsImg::Get(CtrlsImg::I_kDA + i)); + + Image cancel_image; + + { + Gtk_New("button"); + Color ink; + Color text[4]; + Image button[4]; + for(int pass = 0; pass < 2; pass++) { + Button::Style& s = pass ? Button::StyleOk().Write() : Button::StyleNormal().Write(); + s.focusmargin = DPI(4); + for(int i = 0; i < 4; i++) { + Gtk_State(i); + Image m = Hot3(CairoImage()); + FixButton(m); + s.look[i] = m; + Color ink = i == CTRL_DISABLED ? GetInkColor() : GetInkColorBk(); + s.monocolor[i] = s.textcolor[i] = ink; + if(pass == 0) { + button[i] = WithHotSpots(CairoImage(100, 100), DPI(4), DPI(4), 0, 0); + text[i] = ink; + } + FixButton(button[i]); + } + s.ok = Gtk_IconAdjusted("gtk-ok", DPI(16)); + s.cancel = cancel_image = Gtk_IconAdjusted("gtk-cancel", DPI(16)); + s.exit = Gtk_IconAdjusted("gtk-quit", DPI(16)); + } + + if(ChSynthetic(button, text)) + RoundStyleArrows(); + + { + auto& s = ToolButton::StyleDefault().Write(); + s.look[CTRL_NORMAL] = Image(); + s.look[CTRL_HOT] = button[CTRL_HOT]; + s.look[CTRL_PRESSED] = button[CTRL_PRESSED]; + s.look[CTRL_DISABLED] = Image(); + s.look[CTRL_CHECKED] = button[CTRL_PRESSED]; + s.look[CTRL_HOTCHECKED] = button[CTRL_HOT]; + } + } + + if (!gtk_check_version(3, 10, 0)) { + Gtk_OverrideDialogIcon(CtrlImg::I_information, "dialog-information"); + Gtk_OverrideDialogIcon(CtrlImg::I_question, "dialog-question"); + Gtk_OverrideDialogIcon(CtrlImg::I_exclamation, "dialog-warning"); + Gtk_OverrideDialogIcon(CtrlImg::I_error, "dialog-error"); + } else { + // gtk-dialog-* icons deprecated since 3.10 version (2013-09-23) in favor of dialog-* + // https://docs.gtk.org/gtk3/const.STOCK_DIALOG_INFO.html + Gtk_OverrideDialogIcon(CtrlImg::I_information, "gtk-dialog-info"); + Gtk_OverrideDialogIcon(CtrlImg::I_question, "gtk-dialog-question"); + Gtk_OverrideDialogIcon(CtrlImg::I_exclamation, "gtk-dialog-warning"); + Gtk_OverrideDialogIcon(CtrlImg::I_error, "gtk-dialog-error"); + } + + YesButtonImage_Write(Gtk_IconAdjusted("gtk-yes", DPI(16))); + Image no_image = Gtk_IconAdjusted("gtk-no", DPI(16)); + if(Difference(no_image, cancel_image) > 20) // it is ugly when No and Cancel buttons have the same icon + NoButtonImage_Write(no_image); + AbortButtonImage_Write(Gtk_IconAdjusted("gtk-stop", DPI(16))); + RetryButtonImage_Write(Gtk_IconAdjusted("gtk-refresh", DPI(16))); + + for(int i = 0; i < 4; i++) { + ScrollBar::Style& s = ScrollBar::StyleDefault().Write(); + s.arrowsize = 0; + s.thumbmin = DPI(24); + int g = IsDarkTheme() ? get_i(i, 80, 100, 70, 70) + : get_i(i, 192, 200, 128, 128); + s.vthumb[i] = MakeRoundScrollbarThumb(DPI(16), DPI(4), GrayColor(g)); + s.hthumb[i] = RotateClockwise(s.vthumb[i]); + s.hupper[i] = s.hlower[i] = + s.vupper[i] = s.vlower[i] = Blend(SColorFace(), SWhite(), 50); + } + +#if 0 // let us play it safe.... + { + ScrollBar::Style& s = ScrollBar::StyleDefault().Write(); + gboolean stepper; + gint minslider; + GtkWidget *proto = (GtkWidget *)gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, NULL); // to get style params + gtk_widget_style_get(proto, "has-backward-stepper", &stepper, "min-slider-length", &minslider, NULL); + g_object_ref_sink(proto); + if(!stepper) + s.arrowsize = 0; + Gtk_New("scrollbar.horizontal.bottom"); + Size sz = GtkSize(); + Gtk_New("scrollbar.horizontal.bottom contents"); + GtkSize(sz); + Gtk_New("scrollbar.horizontal.bottom contents trough"); + GtkSize(sz); + Gtk_New("scrollbar.horizontal.bottom contents trough slider"); + GtkSize(sz); + + s.barsize = s.thumbwidth = Ctrl::SCL(sz.cy); + s.thumbmin = max(minslider, 2 * s.barsize); + + sz.cx = 4 * sz.cy; + + for(int status = CTRL_NORMAL; status <= CTRL_DISABLED; status++) { + Gtk_New("scrollbar.horizontal.bottom", status); + Image m = CairoImage(sz.cx, sz.cy); + Gtk_New("scrollbar.horizontal.bottom contents", status); + Over(m, CairoImage(sz.cx, sz.cy)); + Gtk_New("scrollbar.horizontal.bottom contents trough", status); + Over(m, CairoImage(sz.cx, sz.cy)); + s.hupper[status] = s.hlower[status] = WithHotSpot(m, CH_SCROLLBAR_IMAGE, 0);; + s.vupper[status] = s.vlower[status] = WithHotSpot(RotateAntiClockwise(m), CH_SCROLLBAR_IMAGE, 0); // we have problems getting this right for vertical + Gtk_New("scrollbar.horizontal.bottom contents trough slider", status); + Image thumb = CairoImage(sz.cx, sz.cy- sMargin.top - sMargin.bottom); + s.hthumb[status] = WithHotSpot(thumb, CH_SCROLLBAR_IMAGE, 0); + s.vthumb[status] = WithHotSpot(RotateClockwise(thumb), CH_SCROLLBAR_IMAGE, 0); + } + } +#endif + { + MenuBar::Style& s = MenuBar::StyleDefault().Write(); + s.pullshift.y = 0; + + Gtk_New("menu"); + Image m = CairoImage(128, 64); + ImageDraw im(m.GetSize()); + im.DrawRect(m.GetSize(), SColorFace()); // fix dark corners in some themes + im.DrawImage(0, 0, m); + m = im; + s.pullshift.y = 0; + int mg = DPI(2); + s.popupframe = WithHotSpot(m, mg, mg); + Size sz = m.GetSize(); + Image body; + s.popupbody = body = Crop(m, mg, mg, sz.cx - 2 * mg, sz.cy - 2 * mg); + s.leftgap = DPI(16) + Zx(6); + SColorMenu_Write(GetBackgroundColor()); + SColorMenuText_Write(s.menutext); + + Gtk_New("menu menuitem"); + s.menutext = GetInkColor(); + Color c = AvgColor(body); + if(Diff(c, s.menutext) < 100) // menutext color too close to background color, fix it + s.menutext = IsDark(c) ? White() : Black(); + Gtk_State(CTRL_HOT); + s.itemtext = GetInkColor(); + Image item; + s.item = item = Hot3(CairoImage(32, 16)); + Over(body, item); + c = AvgColor(Crop(body, item.GetSize())); + if(Diff(c, s.itemtext) < 100) // itemtext color too close to highlight color, fix it + s.itemtext = IsDark(c) ? White() : Black(); + + m = CreateImage(Size(DPI(32), DPI(16)), SColorFace()); + Gtk_New("frame"); + Over(m, CairoImage(DPI(32), DPI(16))); + // PNGEncoder().SaveFile("/home/cxl/frame.png", m); + // Gtk_New("frame border"); + // Over(m, CairoImage(DPI(32), DPI(16))); + Gtk_New("menubar"); + Over(m, CairoImage(DPI(32), DPI(16))); + s.look = Hot3(m); + Color dk = SColorText(); + Color wh = SColorPaper(); + if(IsDark(wh)) + Swap(dk, wh); + s.topitemtext[0] = IsDark(AvgColor(m)) ? wh : dk; + s.topitem[1] = s.topitem[0] = Null; + s.topitemtext[1] = s.topitemtext[0]; + Gtk_New("menubar menuitem", CTRL_HOT); + s.topitem[0] = Null; + Image topitem = Hot3(CairoImage(DPI(32), DPI(16))); + s.topitem[2] = topitem; + s.topitemtext[2] = GetInkColor(); + Over(m, topitem); + c = AvgColor(m); + if(Diff(c, s.topitemtext[2]) < 100) + s.topitemtext[2] = IsDark(c) ? White() : Black(); + } + + SwapOKCancel_Write(Environment().Get("KDE_FULL_SESSION", String()) != "true"); + + Gtk_Free(); +} + +Image GtkThemeIcon(const char *name, int rsz) +{ + return Gtk_Icon(name, rsz); +} + +#else + +Image Gtk_Icon(const char *icon_name, int size) +{ + GdkPixbuf *pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), icon_name, + size, (GtkIconLookupFlags)0, NULL); + if(pixbuf) { + int cx = gdk_pixbuf_get_width(pixbuf); + int cy = gdk_pixbuf_get_height(pixbuf); + + Image m = CairoImage(cx, cy, [&](cairo_t *cr) { + gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); + cairo_paint(cr); + }); + + Size sz = m.GetSize(); + + g_object_ref_sink(pixbuf); + + return sz.cy > size && sz.cy ? Rescale(m, sz.cx * size / sz.cy, size) : m; + } + return Null; +} + +Image GtkThemeIcon(const char *name, int rsz) +{ + return Gtk_Icon(name, rsz); +} + +void ChHostSkin() +{ + SetupFont(); +#if 0 + static Color paper; + ONCELOCK { + GtkStyleContext *ctx = gtk_widget_get_style_context((GtkWidget *)gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); + gtk_style_context_set_state(ctx, GTK_STATE_FLAG_NORMAL); + paper = AvgColor(CairoImage(ctx)); + } + if(IsDark(paper)) { + SColorText_Write(White()); + SColorPaper_Write(Black()); + SColorMenuText_Write(White()); + SColorFace_Write(GrayColor(60)); + SColorMenu_Write(GrayColor(70)); + SColorHighlight_Write(GrayColor(120)); + SColorInfo_Write(GrayColor(79)); + SColorInfoText_Write(SColorText()); + } + else +#endif + { + SColorFace_Write(Color(242, 241, 240)); + SColorMenu_Write(Color(242, 241, 240)); + SColorHighlight_Write(Color(50, 50, 250)); + } + + ChStdSkin(); +} + +#endif + +}; + +#endif diff --git a/uppsrc/Draw/ImageChOp.cpp b/uppsrc/Draw/ImageChOp.cpp index ae1384ee3..951445bde 100644 --- a/uppsrc/Draw/ImageChOp.cpp +++ b/uppsrc/Draw/ImageChOp.cpp @@ -156,6 +156,14 @@ bool IsSingleColor(const Image& m, const Rect& rect_) return true; } +Color GetSingleColor(const Image& m) +{ + Size sz = m.GetSize(); + if(sz.cx == 0 || sz.cy == 0 || !IsSingleColor(m, sz)) + return Null; + return m[0][0]; +} + Image RecreateAlpha(const Image& overwhite, const Image& overblack) { Size sz = overwhite.GetSize(); diff --git a/uppsrc/Draw/ImageOp.h b/uppsrc/Draw/ImageOp.h index 8c08a2ac7..bf3099abb 100644 --- a/uppsrc/Draw/ImageOp.h +++ b/uppsrc/Draw/ImageOp.h @@ -139,6 +139,7 @@ Image HorzSymm(Image src); double Difference(const Image& a, const Image& b); bool IsSingleColor(const Image& m, const Rect& rect); +Color GetSingleColor(const Image& m); Image RecreateAlpha(const Image& overwhite, const Image& overblack); int ImageMargin(const Image& m, int p, int dist);