Bug 265698 - "XUL tab widgets are rendered incorrectly" (Refactor native GTK drawing of tabs, taking advantage of the new CSS) [p=frnchfrgg-mozbugs@altern.org (RIVAUD Julien [_FrnchFrgg_]) r=twanno sr+a1.9=roc]

This commit is contained in:
reed@reedloden.com 2007-12-21 03:51:48 -08:00
Родитель bc9eaba49e
Коммит df0247312a
3 изменённых файлов: 203 добавлений и 77 удалений

Просмотреть файл

@ -1677,74 +1677,150 @@ moz_gtk_progress_chunk_paint(GdkDrawable* drawable, GdkRectangle* rect,
return MOZ_GTK_SUCCESS;
}
gint
moz_gtk_get_tab_thickness(void)
{
ensure_tab_widget();
if (YTHICKNESS(gTabWidget->style) < 2)
return 2; /* some themes don't set ythickness correctly */
return YTHICKNESS(gTabWidget->style);
}
static gint
moz_gtk_tab_paint(GdkDrawable* drawable, GdkRectangle* rect,
GdkRectangle* cliprect, gint flags,
GdkRectangle* cliprect, GtkTabFlags flags,
GtkTextDirection direction)
{
/*
* In order to get the correct shadows and highlights, GTK paints
* tabs right-to-left (end-to-beginning, to be generic), leaving
* out the active tab, and then paints the current tab once
* everything else is painted. In addition, GTK uses a 2-pixel
* overlap between adjacent tabs (this value is hard-coded in
* gtknotebook.c). For purposes of mapping to gecko's frame
* positions, we put this overlap on the far edge of the frame
* (i.e., for a horizontal/top tab strip, we shift the left side
* of each tab 2px to the left, into the neighboring tab's frame
* rect. The right 2px * of a tab's frame will be referred to as
* the "overlap area".
*
* Since we can't guarantee painting order with gecko, we need to
* manage the overlap area manually. There are three types of tab
* boundaries we need to handle:
*
* * two non-active tabs: In this case, we just have both tabs
* paint normally.
*
* * non-active to active tab: Here, we need the tab on the left to paint
* itself normally, then paint the edge of the
* active tab in its overlap area.
*
* * active to non-active tab: In this case, we just have both tabs paint
* normally.
*
* We need to make an exception for the first tab - since there is
* no tab to the left to paint the overlap area, we do _not_ shift
* the tab left by 2px.
*/
/* When the tab isn't selected, we just draw a notebook extension.
* When it is selected, we overwrite the adjacent border of the tabpanel
* touching the tab with a pierced border (called "the gap") to make the
* tab appear physically attached to the tabpanel; see details below. */
GtkStyle* style;
ensure_tab_widget();
gtk_widget_set_direction(gTabWidget, direction);
if (!(flags & MOZ_GTK_TAB_FIRST)) {
rect->x -= 2;
rect->width += 2;
}
style = gTabWidget->style;
TSOffsetStyleGCs(style, rect->x, rect->y);
gtk_paint_extension(style, drawable,
((flags & MOZ_GTK_TAB_SELECTED) ?
GTK_STATE_NORMAL : GTK_STATE_ACTIVE),
GTK_SHADOW_OUT, cliprect, gTabWidget, "tab", rect->x,
rect->y, rect->width, rect->height, GTK_POS_BOTTOM);
if (flags & MOZ_GTK_TAB_BEFORE_SELECTED) {
gboolean before_selected = ((flags & MOZ_GTK_TAB_BEFORE_SELECTED)!=0);
cliprect->y -= 2;
cliprect->height += 2;
rect->y -= 2 * before_selected;
rect->x += rect->width - 2;
TSOffsetStyleGCs(style, rect->x, rect->y);
gtk_paint_extension(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
if ((flags & MOZ_GTK_TAB_SELECTED) == 0) {
/* Only draw the tab */
gtk_paint_extension(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_OUT,
cliprect, gTabWidget, "tab",
rect->x, rect->y, rect->width,
rect->height + (2 * before_selected),
GTK_POS_BOTTOM);
rect->x, rect->y, rect->width, rect->height,
(flags & MOZ_GTK_TAB_BOTTOM) ?
GTK_POS_TOP : GTK_POS_BOTTOM );
} else {
/* Draw the tab and the gap
* We want the gap to be positionned exactly on the tabpanel top
* border; since tabbox.css may set a negative margin so that the tab
* frame rect already overlaps the tabpanel frame rect, we need to take
* that into account when drawing. To that effect, nsNativeThemeGTK
* passes us this negative margin (bmargin in the graphic below) in the
* lowest bits of |flags|. We use it to set gap_voffset, the distance
* between the top of the gap and the bottom of the tab (resp. the
* bottom of the gap and the top of the tab when we draw a bottom tab),
* while ensuring that the gap always touches the border of the tab,
* i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results
* with big negative or positive margins.
* Here is a graphical explanation in the case of top tabs:
* ___________________________
* / \
* | T A B |
* ----------|. . . . . . . . . . . . . . .|----- top of tabpanel
* : ^ bmargin : ^
* : | (-negative margin, : |
* bottom : v passed in flags) : | gap_height
* of -> :.............................: | (the size of the
* the tab . part of the gap . | tabpanel top border)
* . outside of the tab . v
* ----------------------------------------------
*
* To draw the gap, we use gtk_paint_box_gap(), see comment in
* moz_gtk_tabpanels_paint(). This box_gap is made 3 * gap_height tall,
* which should suffice to ensure that the only visible border is the
* pierced one. If the tab is in the middle, we make the box_gap begin
* a bit to the left of the tab and end a bit to the right, adjusting
* the gap position so it still is under the tab, because we want the
* rendering of a gap in the middle of a tabpanel. This is the role of
* the gints gap_{l,r}_offset. On the contrary, if the tab is the
* first, we align the start border of the box_gap with the start
* border of the tab (left if LTR, right if RTL), by setting the
* appropriate offset to 0.*/
gint gap_loffset, gap_roffset, gap_voffset, gap_height;
/* Get height needed by the gap */
gap_height = moz_gtk_get_tab_thickness();
/* Extract gap_voffset from the first bits of flags */
gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK;
if (gap_voffset > gap_height)
gap_voffset = gap_height;
/* Set gap_{l,r}_offset to appropriate values */
gap_loffset = gap_roffset = 20; /* should be enough */
if (flags & MOZ_GTK_TAB_FIRST) {
if (direction == GTK_TEXT_DIR_RTL)
gap_roffset = 0;
else
gap_loffset = 0;
}
if (flags & MOZ_GTK_TAB_BOTTOM) {
/* Enlarge the cliprect to have room for the full gap height */
cliprect->height += gap_height - gap_voffset;
cliprect->y -= gap_height - gap_voffset;
/* Draw the tab */
gtk_paint_extension(style, drawable, GTK_STATE_NORMAL,
GTK_SHADOW_OUT, cliprect, gTabWidget, "tab",
rect->x, rect->y + gap_voffset, rect->width,
rect->height - gap_voffset, GTK_POS_TOP);
/* Draw the gap; erase with background color before painting in
* case theme does not */
gtk_style_apply_default_background(style, drawable, TRUE,
GTK_STATE_NORMAL, cliprect,
rect->x,
rect->y + gap_voffset
- gap_height,
rect->width, gap_height);
gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
cliprect, gTabWidget, "notebook",
rect->x - gap_loffset,
rect->y + gap_voffset - 3 * gap_height,
rect->width + gap_loffset + gap_roffset,
3 * gap_height, GTK_POS_BOTTOM,
gap_loffset, rect->width);
} else {
/* Enlarge the cliprect to have room for the full gap height */
cliprect->height += gap_height - gap_voffset;
/* Draw the tab */
gtk_paint_extension(style, drawable, GTK_STATE_NORMAL,
GTK_SHADOW_OUT, cliprect, gTabWidget, "tab",
rect->x, rect->y, rect->width,
rect->height - gap_voffset, GTK_POS_BOTTOM);
/* Draw the gap; erase with background color before painting in
* case theme does not */
gtk_style_apply_default_background(style, drawable, TRUE,
GTK_STATE_NORMAL, cliprect,
rect->x,
rect->y + rect->height
- gap_voffset,
rect->width, gap_height);
gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
cliprect, gTabWidget, "notebook",
rect->x - gap_loffset,
rect->y + rect->height - gap_voffset,
rect->width + gap_loffset + gap_roffset,
3 * gap_height, GTK_POS_TOP,
gap_loffset, rect->width);
}
}
return MOZ_GTK_SUCCESS;
@ -1754,6 +1830,10 @@ static gint
moz_gtk_tabpanels_paint(GdkDrawable* drawable, GdkRectangle* rect,
GdkRectangle* cliprect, GtkTextDirection direction)
{
/* We use gtk_paint_box_gap() to draw the tabpanels widget. gtk_paint_box()
* draws an all-purpose box, which a lot of themes render differently.
* A zero-width gap is still visible in most themes, so we hide it to the
* left (10px should be enough) */
GtkStyle* style;
ensure_tab_widget();
@ -1762,9 +1842,10 @@ moz_gtk_tabpanels_paint(GdkDrawable* drawable, GdkRectangle* rect,
style = gTabWidget->style;
TSOffsetStyleGCs(style, rect->x, rect->y);
gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
cliprect, gTabWidget, "notebook", rect->x, rect->y,
rect->width, rect->height);
gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
cliprect, gTabWidget, "notebook", rect->x, rect->y,
rect->width, rect->height,
GTK_POS_TOP, -10, 0);
return MOZ_GTK_SUCCESS;
}
@ -2198,6 +2279,10 @@ moz_gtk_get_widget_border(GtkThemeWidgetType widget, gint* left, gint* top,
ensure_check_menu_item_widget();
w = gCheckMenuItemWidget;
break;
case MOZ_GTK_TAB:
ensure_tab_widget();
w = gTabWidget;
break;
/* These widgets have no borders, since they are not containers. */
case MOZ_GTK_SPLITTER_HORIZONTAL:
case MOZ_GTK_SPLITTER_VERTICAL:
@ -2481,7 +2566,8 @@ moz_gtk_widget_paint(GtkThemeWidgetType widget, GdkDrawable* drawable,
direction);
break;
case MOZ_GTK_TAB:
return moz_gtk_tab_paint(drawable, rect, cliprect, flags, direction);
return moz_gtk_tab_paint(drawable, rect, cliprect,
(GtkTabFlags) flags, direction);
break;
case MOZ_GTK_TABPANELS:
return moz_gtk_tabpanels_paint(drawable, rect, cliprect, direction);

Просмотреть файл

@ -80,12 +80,14 @@ typedef struct {
/** flags for tab state **/
typedef enum {
/* first eight bits are used to pass a margin */
MOZ_GTK_TAB_MARGIN_MASK = 0xFF,
/* bottom tabs */
MOZ_GTK_TAB_BOTTOM = 1 << 8,
/* the first tab in the group */
MOZ_GTK_TAB_FIRST = 1 << 0,
/* the tab just before the selected tab */
MOZ_GTK_TAB_BEFORE_SELECTED = 1 << 1,
MOZ_GTK_TAB_FIRST = 1 << 9,
/* the selected tab */
MOZ_GTK_TAB_SELECTED = 1 << 2
MOZ_GTK_TAB_SELECTED = 1 << 10
} GtkTabFlags;
/** flags for menuitems **/
@ -356,6 +358,11 @@ gint moz_gtk_splitter_get_metrics(gint orientation, gint* size);
*/
GtkWidget* moz_gtk_get_scrollbar_widget(void);
/**
* Get the YTHICKNESS of a tab (notebook extension).
*/
gint moz_gtk_get_tab_thickness(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */

Просмотреть файл

@ -499,20 +499,28 @@ nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame,
aGtkWidgetType = MOZ_GTK_TABPANELS;
break;
case NS_THEME_TAB:
case NS_THEME_TAB_LEFT_EDGE:
case NS_THEME_TAB_RIGHT_EDGE:
{
if (aWidgetFlags) {
*aWidgetFlags = 0;
/* First bits will be used to store max(0,-bmargin) where bmargin
* is the bottom margin of the tab in pixels (resp. top margin,
* for bottom tabs). */
gint margin;
if (IsBottomTab(aFrame)) {
*aWidgetFlags = MOZ_GTK_TAB_BOTTOM;
margin = aFrame->GetUsedMargin().top;
} else {
*aWidgetFlags = 0;
margin = aFrame->GetUsedMargin().bottom;
}
if (aWidgetType == NS_THEME_TAB &&
CheckBooleanAttr(aFrame, nsWidgetAtoms::selected))
*aWidgetFlags |= PR_MIN(MOZ_GTK_TAB_MARGIN_MASK,
PR_MAX(0, aFrame->PresContext()->
AppUnitsToDevPixels(-margin) ));
if (IsSelectedTab(aFrame))
*aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
else if (aWidgetType == NS_THEME_TAB_LEFT_EDGE)
*aWidgetFlags |= MOZ_GTK_TAB_BEFORE_SELECTED;
if (aFrame->GetContent()->HasAttr(kNameSpaceID_None,
nsWidgetAtoms::firsttab))
if (IsFirstTab(aFrame))
*aWidgetFlags |= MOZ_GTK_TAB_FIRST;
}
@ -797,6 +805,7 @@ NS_IMETHODIMP
nsNativeThemeGTK::GetWidgetBorder(nsIDeviceContext* aContext, nsIFrame* aFrame,
PRUint8 aWidgetType, nsMargin* aResult)
{
GtkTextDirection direction = GetTextDirection(aFrame);
aResult->top = aResult->left = aResult->right = aResult->bottom = 0;
switch (aWidgetType) {
case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
@ -820,15 +829,25 @@ nsNativeThemeGTK::GetWidgetBorder(nsIDeviceContext* aContext, nsIFrame* aFrame,
// To make this happen, we draw a button border for the outer button,
// but don't reserve any space for it.
break;
case NS_THEME_TAB:
// Top tabs have no bottom border, bottom tabs have no top border
moz_gtk_get_widget_border(MOZ_GTK_TAB, &aResult->left, &aResult->top,
&aResult->right, &aResult->bottom, direction,
FALSE);
if (IsBottomTab(aFrame))
aResult->top = 0;
else
aResult->bottom = 0;
break;
default:
{
GtkThemeWidgetType gtkWidgetType;
GtkTextDirection direction = GetTextDirection(aFrame);
if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nsnull,
nsnull))
moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
&aResult->right, &aResult->bottom, direction,
(aFrame ? aFrame->GetContent()->IsNodeOfType(nsINode::eHTML) : FALSE));
aFrame && aFrame->GetContent()->
IsNodeOfType(nsINode::eHTML));
}
}
return NS_OK;
@ -855,8 +874,24 @@ nsNativeThemeGTK::GetWidgetOverflow(nsIDeviceContext* aContext,
nsRect* aResult)
{
nsIntMargin extraSize;
if (!GetExtraSizeForWidget(aWidgetType, &extraSize))
if (aWidgetType == NS_THEME_TAB)
{
if (!IsSelectedTab(aFrame))
return PR_FALSE;
if (IsBottomTab(aFrame)) {
extraSize = nsMargin(0, aFrame->PresContext()->
DevPixelsToAppUnits(moz_gtk_get_tab_thickness())
+ PR_MIN(0, aFrame->GetUsedMargin().top), 0, 0);
} else {
extraSize = nsMargin(0, 0, 0, aFrame->PresContext()->
DevPixelsToAppUnits(moz_gtk_get_tab_thickness())
+ PR_MIN(0, aFrame->GetUsedMargin().bottom));
}
}
else if (!GetExtraSizeForWidget(aWidgetType, &extraSize))
return PR_FALSE;
PRInt32 p2a = aContext->AppUnitsPerDevPixel();
nsMargin m(NSIntPixelsToAppUnits(extraSize.left, p2a),
NSIntPixelsToAppUnits(extraSize.top, p2a),
@ -1147,8 +1182,6 @@ nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
case NS_THEME_TAB:
// case NS_THEME_TAB_PANEL:
case NS_THEME_TAB_LEFT_EDGE:
case NS_THEME_TAB_RIGHT_EDGE:
case NS_THEME_TAB_PANELS:
case NS_THEME_TOOLTIP:
case NS_THEME_SPINNER: