Bug 1308936 - Draw tooltips correctly r=karlt
authorJan Horak <jhorak@redhat.com>
Fri, 11 Nov 2016 11:31:29 +1300
changeset 348818 bb7bc01deac5bf711297e216328e640aeb729895
parent 348817 fec4c0269a8281c2b945d43869fb72e879d0b660
child 348819 53c441f68990de42869a32303a79e1747990e0f2
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1308936
milestone52.0a1
Bug 1308936 - Draw tooltips correctly r=karlt Tooltip widget is made in GTK3 as following tree: * Tooltip window * Horizontal Box * Icon (not supported by Firefox) * Label Each element can be fully styled by CSS of GTK theme so we have to draw all elements with appropriate offset and full box model. MozReview-Commit-ID: E9yYd5UWBu4
toolkit/themes/linux/global/popup.css
widget/gtk/gtk2drawing.c
widget/gtk/gtk3drawing.cpp
--- a/toolkit/themes/linux/global/popup.css
+++ b/toolkit/themes/linux/global/popup.css
@@ -75,18 +75,16 @@ panel[type="arrow"][side="right"] {
   margin-left: -5px;
 }
 
 /* ::::: tooltip ::::: */
 
 tooltip {
   -moz-appearance: tooltip;
   margin-top: 21px;
-  /* GTK hardcodes this to 4px */
-  padding: 4px;
   max-width: 40em;
   color: InfoText;
   font: message-box;
 }
 
 tooltip[titletip="true"] {
  /* See bug 32157 comment 128
   * margin: -2px 0px 0px -3px;
--- a/widget/gtk/gtk2drawing.c
+++ b/widget/gtk/gtk2drawing.c
@@ -2972,16 +2972,20 @@ moz_gtk_get_widget_border(WidgetNodeType
         ensure_check_menu_item_widget();
         w = gCheckMenuItemWidget;
         break;
     case MOZ_GTK_TAB_TOP:
     case MOZ_GTK_TAB_BOTTOM:
         ensure_tab_widget();
         w = gTabWidget;
         break;
+    case MOZ_GTK_TOOLTIP:
+        // In GTK 2 the spacing between box is set to 4.
+        *left = *top = *right = *bottom = 4;
+        return MOZ_GTK_SUCCESS;
     /* These widgets have no borders, since they are not containers. */
     case MOZ_GTK_SPLITTER_HORIZONTAL:
     case MOZ_GTK_SPLITTER_VERTICAL:
     case MOZ_GTK_CHECKBUTTON:
     case MOZ_GTK_RADIOBUTTON:
     case MOZ_GTK_SCROLLBAR_BUTTON:
     case MOZ_GTK_SCROLLBAR_HORIZONTAL:
     case MOZ_GTK_SCROLLBAR_VERTICAL:
@@ -2993,17 +2997,16 @@ moz_gtk_get_widget_border(WidgetNodeType
     case MOZ_GTK_PROGRESS_CHUNK:
     case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
     case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
     case MOZ_GTK_TREEVIEW_EXPANDER:
     case MOZ_GTK_TOOLBAR_SEPARATOR:
     case MOZ_GTK_MENUSEPARATOR:
     /* These widgets have no borders.*/
     case MOZ_GTK_SPINBUTTON:
-    case MOZ_GTK_TOOLTIP:
     case MOZ_GTK_WINDOW:
     case MOZ_GTK_RESIZER:
     case MOZ_GTK_MENUARROW:
     case MOZ_GTK_TOOLBARBUTTON_ARROW:
     case MOZ_GTK_TOOLBAR:
     case MOZ_GTK_MENUBAR:
     case MOZ_GTK_TAB_SCROLLARROW:
         *left = *top = *right = *bottom = 0;
--- a/widget/gtk/gtk3drawing.cpp
+++ b/widget/gtk/gtk3drawing.cpp
@@ -445,30 +445,37 @@ moz_gtk_get_widget_min_size(WidgetNodeTy
   ReleaseStyleContext(style);
 
   *width += border.left + border.right + margin.left + margin.right +
             padding.left + padding.right;
   *height += border.top + border.bottom + margin.top + margin.bottom +
              padding.top + padding.bottom;
 }
 
+static void
+moz_gtk_rectangle_inset(GdkRectangle* rect, GtkBorder& aBorder)
+{
+    MOZ_ASSERT(rect);
+    rect->x += aBorder.left;
+    rect->y += aBorder.top;
+    rect->width -= aBorder.left + aBorder.right;
+    rect->height -= aBorder.top + aBorder.bottom;
+}
+
 /* Subtracting margin is used to inset drawing of element which can have margins,
  * like scrollbar, scrollbar's trough, thumb and scrollbar's button */
 static void
 moz_gtk_subtract_margin(GtkStyleContext* style, GdkRectangle* rect)
 {
     MOZ_ASSERT(rect);
     GtkBorder margin;
 
     gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
                                  &margin);
-    rect->x += margin.left;
-    rect->y += margin.top;
-    rect->width -= margin.right + margin.left;
-    rect->height -= margin.top + margin.bottom;
+    moz_gtk_rectangle_inset(rect, margin);
 }
 
 static gint
 moz_gtk_scrollbar_button_paint(cairo_t *cr, const GdkRectangle* aRect,
                                GtkWidgetState* state,
                                GtkScrollbarButtonFlags flags,
                                GtkTextDirection direction)
 {
@@ -1242,22 +1249,67 @@ moz_gtk_toolbar_separator_paint(cairo_t 
                         rect->x + (rect->width - paint_width) / 2,
                         rect->y + rect->height * end_fraction);
     }
     ReleaseStyleContext(style);
     return MOZ_GTK_SUCCESS;
 }
 
 static gint
-moz_gtk_tooltip_paint(cairo_t *cr, GdkRectangle* rect,
+moz_gtk_tooltip_paint(cairo_t *cr, const GdkRectangle* aRect,
                       GtkTextDirection direction)
 {
+    // Tooltip widget is made in GTK3 as following tree:
+    // Tooltip window
+    //   Horizontal Box
+    //     Icon (not supported by Firefox)
+    //     Label
+    // Each element can be fully styled by CSS of GTK theme.
+    // We have to draw all elements with appropriate offset and right dimensions.
+
+    // Tooltip drawing
     GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLTIP, direction);
-    gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
-    gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+    GdkRectangle rect = *aRect;
+    gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+    gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+    // Horizontal Box drawing
+    //
+    // The box element has hard-coded 6px margin-* GtkWidget properties, which
+    // are added between the window dimensions and the CSS margin box of the
+    // horizontal box.  The frame of the tooltip window is drawn in the
+    // 6px margin.
+    // For drawing Horizontal Box we have to inset drawing area by that 6px
+    // plus its CSS margin.
+    GtkStyleContext* boxStyle =
+        CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), style);
+
+    rect.x += 6;
+    rect.y += 6;
+    rect.width -= 12;
+    rect.height -= 12;
+
+    moz_gtk_subtract_margin(boxStyle, &rect);
+    gtk_render_background(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+    gtk_render_frame(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+
+    // Label drawing
+    GtkBorder padding, border;
+    gtk_style_context_get_padding(boxStyle, GTK_STATE_FLAG_NORMAL, &padding);
+    moz_gtk_rectangle_inset(&rect, padding);
+    gtk_style_context_get_border(boxStyle, GTK_STATE_FLAG_NORMAL, &border);
+    moz_gtk_rectangle_inset(&rect, border);
+
+    GtkStyleContext* labelStyle =
+        CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+    moz_gtk_draw_styled_frame(labelStyle, cr, &rect, false);
+    g_object_unref(labelStyle);
+
+    g_object_unref(boxStyle);
+
     ReleaseStyleContext(style);
     return MOZ_GTK_SUCCESS;
 }
 
 static gint
 moz_gtk_resizer_paint(cairo_t *cr, GdkRectangle* rect,
                       GtkWidgetState* state,
                       GtkTextDirection direction)
@@ -2121,18 +2173,38 @@ moz_gtk_get_widget_border(WidgetNodeType
             return MOZ_GTK_SUCCESS;
         }
     case MOZ_GTK_INFO_BAR:
         w = GetWidget(MOZ_GTK_INFO_BAR);
         break;
     case MOZ_GTK_TOOLTIP:
         {
             style = ClaimStyleContext(MOZ_GTK_TOOLTIP);
-            moz_gtk_add_style_border(style, left, top, right, bottom);
-            moz_gtk_add_style_padding(style, left, top, right, bottom);
+            // In GTK 3 there are 6 pixels of additional margin around the box.
+            // See details there:
+            // https://github.com/GNOME/gtk/blob/5ea69a136bd7e4970b3a800390e20314665aaed2/gtk/ui/gtktooltipwindow.ui#L11
+            *left = *right = *top = *bottom = 6;
+
+            // We also need to add margin/padding/borders from Tooltip content.
+            // Tooltip contains horizontal box, where icon and label is put.
+            // We ignore icon as long as we don't have support for it.
+            GtkStyleContext* boxStyle =
+                CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
+                                     style);
+            moz_gtk_add_margin_border_padding(boxStyle,
+                                              left, top, right, bottom);
+
+            GtkStyleContext* labelStyle =
+                CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+            moz_gtk_add_margin_border_padding(labelStyle,
+                                              left, top, right, bottom);
+
+            g_object_unref(labelStyle);
+            g_object_unref(boxStyle);
+
             ReleaseStyleContext(style);
             return MOZ_GTK_SUCCESS;
         }
     case MOZ_GTK_SCROLLBAR_VERTICAL:
     case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
         {
           if (gtk_check_version(3,20,0) == nullptr) {
             style = ClaimStyleContext(widget);