/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Return widget scale factor of the monitor where the window is located by the // most part. We intentionally honor the text scale factor here in order to // have consistent scaling with other UI elements. staticinline CSSToLayoutDeviceScale GetWidgetScaleFactor(nsIFrame* aFrame) { return aFrame->PresContext()->CSSToDevPixelScale();
}
/* static */
GtkTextDirection nsNativeThemeGTK::GetTextDirection(nsIFrame* aFrame) { // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to // horizontal text with direction=RTL), rather than just considering the // text direction. GtkTextDirection does not have distinct values for // vertical writing modes, but considering the block flow direction is // important for resizers and scrollbar elements, at least. return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
}
if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) { // For these widget types, some element (either a child or parent) // actually has element focus, so we check the focused attribute // to see whether to draw in the focused state.
aState->focused = elementState.HasState(ElementState::FOCUSRING); if (aAppearance == StyleAppearance::Radio ||
aAppearance == StyleAppearance::Checkbox) { // In XUL, checkboxes and radios shouldn't have focus rings, their // labels do
aState->focused = FALSE;
}
// A button with drop down menu open or an activated toggle button // should always appear depressed. if (aAppearance == StyleAppearance::Button ||
aAppearance == StyleAppearance::Toolbarbutton ||
aAppearance == StyleAppearance::Dualbutton ||
aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
aAppearance == StyleAppearance::Menulist ||
aAppearance == StyleAppearance::MenulistButton) { bool menuOpen = IsOpenButton(aFrame);
aState->depressed = IsCheckedButton(aFrame) || menuOpen; // we must not highlight buttons with open drop down menus on hover.
aState->inHover = aState->inHover && !menuOpen;
}
}
aGtkWidgetType = elementState.HasState(ElementState::INDETERMINATE)
? IsVerticalProgress(stateFrame)
? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
: MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
: MOZ_GTK_PROGRESS_CHUNK;
} break; case StyleAppearance::TabScrollArrowBack: case StyleAppearance::TabScrollArrowForward: if (aWidgetFlags)
*aWidgetFlags = aAppearance == StyleAppearance::TabScrollArrowBack
? GTK_ARROW_LEFT
: GTK_ARROW_RIGHT;
aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW; break; case StyleAppearance::Tabpanels:
aGtkWidgetType = MOZ_GTK_TABPANELS; break; case StyleAppearance::Tab: { if (IsBottomTab(aFrame)) {
aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
} else {
aGtkWidgetType = MOZ_GTK_TAB_TOP;
}
if (aWidgetFlags) { /* 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). */
*aWidgetFlags = GetTabMarginPixels(aFrame);
if (IsSelectedTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
if (IsFirstTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
}
} break; case StyleAppearance::Splitter: if (IsHorizontal(aFrame))
aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL; else
aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL; break; case StyleAppearance::MozWindowTitlebar:
aGtkWidgetType = MOZ_GTK_HEADER_BAR; break; case StyleAppearance::MozWindowDecorations:
aGtkWidgetType = MOZ_GTK_WINDOW_DECORATION; break; case StyleAppearance::MozWindowTitlebarMaximized:
aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED; break; case StyleAppearance::MozWindowButtonClose:
aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE; break; case StyleAppearance::MozWindowButtonMinimize:
aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE; break; case StyleAppearance::MozWindowButtonMaximize:
aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE; break; case StyleAppearance::MozWindowButtonRestore:
aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE; break; default: returnfalse;
}
returntrue;
}
class SystemCairoClipper : public ClipExporter { public: explicit SystemCairoClipper(cairo_t* aContext, gint aScaleFactor = 1)
: mContext(aContext), mScaleFactor(aScaleFactor) {}
void BeginClip(const Matrix& aTransform) override {
cairo_matrix_t mat;
GfxMatrixToCairoMatrix(aTransform, mat); // We also need to remove the scale factor effect from the matrix
mat.y0 = mat.y0 / mScaleFactor;
mat.x0 = mat.x0 / mScaleFactor;
cairo_set_matrix(mContext, &mat);
Point drawOffsetScaled;
Point drawOffsetOriginal;
Matrix transform; if (!aSnapped) { // If we are not snapped, we depend on the DT for translation.
drawOffsetOriginal = aDrawOrigin;
drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
: drawOffsetOriginal;
transform = aDrawTarget->GetTransform().PreTranslate(drawOffsetScaled);
} else { // Otherwise, we only need to take the device offset into account.
drawOffsetOriginal = aDrawOrigin - aContext->GetDeviceOffset();
drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
: drawOffsetOriginal;
transform = Matrix::Translation(drawOffsetScaled);
}
if (!useHiDPIWidgets && aScaleFactor != 1) {
transform.PreScale(aScaleFactor, aScaleFactor);
}
// A direct Cairo draw target is not available, so we need to create a // temporary one. #ifdefined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE) if (GdkIsX11Display()) { // If using a Cairo xlib surface, then try to reuse it.
BorrowedXlibDrawable borrow(aDrawTarget); if (Drawable drawable = borrow.GetDrawable()) {
nsIntSize size = borrow.GetSize();
cairo_surface_t* surf = cairo_xlib_surface_create(
borrow.GetDisplay(), drawable, borrow.GetVisual(), size.width,
size.height); if (!NS_WARN_IF(!surf)) {
Point offset = borrow.GetOffset(); if (offset != Point()) {
cairo_surface_set_device_offset(surf, offset.x, offset.y);
}
cairo_t* cr = cairo_create(surf); if (!NS_WARN_IF(!cr)) {
RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
aContext->ExportClip(*clipper);
// Unmap the surface before using it as a source
dataSurface->Unmap();
if (cr) { // The widget either needs to be masked or has transparency, so use the // slower drawing path.
aDrawTarget->DrawSurface(
dataSurface,
Rect(aSnapped ? drawOffsetOriginal -
aDrawTarget->GetTransform().GetTranslation()
: drawOffsetOriginal,
Size(aDrawSize)),
Rect(0, 0, aDrawSize.width, aDrawSize.height));
cairo_destroy(cr);
}
if (surf) {
cairo_surface_destroy(surf);
}
}
}
}
CSSIntMargin nsNativeThemeGTK::GetExtraSizeForWidget(
nsIFrame* aFrame, StyleAppearance aAppearance) {
CSSIntMargin extra; // Allow an extra one pixel above and below the thumb for certain // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least); // We modify the frame's overflow area. See bug 297508. switch (aAppearance) { case StyleAppearance::Button: { if (IsDefaultButton(aFrame)) { // Some themes draw a default indicator outside the widget, // include that in overflow
moz_gtk_button_get_default_overflow(&extra.top.value, &extra.left.value,
&extra.bottom.value,
&extra.right.value); break;
} return {};
} default: return {};
} return extra;
}
gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
// Align to device pixels where sensible // to provide crisper and faster drawing. // Don't snap if it's a non-unit scale factor. We're going to have to take // slow paths then in any case. // We prioritize the size when snapping in order to avoid distorting widgets // that should be square, which can occur if edges are snapped independently. bool snapped = ctx->UserToDevicePixelSnapped(
rect, gfxContext::SnapOption::PrioritizeSize); if (snapped) { // Leave rect in device coords but make dirtyRect consistent.
dirtyRect = ctx->UserToDevice(dirtyRect);
}
// Translate the dirty rect so that it is wrt the widget top-left.
dirtyRect.MoveBy(-rect.TopLeft()); // Round out the dirty rect to gdk pixels to ensure that gtk draws // enough pixels for interpolation to device pixels.
dirtyRect.RoundOut();
// GTK themes can only draw an integer number of pixels // (even when not snapped).
LayoutDeviceIntRect widgetRect(0, 0, NS_lround(rect.Width()),
NS_lround(rect.Height()));
// This is the rectangle that will actually be drawn, in gdk pixels
LayoutDeviceIntRect drawingRect(
int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
int32_t(dirtyRect.Width()), int32_t(dirtyRect.Height())); if (widgetRect.IsEmpty() ||
!drawingRect.IntersectRect(widgetRect, drawingRect)) { return NS_OK;
}
NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance), "Trying to render an unsafe widget!");
// gdk rectangles are wrt the drawing rect. auto scaleFactor = GetWidgetScaleFactor(aFrame);
LayoutDeviceIntRect gdkDevRect(-drawingRect.TopLeft(), widgetRect.Size());
// Save actual widget scale to GtkWidgetState as we don't provide // the frame to gtk3drawing routines.
state.image_scale = std::ceil(scaleFactor.scale);
// translate everything so (0,0) is the top left of the drawingRect
gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft().ToUnknownPoint();
if (!safeState) { // gdk_flush() call from expose event crashes Gtk+ on Wayland // (Gnome BZ #773307) if (GdkIsX11Display()) {
gdk_flush();
}
gLastGdkError = gdk_error_trap_pop();
if (gLastGdkError) { #ifdef DEBUG
printf( "GTK theme failed for widget type %d, error was %d, state was " "[active=%d,focused=%d,inHover=%d,disabled=%d]\n", static_cast<int>(aAppearance), gLastGdkError, state.active,
state.focused, state.inHover, state.disabled); #endif
NS_WARNING("GTK theme failed; disabling unsafe widget");
SetWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance); // force refresh of the window, because the widget was not // successfully drawn it must be redrawn using the default look
RefreshWidgetWindow(aFrame);
} else {
SetWidgetStateSafe(mSafeWidgetStates, aAppearance, &state);
}
}
// Indeterminate progress bar are animated. if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) { if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
NS_WARNING("unable to animate widget!");
}
}
CSSIntMargin result;
GtkTextDirection direction = GetTextDirection(aFrame); switch (aAppearance) { case StyleAppearance::Dualbutton: // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw // around the entire button + dropdown, and also an inner border if you're // over the button part. But, we want the inner button to be right up // against the edge of the outer button so that the borders overlap. // To make this happen, we draw a button border for the outer button, // but don't reserve any space for it. break; case StyleAppearance::Tab: {
WidgetNodeType gtkWidgetType;
gint flags;
bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
nsIFrame* aFrame,
StyleAppearance aAppearance,
LayoutDeviceIntMargin* aResult) { if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) { return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
} switch (aAppearance) { case StyleAppearance::Toolbarbutton: case StyleAppearance::Tooltip: case StyleAppearance::MozWindowButtonClose: case StyleAppearance::MozWindowButtonMinimize: case StyleAppearance::MozWindowButtonMaximize: case StyleAppearance::MozWindowButtonRestore: case StyleAppearance::Dualbutton: case StyleAppearance::TabScrollArrowBack: case StyleAppearance::TabScrollArrowForward: case StyleAppearance::ToolbarbuttonDropdown: case StyleAppearance::ButtonArrowUp: case StyleAppearance::ButtonArrowDown: case StyleAppearance::ButtonArrowNext: case StyleAppearance::ButtonArrowPrevious: case StyleAppearance::RangeThumb: // Radios and checkboxes return a fixed size in GetMinimumWidgetSize // and have a meaningful baseline, so they can't have // author-specified padding. case StyleAppearance::Checkbox: case StyleAppearance::Radio:
aResult->SizeTo(0, 0, 0, 0); returntrue; default: break;
}
auto nsNativeThemeGTK::IsWidgetNonNative(
nsIFrame* aFrame, StyleAppearance aAppearance) -> NonNative { if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) { return NonNative::Always;
}
// If the current GTK theme color scheme matches our color-scheme, then we // can draw a native widget. if (LookAndFeel::ColorSchemeForFrame(aFrame) ==
PreferenceSheet::ColorSchemeForChrome()) { return NonNative::No;
}
// As an special-case, for tooltips, we check if the tooltip color is the // same between the light and dark themes. If so we can get away with drawing // the native widget, see bug 1817396. if (aAppearance == StyleAppearance::Tooltip) { auto darkColor =
LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Dark,
LookAndFeel::UseStandins::No); auto lightColor =
LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Light,
LookAndFeel::UseStandins::No); if (darkColor == lightColor) { return NonNative::No;
}
}
// If the non-native theme doesn't support the widget then oh well... if (!Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance)) { return NonNative::No;
}
// Scale the min content height proportionately with the font-size if it's // smaller than the default one. This prevents <input type=text // style="font-size: .5em"> from keeping a ridiculously large size, for // example. const gfxFloat fieldFontSizeInCSSPixels = [] {
gfxFontStyle fieldFontStyle;
nsAutoString unusedFontName;
DebugOnly<bool> result = LookAndFeel::GetFont(
LookAndFeel::FontID::MozField, unusedFontName, fieldFontStyle);
MOZ_ASSERT(result, "GTK look and feel supports the field font"); // NOTE: GetFont returns font sizes in CSS pixels, and we want just // that. return fieldFontStyle.size;
}();
if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) { return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
}
switch (aAppearance) { // Combobox dropdowns don't support native theming in vertical mode. case StyleAppearance::Menulist: case StyleAppearance::MenulistButton: if (aFrame && aFrame->GetWritingMode().IsVertical()) { returnfalse;
}
[[fallthrough]];
case StyleAppearance::Button: case StyleAppearance::Radio: case StyleAppearance::Checkbox: case StyleAppearance::Toolbarbutton: case StyleAppearance::Dualbutton: // so we can override the border with 0 case StyleAppearance::ToolbarbuttonDropdown: case StyleAppearance::ButtonArrowUp: case StyleAppearance::ButtonArrowDown: case StyleAppearance::ButtonArrowNext: case StyleAppearance::ButtonArrowPrevious: case StyleAppearance::Listbox: case StyleAppearance::ProgressBar: case StyleAppearance::Progresschunk: case StyleAppearance::Tab: // case StyleAppearance::Tabpanel: case StyleAppearance::Tabpanels: case StyleAppearance::TabScrollArrowBack: case StyleAppearance::TabScrollArrowForward: case StyleAppearance::Tooltip: case StyleAppearance::Spinner: case StyleAppearance::SpinnerUpbutton: case StyleAppearance::SpinnerDownbutton: case StyleAppearance::SpinnerTextfield: case StyleAppearance::NumberInput: case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: case StyleAppearance::Range: case StyleAppearance::RangeThumb: case StyleAppearance::Splitter: case StyleAppearance::MozWindowButtonClose: case StyleAppearance::MozWindowButtonMinimize: case StyleAppearance::MozWindowButtonMaximize: case StyleAppearance::MozWindowButtonRestore: case StyleAppearance::MozWindowTitlebar: case StyleAppearance::MozWindowTitlebarMaximized: case StyleAppearance::MozWindowDecorations: return !IsWidgetStyled(aPresContext, aFrame, aAppearance); default: break;
}
returnfalse;
}
NS_IMETHODIMP_(bool)
nsNativeThemeGTK::WidgetIsContainer(StyleAppearance aAppearance) { // XXXdwh At some point flesh all of this out. if (aAppearance == StyleAppearance::Radio ||
aAppearance == StyleAppearance::RangeThumb ||
aAppearance == StyleAppearance::Checkbox ||
aAppearance == StyleAppearance::TabScrollArrowBack ||
aAppearance == StyleAppearance::TabScrollArrowForward ||
aAppearance == StyleAppearance::ButtonArrowUp ||
aAppearance == StyleAppearance::ButtonArrowDown ||
aAppearance == StyleAppearance::ButtonArrowNext ||
aAppearance == StyleAppearance::ButtonArrowPrevious) returnfalse; returntrue;
}
bool nsNativeThemeGTK::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
StyleAppearance aAppearance) { if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) { return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
} switch (aAppearance) { case StyleAppearance::Checkbox: case StyleAppearance::Radio: // These are drawn only for non-XUL elements, but in XUL the label has // the focus ring. returntrue; case StyleAppearance::Button: case StyleAppearance::Menulist: case StyleAppearance::MenulistButton: case StyleAppearance::Textarea: case StyleAppearance::Textfield: case StyleAppearance::NumberInput: case StyleAppearance::PasswordInput: returntrue; default: returnfalse;
}
}
switch (aAppearance) { // Tooltips use gtk_paint_flat_box() on Gtk2 // but are shaped on Gtk3 case StyleAppearance::Tooltip: return eTransparent; default: return eUnknownTransparency;
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.35Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.