@@ -49,6 +49,8 @@ struct Layout {
4949// GTK's own window API already speaks in content sizes and shadowless positions
5050// (gtk_window_resize, gtk_window_move); this makes the measured side agree with it,
5151// from where GTK allocated the window's child and its title bar.
52+ static GtkWidget* FindHeaderBar (GtkWidget* widget);
53+
5254static Layout GetLayout (GtkWidget* widget, GdkWindow* gdk_window) {
5355 Layout layout;
5456 // An unmapped window has no frame yet, and GDK answers with an estimate that is not
@@ -70,7 +72,41 @@ static Layout GetLayout(GtkWidget* widget, GdkWindow* gdk_window) {
7072 GtkAllocation child_allocation = {};
7173 gint child_x = 0 ;
7274 gint child_y = 0 ;
73- if (!child || !gtk_widget_get_mapped (child) ||
75+ if (!child) {
76+ // A window nobody put content into - one this library created - has no child to
77+ // measure. GTK's own idea of the window's size leaves client-side decorations out,
78+ // which tells the content's size; where it sits inside the surface is not public,
79+ // so the shadow is taken to be as wide above as below.
80+ gint width = 0 ;
81+ gint height = 0 ;
82+ gtk_window_get_size (GTK_WINDOW (widget), &width, &height);
83+ if (width <= 1 || height <= 1 ||
84+ (width >= layout.content .width && height >= layout.content .height )) {
85+ return layout;
86+ }
87+ gint title_height = 0 ;
88+ GtkWidget* header_bar = FindHeaderBar (widget);
89+ if (header_bar && gtk_widget_get_mapped (header_bar)) {
90+ title_height = gtk_widget_get_allocated_height (header_bar);
91+ }
92+ const gint side = (layout.content .width - width) / 2 ;
93+ const gint shadow_top = (layout.content .height - height - title_height) / 2 ;
94+ if (side < 0 || shadow_top < 0 ) {
95+ return layout;
96+ }
97+ layout.frame = {origin_x + side, origin_y + shadow_top, width, height + title_height};
98+ layout.content = {origin_x + side, origin_y + shadow_top + title_height, width, height};
99+ #ifdef GDK_WINDOWING_WAYLAND
100+ if (GDK_IS_WAYLAND_DISPLAY (gdk_window_get_display (gdk_window))) {
101+ layout.content .x -= layout.frame .x ;
102+ layout.content .y -= layout.frame .y ;
103+ layout.frame .x = 0 ;
104+ layout.frame .y = 0 ;
105+ }
106+ #endif
107+ return layout;
108+ }
109+ if (!gtk_widget_get_mapped (child) ||
74110 !gtk_widget_translate_coordinates (child, widget, 0 , 0 , &child_x, &child_y)) {
75111 return layout;
76112 }
@@ -845,13 +881,106 @@ TitleBarStyle Window::GetTitleBarStyle() const {
845881 return pimpl_->title_bar_style_ ;
846882}
847883
884+ // The shadow of a window with client-side decorations - every toplevel on Wayland, and
885+ // windows with a header bar on X11 - is drawn by GTK itself, from the CSS of the window's
886+ // "decoration" node. That node cannot be styled through the window's own style context,
887+ // so the window gets a style class, and one rule for the whole screen takes the shadow
888+ // (and the hairline border that is part of it) away from windows carrying it. The state
889+ // lives on the widget, so every wrapper of the window agrees. A window the window
890+ // manager decorates has no such node: its shadow is not the application's to remove.
891+ static const char * kNoShadowStyleClass = " nativeapi-no-shadow" ;
892+ static const char * kPendingNoShadowKey = " nativeapi-pending-no-shadow" ;
893+
894+ static void EnsureNoShadowRule (GtkWidget* widget) {
895+ static bool installed = false ;
896+ if (installed) {
897+ return ;
898+ }
899+ installed = true ;
900+ GtkCssProvider* provider = gtk_css_provider_new ();
901+ gtk_css_provider_load_from_data (provider,
902+ " window.nativeapi-no-shadow decoration,"
903+ " window.nativeapi-no-shadow decoration:backdrop {"
904+ " box-shadow: none; border: none; }" ,
905+ -1 , nullptr );
906+ gtk_style_context_add_provider_for_screen (gtk_widget_get_screen (widget),
907+ GTK_STYLE_PROVIDER (provider),
908+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION );
909+ g_object_unref (provider);
910+ }
911+
912+ // Only ever on a mapped window. The shadow is also a margin of the surface, and GTK
913+ // does not survive that margin changing between realizing a window and mapping it: it
914+ // asks for a size with the new margin and allocates with the old one, which leaves the
915+ // content too small for good. On a mapped window the change is taken in - the content
916+ // size is asked for again, so that it is the surface that shrinks or grows.
917+ // GTK marks the windows it decorates itself with the "csd" style class.
918+ static bool DrawsOwnShadow (GtkWidget* widget) {
919+ return gtk_style_context_has_class (gtk_widget_get_style_context (widget), " csd" );
920+ }
921+
922+ static void ApplyShadowClass (GtkWidget* widget, bool has_shadow) {
923+ GtkStyleContext* context = gtk_widget_get_style_context (widget);
924+ if (!has_shadow && !DrawsOwnShadow (widget)) {
925+ return ; // the window manager's shadow: HasShadow() goes on saying true
926+ }
927+ if (has_shadow == !gtk_style_context_has_class (context, kNoShadowStyleClass )) {
928+ return ;
929+ }
930+ if (has_shadow) {
931+ gtk_style_context_remove_class (context, kNoShadowStyleClass );
932+ } else {
933+ EnsureNoShadowRule (widget);
934+ gtk_style_context_add_class (context, kNoShadowStyleClass );
935+ }
936+ }
937+
938+ // What SetHasShadow() asked for while the window was not mapped: 1 for no shadow, 2 for
939+ // a shadow. Also covers a window that is hidden at the moment, which is in the same
940+ // state as one that was never shown.
941+ static gboolean OnMappedApplyShadow (GtkWidget* widget, GdkEvent* event, gpointer data) {
942+ (void )event;
943+ (void )data;
944+ const gint pending = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), kPendingNoShadowKey ));
945+ g_object_set_data (G_OBJECT (widget), kPendingNoShadowKey , nullptr );
946+ if (pending != 0 ) {
947+ ApplyShadowClass (widget, pending == 2 );
948+ }
949+ g_signal_handlers_disconnect_matched (widget, G_SIGNAL_MATCH_FUNC , 0 , 0 , nullptr ,
950+ reinterpret_cast <gpointer>(OnMappedApplyShadow), nullptr );
951+ return FALSE ;
952+ }
953+
848954void Window::SetHasShadow (bool has_shadow) {
849- // Window shadows are typically managed by the window manager
850- // Provide stub implementation
955+ GtkWidget* widget = pimpl_->widget_ ;
956+ if (!widget || !GTK_IS_WINDOW (widget)) {
957+ return ;
958+ }
959+ GObject* object = G_OBJECT (widget);
960+ if (gtk_widget_get_mapped (widget)) {
961+ g_object_set_data (object, kPendingNoShadowKey , nullptr );
962+ ApplyShadowClass (widget, has_shadow);
963+ return ;
964+ }
965+ if (!has_shadow && gtk_widget_get_realized (widget) && !DrawsOwnShadow (widget)) {
966+ return ; // see ApplyShadowClass()
967+ }
968+ if (!g_object_get_data (object, kPendingNoShadowKey )) {
969+ g_signal_connect (widget, " map-event" , G_CALLBACK (OnMappedApplyShadow), nullptr );
970+ }
971+ g_object_set_data (object, kPendingNoShadowKey , GINT_TO_POINTER (has_shadow ? 2 : 1 ));
851972}
852973
853974bool Window::HasShadow () const {
854- return true ; // Default assumption
975+ GtkWidget* widget = pimpl_->widget_ ;
976+ if (!widget || !GTK_IS_WINDOW (widget)) {
977+ return true ;
978+ }
979+ const gint pending = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), kPendingNoShadowKey ));
980+ if (pending != 0 ) {
981+ return pending == 2 ;
982+ }
983+ return !gtk_style_context_has_class (gtk_widget_get_style_context (widget), kNoShadowStyleClass );
855984}
856985
857986void Window::SetOpacity (float opacity) {
0 commit comments