From 5b31eea4fd932bcf559e1ad71ca4c04ef4281178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 28 Oct 2021 10:52:31 +0000 Subject: [PATCH] Bug 1737676 - Use GTK menu radius on native context menus and panels. r=stransky Differential Revision: https://phabricator.services.mozilla.com/D129439 --- servo/components/style/custom_properties.rs | 7 +- servo/components/style/gecko/media_queries.rs | 7 ++ toolkit/themes/linux/global/popup.css | 4 +- widget/LookAndFeel.h | 3 + widget/gtk/WidgetStyleCache.cpp | 55 ++++++++---- widget/gtk/WidgetStyleCache.h | 2 +- widget/gtk/gtkdrawing.h | 2 + widget/gtk/nsLookAndFeel.cpp | 88 ++++++++++++------- widget/gtk/nsLookAndFeel.h | 1 + widget/nsXPLookAndFeel.cpp | 1 + xpcom/ds/StaticAtoms.py | 1 + 11 files changed, 121 insertions(+), 50 deletions(-) diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs index d424584f2e24..0a32f6a9d223 100644 --- a/servo/components/style/custom_properties.rs +++ b/servo/components/style/custom_properties.rs @@ -75,8 +75,13 @@ fn get_titlebar_radius(device: &Device) -> VariableValue { VariableValue::pixel(device.titlebar_radius()) } -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 1] = [ +fn get_menu_radius(device: &Device) -> VariableValue { + VariableValue::pixel(device.menu_radius()) +} + +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 2] = [ make_variable!(atom!("-moz-gtk-csd-titlebar-radius"), get_titlebar_radius), + make_variable!(atom!("-moz-gtk-menu-radius"), get_menu_radius), ]; impl CssEnvironment { diff --git a/servo/components/style/gecko/media_queries.rs b/servo/components/style/gecko/media_queries.rs index f814781d0ff2..d1d118722f87 100644 --- a/servo/components/style/gecko/media_queries.rs +++ b/servo/components/style/gecko/media_queries.rs @@ -375,6 +375,13 @@ impl Device { } } + /// Returns the gtk menu radius in CSS pixels. + pub fn menu_radius(&self) -> f32 { + unsafe { + bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::GtkMenuRadius as i32) as f32 + } + } + /// Return whether the document is a chrome document. #[inline] pub fn is_chrome_document(&self) -> bool { diff --git a/toolkit/themes/linux/global/popup.css b/toolkit/themes/linux/global/popup.css index edc34ff8555f..c4128a1291cd 100644 --- a/toolkit/themes/linux/global/popup.css +++ b/toolkit/themes/linux/global/popup.css @@ -10,10 +10,10 @@ menupopup, panel { min-width: 1px; - --panel-padding: 0; + --panel-padding: max(env(-moz-gtk-menu-radius) - 1px, 0px) 0; --panel-color: MenuText; --panel-background: Menu; - --panel-border-radius: 0; + --panel-border-radius: env(-moz-gtk-menu-radius); --panel-border-color: ThreeDShadow; --panel-width: initial; } diff --git a/widget/LookAndFeel.h b/widget/LookAndFeel.h index ba9353406359..d2997c41e388 100644 --- a/widget/LookAndFeel.h +++ b/widget/LookAndFeel.h @@ -334,6 +334,9 @@ class LookAndFeel { /** GTK titlebar radius */ TitlebarRadius, + /** GTK menu radius */ + GtkMenuRadius, + /* * Not an ID; used to define the range of valid IDs. Must be last. */ diff --git a/widget/gtk/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp index 19f0c5eed0ff..28fe6fc67483 100644 --- a/widget/gtk/WidgetStyleCache.cpp +++ b/widget/gtk/WidgetStyleCache.cpp @@ -20,6 +20,13 @@ static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR && GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL, "incorrect direction state flags"); +enum class CSDStyle { + Unknown, + Solid, + Normal, +}; + +static CSDStyle gCSDStyle = CSDStyle::Unknown; static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT]; static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT]; @@ -70,6 +77,8 @@ static GtkWidget* CreateMenuBarWidget() { static GtkWidget* CreateMenuPopupWidget() { GtkWidget* widget = gtk_menu_new(); + GtkStyleContext* style = gtk_widget_get_style_context(widget); + gtk_style_context_add_class(style, GTK_STYLE_CLASS_POPUP); gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW), nullptr); return widget; @@ -431,8 +440,7 @@ static GtkWidget* CreateNotebookWidget() { return widget; } -static void CreateHeaderBarWidget(WidgetNodeType aAppearance, - bool aIsSolidCSDStyleUsed) { +static void CreateHeaderBarWidget(WidgetNodeType aAppearance) { sWidgetStorage[aAppearance] = gtk_header_bar_new(); GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); @@ -452,7 +460,7 @@ static void CreateHeaderBarWidget(WidgetNodeType aAppearance, // Headerbar has to be placed to window with csd or solid-csd style // to properly draw the decorated. gtk_style_context_add_class(style, - aIsSolidCSDStyleUsed ? "solid-csd" : "csd"); + IsSolidCSDStyleUsed() ? "solid-csd" : "csd"); GtkWidget* fixed = gtk_fixed_new(); gtk_container_add(GTK_CONTAINER(window), fixed); @@ -597,6 +605,22 @@ static bool IsToolbarButtonEnabled(ButtonLayout* aButtonLayout, return false; } +bool IsSolidCSDStyleUsed() { + if (gCSDStyle == CSDStyle::Unknown) { + bool solid; + { + GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_titlebar(GTK_WINDOW(window), gtk_header_bar_new()); + gtk_widget_realize(window); + GtkStyleContext* windowStyle = gtk_widget_get_style_context(window); + solid = gtk_style_context_has_class(windowStyle, "solid-csd"); + gtk_widget_destroy(window); + } + gCSDStyle = solid ? CSDStyle::Solid : CSDStyle::Normal; + } + return gCSDStyle == CSDStyle::Solid; +} + static void CreateHeaderBarButtons() { GtkWidget* headerBar = sWidgetStorage[MOZ_GTK_HEADER_BAR]; MOZ_ASSERT(headerBar != nullptr, "We're missing header bar widget!"); @@ -634,18 +658,8 @@ static void CreateHeaderBarButtons() { } static void CreateHeaderBar() { - const bool isSolidCSDStyleUsed = []() { - GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_titlebar(GTK_WINDOW(window), gtk_header_bar_new()); - gtk_widget_realize(window); - GtkStyleContext* windowStyle = gtk_widget_get_style_context(window); - bool ret = gtk_style_context_has_class(windowStyle, "solid-csd"); - gtk_widget_destroy(window); - return ret; - }(); - - CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR, isSolidCSDStyleUsed); - CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED, isSolidCSDStyleUsed); + CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR); + CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED); CreateHeaderBarButtons(); } @@ -1152,6 +1166,13 @@ static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType) { false, "MOZ_GTK_HEADER_BAR_BUTTON_RESTORE is used as an icon only!"); return nullptr; } + case MOZ_GTK_MENUPOPUP_DECORATION: { + GtkStyleContext* parentStyle = + CreateSubStyleWithClass(MOZ_GTK_MENUPOPUP, "csd"); + style = CreateCSSNode("decoration", parentStyle); + g_object_unref(parentStyle); + break; + } case MOZ_GTK_WINDOW_DECORATION: { GtkStyleContext* parentStyle = CreateSubStyleWithClass(MOZ_GTK_WINDOW, "csd"); @@ -1309,12 +1330,14 @@ static GtkStyleContext* GetWidgetStyleInternal(WidgetNodeType aNodeType) { return style; } -void ResetWidgetCache(void) { +void ResetWidgetCache() { for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) { if (sStyleStorage[i]) g_object_unref(sStyleStorage[i]); } mozilla::PodArrayZero(sStyleStorage); + gCSDStyle = CSDStyle::Unknown; + /* This will destroy all of our widgets */ if (sWidgetStorage[MOZ_GTK_WINDOW]) { gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]); diff --git a/widget/gtk/WidgetStyleCache.h b/widget/gtk/WidgetStyleCache.h index 13fa53d4b4b6..f80eef2af7e3 100644 --- a/widget/gtk/WidgetStyleCache.h +++ b/widget/gtk/WidgetStyleCache.h @@ -50,7 +50,7 @@ GtkStyleContext* CreateStyleContextWithStates( GtkTextDirection aDirection = GTK_TEXT_DIR_NONE, GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL); -void ResetWidgetCache(void); +void ResetWidgetCache(); bool IsSolidCSDStyleUsed(); diff --git a/widget/gtk/gtkdrawing.h b/widget/gtk/gtkdrawing.h index a5c29b98bd7c..8d5e3bc3c933 100644 --- a/widget/gtk/gtkdrawing.h +++ b/widget/gtk/gtkdrawing.h @@ -347,6 +347,8 @@ enum WidgetNodeType : int { MOZ_GTK_WINDOW_DECORATION, MOZ_GTK_WINDOW_DECORATION_SOLID, + MOZ_GTK_MENUPOPUP_DECORATION, + MOZ_GTK_WIDGET_NODE_COUNT }; diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp index 205bef4ef96a..1ecbdac96801 100644 --- a/widget/gtk/nsLookAndFeel.cpp +++ b/widget/gtk/nsLookAndFeel.cpp @@ -122,6 +122,20 @@ nsLookAndFeel::~nsLookAndFeel() { gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr); } +#if 0 +static void DumpStyleContext(GtkStyleContext* aStyle) { + static auto sGtkStyleContextToString = + reinterpret_cast( + dlsym(RTLD_DEFAULT, "gtk_style_context_to_string")); + char* str = sGtkStyleContextToString(aStyle, ~0); + printf("%s\n", str); + g_free(str); + str = gtk_widget_path_to_string(gtk_style_context_get_path(aStyle)); + printf("%s\n", str); + g_free(str); +} +#endif + // Modifies color |*aDest| as if a pattern of color |aSource| was painted with // CAIRO_OPERATOR_OVER to a surface with color |*aDest|. static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) { @@ -837,6 +851,11 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { aResult = EffectiveTheme().mTitlebarRadius; break; } + case IntID::GtkMenuRadius: { + EnsureInit(); + aResult = EffectiveTheme().mMenuRadius; + break; + } case IntID::AllowOverlayScrollbarsOverlap: { aResult = 1; break; @@ -1406,6 +1425,35 @@ static nscolor GetBackgroundColor( return NS_TRANSPARENT; } +static int32_t GetBorderRadius(GtkStyleContext* aStyle) { + GValue value = G_VALUE_INIT; + // NOTE(emilio): In an ideal world, we'd query the two longhands + // (border-top-left-radius and border-top-right-radius) separately. However, + // that doesn't work (GTK rejects the query with: + // + // Style property "border-top-left-radius" is not gettable + // + // However! Getting border-radius does work, and it does return the + // border-top-left-radius as a gint: + // + // https://docs.gtk.org/gtk3/const.STYLE_PROPERTY_BORDER_RADIUS.html + // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-20/gtk/gtkcssshorthandpropertyimpl.c#L961-977 + // + // So we abuse this fact, and make the assumption here that the + // border-top-{left,right}-radius are the same, and roll with it. + gtk_style_context_get_property(aStyle, "border-radius", GTK_STATE_FLAG_NORMAL, + &value); + auto unset = MakeScopeExit([&] { g_value_unset(&value); }); + + auto type = G_VALUE_TYPE(&value); + if (type == G_TYPE_INT) { + return g_value_get_int(&value); + } + NS_WARNING(nsPrintfCString("Unknown value type %lu for titlebar radius", type) + .get()); + return 0; +} + void nsLookAndFeel::PerThemeData::Init() { mName = GetGtkTheme(); @@ -1533,36 +1581,7 @@ void nsLookAndFeel::PerThemeData::Init() { mTitlebarInactiveText = GDK_RGBA_TO_NS_RGBA(color); mTitlebarInactiveBackground = GetBackgroundColor(style, mTitlebarText, GTK_STATE_FLAG_BACKDROP); - - GValue value = G_VALUE_INIT; - // NOTE(emilio): In an ideal world, we'd query the two longhands - // (border-top-left-radius and border-top-right-radius) separately. However, - // that doesn't work (GTK rejects the query with: - // - // Style property "border-top-left-radius" is not gettable - // - // However! Getting border-radius does work, and it does return the - // border-top-left-radius as a gint: - // - // https://docs.gtk.org/gtk3/const.STYLE_PROPERTY_BORDER_RADIUS.html - // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-20/gtk/gtkcssshorthandpropertyimpl.c#L961-977 - // - // So we abuse this fact, and make the assumption here that the - // border-top-{left,right}-radius are the same, and roll with it. - gtk_style_context_get_property(style, "border-radius", - GTK_STATE_FLAG_NORMAL, &value); - - mTitlebarRadius = [&]() -> int { - auto type = G_VALUE_TYPE(&value); - if (type == G_TYPE_INT) { - return g_value_get_int(&value); - } - NS_WARNING( - nsPrintfCString("Unknown value type %lu for titlebar radius", type) - .get()); - return 0; - }(); - g_value_unset(&value); + mTitlebarRadius = IsSolidCSDStyleUsed() ? 0 : GetBorderRadius(style); } style = GetStyleContext(MOZ_GTK_MENUPOPUP); @@ -1585,6 +1604,14 @@ void nsLookAndFeel::PerThemeData::Init() { "background"); return mMozWindowBackground; }(); + mMenuRadius = 0; + if (!IsSolidCSDStyleUsed()) { + mMenuRadius = GetBorderRadius(style); + if (!mMenuRadius) { + mMenuRadius = + GetBorderRadius(GetStyleContext(MOZ_GTK_MENUPOPUP_DECORATION)); + } + } style = GetStyleContext(MOZ_GTK_MENUITEM); gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color); @@ -1825,6 +1852,7 @@ void nsLookAndFeel::PerThemeData::Init() { NS_SUCCEEDED(rv) ? color : 0); } LOGLNF(" * titlebar-radius: %d\n", mTitlebarRadius); + LOGLNF(" * menu-radius: %d\n", mMenuRadius); } } diff --git a/widget/gtk/nsLookAndFeel.h b/widget/gtk/nsLookAndFeel.h index 828930a1bc2a..4bdb2bee778e 100644 --- a/widget/gtk/nsLookAndFeel.h +++ b/widget/gtk/nsLookAndFeel.h @@ -124,6 +124,7 @@ class nsLookAndFeel final : public nsXPLookAndFeel { float mCaretRatio = 0.0f; int32_t mTitlebarRadius = 0; + int32_t mMenuRadius = 0; char16_t mInvisibleCharacter = 0; bool mMenuSupportsDrag = false; diff --git a/widget/nsXPLookAndFeel.cpp b/widget/nsXPLookAndFeel.cpp index 5f6799809304..667bdc70dd92 100644 --- a/widget/nsXPLookAndFeel.cpp +++ b/widget/nsXPLookAndFeel.cpp @@ -175,6 +175,7 @@ static const char sIntPrefs[][43] = { "ui.systemHorizontalScrollbarHeight", "ui.touchDeviceSupportPresent", "ui.titlebarRadius", + "ui.GtkMenuRadius", }; static_assert(ArrayLength(sIntPrefs) == size_t(LookAndFeel::IntID::End), diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py index d91e823ee30a..d1ff8092b7bb 100644 --- a/xpcom/ds/StaticAtoms.py +++ b/xpcom/ds/StaticAtoms.py @@ -2231,6 +2231,7 @@ STATIC_ATOMS = [ Atom("_moz_gtk_csd_maximize_button", "-moz-gtk-csd-maximize-button"), Atom("_moz_gtk_csd_close_button", "-moz-gtk-csd-close-button"), Atom("_moz_gtk_csd_reversed_placement", "-moz-gtk-csd-reversed-placement"), + Atom("_moz_gtk_menu_radius", "-moz-gtk-menu-radius"), Atom("_moz_proton", "-moz-proton"), Atom("_moz_proton_places_tooltip", "-moz-proton-places-tooltip"), Atom("_moz_system_dark_theme", "-moz-system-dark-theme"),