зеркало из https://github.com/mozilla/gecko-dev.git
4393 строки
170 KiB
Plaintext
4393 строки
170 KiB
Plaintext
/* -*- 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/. */
|
|
|
|
#include "nsNativeThemeCocoa.h"
|
|
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/gfx/Helpers.h"
|
|
#include "nsChildView.h"
|
|
#include "nsDeviceContext.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsObjCExceptions.h"
|
|
#include "nsNumberControlFrame.h"
|
|
#include "nsRangeFrame.h"
|
|
#include "nsRect.h"
|
|
#include "nsSize.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsIContent.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsAtom.h"
|
|
#include "nsNameSpaceManager.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsCocoaFeatures.h"
|
|
#include "nsCocoaWindow.h"
|
|
#include "nsNativeBasicTheme.h"
|
|
#include "nsNativeThemeColors.h"
|
|
#include "nsIScrollableFrame.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/EventStates.h"
|
|
#include "mozilla/Range.h"
|
|
#include "mozilla/RelativeLuminanceUtils.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/HTMLMeterElement.h"
|
|
#include "mozilla/layers/StackingContextHelper.h"
|
|
#include "mozilla/StaticPrefs_layout.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "nsLookAndFeel.h"
|
|
#include "VibrancyManager.h"
|
|
|
|
#include "gfxContext.h"
|
|
#include "gfxQuartzSurface.h"
|
|
#include "gfxQuartzNativeDrawing.h"
|
|
#include "gfxUtils.h" // for ToDeviceColor
|
|
#include <algorithm>
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
using mozilla::dom::HTMLMeterElement;
|
|
|
|
#define DRAW_IN_FRAME_DEBUG 0
|
|
#define SCROLLBARS_VISUAL_DEBUG 0
|
|
|
|
// private Quartz routines needed here
|
|
extern "C" {
|
|
CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
|
|
CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
|
|
typedef CFTypeRef CUIRendererRef;
|
|
void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
|
|
CFDictionaryRef* result);
|
|
}
|
|
|
|
// Workaround for NSCell control tint drawing
|
|
// Without this workaround, NSCells are always drawn with the clear control tint
|
|
// as long as they're not attached to an NSControl which is a subview of an active window.
|
|
// XXXmstange Why doesn't Webkit need this?
|
|
@implementation NSCell (ControlTintWorkaround)
|
|
- (int)_realControlTint {
|
|
return [self controlTint];
|
|
}
|
|
@end
|
|
|
|
// The purpose of this class is to provide objects that can be used when drawing
|
|
// NSCells using drawWithFrame:inView: without causing any harm. The only
|
|
// messages that will be sent to such an object are "isFlipped" and
|
|
// "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
|
|
// on 10.4 (see bug 465069); currentEditor (which isn't even a method of
|
|
// NSView) will be called when drawing search fields, and we only provide it in
|
|
// order to prevent "unrecognized selector" exceptions.
|
|
// There's no need to pass the actual NSView that we're drawing into to
|
|
// drawWithFrame:inView:. What's more, doing so even causes unnecessary
|
|
// invalidations as soon as we draw a focusring!
|
|
@interface CellDrawView : NSView
|
|
|
|
@end
|
|
|
|
@implementation CellDrawView
|
|
|
|
- (BOOL)isFlipped {
|
|
return YES;
|
|
}
|
|
|
|
- (NSText*)currentEditor {
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
// These two classes don't actually add any behavior over NSButtonCell. Their
|
|
// purpose is to make it easy to distinguish NSCell objects that are used for
|
|
// drawing radio buttons / checkboxes from other cell types.
|
|
// The class names are made up, there are no classes with these names in AppKit.
|
|
// The reason we need them is that calling [cell setButtonType:NSRadioButton]
|
|
// doesn't leave an easy-to-check "marker" on the cell object - there is no
|
|
// -[NSButtonCell buttonType] method.
|
|
@interface RadioButtonCell : NSButtonCell
|
|
@end
|
|
|
|
@implementation RadioButtonCell
|
|
@end
|
|
|
|
@interface CheckboxCell : NSButtonCell
|
|
@end
|
|
|
|
@implementation CheckboxCell
|
|
@end
|
|
|
|
static void DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
|
|
if ([aCell showsFirstResponder]) {
|
|
CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
|
CGContextSaveGState(cgContext);
|
|
|
|
// It's important to set the focus ring style before we enter the
|
|
// transparency layer so that the transparency layer only contains
|
|
// the normal button mask without the focus ring, and the conversion
|
|
// to the focus ring shape happens only when the transparency layer is
|
|
// ended.
|
|
NSSetFocusRingStyle(NSFocusRingOnly);
|
|
|
|
// We need to draw the whole button into a transparency layer because
|
|
// many button types are composed of multiple parts, and if these parts
|
|
// were drawn while the focus ring style was active, each individual part
|
|
// would produce a focus ring for itself. But we only want one focus ring
|
|
// for the whole button. The transparency layer is a way to merge the
|
|
// individual button parts together before the focus ring shape is
|
|
// calculated.
|
|
CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
|
|
[aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
|
|
CGContextEndTransparencyLayer(cgContext);
|
|
|
|
CGContextRestoreGState(cgContext);
|
|
}
|
|
}
|
|
|
|
static bool FocusIsDrawnByDrawWithFrame(NSCell* aCell) {
|
|
#if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
|
|
// When building with the 10.8 SDK or higher, focus rings don't draw as part
|
|
// of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
|
|
// to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
|
|
// See the NSButtonCell section under
|
|
// https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
|
|
return false;
|
|
#else
|
|
if (!nsCocoaFeatures::OnYosemiteOrLater()) {
|
|
// When building with the 10.7 SDK or lower, focus rings always draw as
|
|
// part of -[NSCell drawWithFrame:inView:] if the build is run on 10.9 or
|
|
// lower.
|
|
return true;
|
|
}
|
|
|
|
// On 10.10, whether the focus ring is drawn as part of
|
|
// -[NSCell drawWithFrame:inView:] depends on the cell type.
|
|
// Radio buttons and checkboxes draw their own focus rings, other cell
|
|
// types need -[NSCell drawFocusRingMaskWithFrame:inView:].
|
|
return
|
|
[aCell isKindOfClass:[RadioButtonCell class]] || [aCell isKindOfClass:[CheckboxCell class]];
|
|
#endif
|
|
}
|
|
|
|
static void DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView) {
|
|
[aCell drawWithFrame:aWithFrame inView:aInView];
|
|
|
|
if (!FocusIsDrawnByDrawWithFrame(aCell)) {
|
|
DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* NSProgressBarCell is used to draw progress bars of any size.
|
|
*/
|
|
@interface NSProgressBarCell : NSCell {
|
|
/*All instance variables are private*/
|
|
double mValue;
|
|
double mMax;
|
|
bool mIsIndeterminate;
|
|
bool mIsHorizontal;
|
|
}
|
|
|
|
- (void)setValue:(double)value;
|
|
- (double)value;
|
|
- (void)setMax:(double)max;
|
|
- (double)max;
|
|
- (void)setIndeterminate:(bool)aIndeterminate;
|
|
- (bool)isIndeterminate;
|
|
- (void)setHorizontal:(bool)aIsHorizontal;
|
|
- (bool)isHorizontal;
|
|
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView;
|
|
@end
|
|
|
|
@implementation NSProgressBarCell
|
|
|
|
- (void)setMax:(double)aMax {
|
|
mMax = aMax;
|
|
}
|
|
|
|
- (double)max {
|
|
return mMax;
|
|
}
|
|
|
|
- (void)setValue:(double)aValue {
|
|
mValue = aValue;
|
|
}
|
|
|
|
- (double)value {
|
|
return mValue;
|
|
}
|
|
|
|
- (void)setIndeterminate:(bool)aIndeterminate {
|
|
mIsIndeterminate = aIndeterminate;
|
|
}
|
|
|
|
- (bool)isIndeterminate {
|
|
return mIsIndeterminate;
|
|
}
|
|
|
|
- (void)setHorizontal:(bool)aIsHorizontal {
|
|
mIsHorizontal = aIsHorizontal;
|
|
}
|
|
|
|
- (bool)isHorizontal {
|
|
return mIsHorizontal;
|
|
}
|
|
|
|
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
|
|
CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
|
|
|
HIThemeTrackDrawInfo tdi;
|
|
|
|
tdi.version = 0;
|
|
tdi.min = 0;
|
|
|
|
tdi.value = INT32_MAX * (mValue / mMax);
|
|
tdi.max = INT32_MAX;
|
|
tdi.bounds = NSRectToCGRect(cellFrame);
|
|
tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
|
|
tdi.enableState =
|
|
[self controlTint] == NSClearControlTint ? kThemeTrackInactive : kThemeTrackActive;
|
|
|
|
NSControlSize size = [self controlSize];
|
|
if (size == NSRegularControlSize) {
|
|
tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar : kThemeLargeProgressBar;
|
|
} else {
|
|
NS_ASSERTION(size == NSSmallControlSize,
|
|
"We shouldn't have another size than small and regular for the moment");
|
|
tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar : kThemeMediumProgressBar;
|
|
}
|
|
|
|
int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
|
|
int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
|
|
tdi.trackInfo.progress.phase =
|
|
uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) / milliSecondsPerStep);
|
|
|
|
HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
|
|
}
|
|
|
|
@end
|
|
|
|
@interface SearchFieldCellWithFocusRing : NSSearchFieldCell {
|
|
}
|
|
@end
|
|
|
|
// Workaround for Bug 542048
|
|
// On 64-bit, NSSearchFieldCells don't draw focus rings.
|
|
#if defined(__x86_64__)
|
|
|
|
@implementation SearchFieldCellWithFocusRing
|
|
|
|
- (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView {
|
|
[super drawWithFrame:rect inView:controlView];
|
|
|
|
if (FocusIsDrawnByDrawWithFrame(self)) {
|
|
// For some reason, -[NSSearchFieldCell drawWithFrame:inView] doesn't draw a
|
|
// focus ring in 64 bit mode, no matter what SDK is used or what OS X version
|
|
// we're running on. But if FocusIsDrawnByDrawWithFrame(self), then our
|
|
// caller expects us to draw a focus ring. So we just do that here.
|
|
DrawFocusRingForCellIfNeeded(self, rect, controlView);
|
|
}
|
|
}
|
|
|
|
- (void)drawFocusRingMaskWithFrame:(NSRect)rect inView:(NSView*)controlView {
|
|
// By default this draws nothing. I don't know why.
|
|
// We just draw the search field again. It's a great mask shape for its own
|
|
// focus ring.
|
|
[super drawWithFrame:rect inView:controlView];
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|
|
|
|
@interface ToolbarSearchFieldCellWithFocusRing : SearchFieldCellWithFocusRing
|
|
@end
|
|
|
|
@implementation ToolbarSearchFieldCellWithFocusRing
|
|
|
|
- (BOOL)_isToolbarMode {
|
|
// This function is called during -[NSSearchFieldCell drawWithFrame:inView:].
|
|
// Returning YES from it selects the style that's appropriate for search
|
|
// fields inside toolbars.
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
#define HITHEME_ORIENTATION kHIThemeOrientationNormal
|
|
|
|
static CGFloat kMaxFocusRingWidth = 0; // initialized by the nsNativeThemeCocoa constructor
|
|
|
|
// These enums are for indexing into the margin array.
|
|
enum {
|
|
leopardOSorlater = 0, // 10.6 - 10.9
|
|
yosemiteOSorlater = 1 // 10.10+
|
|
};
|
|
|
|
enum { miniControlSize, smallControlSize, regularControlSize };
|
|
|
|
enum { leftMargin, topMargin, rightMargin, bottomMargin };
|
|
|
|
static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
|
|
if (cocoaControlSize == NSMiniControlSize)
|
|
return miniControlSize;
|
|
else if (cocoaControlSize == NSSmallControlSize)
|
|
return smallControlSize;
|
|
else
|
|
return regularControlSize;
|
|
}
|
|
|
|
static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
|
|
if (enumControlSize == miniControlSize)
|
|
return NSMiniControlSize;
|
|
else if (enumControlSize == smallControlSize)
|
|
return NSSmallControlSize;
|
|
else
|
|
return NSRegularControlSize;
|
|
}
|
|
|
|
static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) {
|
|
if (aControlSize == NSRegularControlSize)
|
|
return @"regular";
|
|
else if (aControlSize == NSSmallControlSize)
|
|
return @"small";
|
|
else
|
|
return @"mini";
|
|
}
|
|
|
|
static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize,
|
|
const float marginSet[][3][4]) {
|
|
if (!marginSet) return;
|
|
|
|
static int osIndex = nsCocoaFeatures::OnYosemiteOrLater() ? yosemiteOSorlater : leopardOSorlater;
|
|
size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
|
|
const float* buttonMargins = marginSet[osIndex][controlSize];
|
|
rect->origin.x -= buttonMargins[leftMargin];
|
|
rect->origin.y -= buttonMargins[bottomMargin];
|
|
rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
|
|
rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
|
|
}
|
|
|
|
static NSWindow* NativeWindowForFrame(nsIFrame* aFrame, nsIWidget** aTopLevelWidget = NULL) {
|
|
if (!aFrame) return nil;
|
|
|
|
nsIWidget* widget = aFrame->GetNearestWidget();
|
|
if (!widget) return nil;
|
|
|
|
nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
|
|
if (aTopLevelWidget) *aTopLevelWidget = topLevelWidget;
|
|
|
|
return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
|
|
}
|
|
|
|
static NSSize WindowButtonsSize(nsIFrame* aFrame) {
|
|
NSWindow* window = NativeWindowForFrame(aFrame);
|
|
if (!window) {
|
|
// Return fallback values.
|
|
return NSMakeSize(54, 16);
|
|
}
|
|
|
|
NSRect buttonBox = NSZeroRect;
|
|
NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
|
|
if (closeButton) {
|
|
buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
|
|
}
|
|
NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
|
|
if (minimizeButton) {
|
|
buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
|
|
}
|
|
NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
|
|
if (zoomButton) {
|
|
buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
|
|
}
|
|
return buttonBox.size;
|
|
}
|
|
|
|
static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) {
|
|
nsIWidget* topLevelWidget = NULL;
|
|
NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
|
|
if (!topLevelWidget || !win) return YES;
|
|
|
|
// XUL popups, e.g. the toolbar customization popup, can't become key windows,
|
|
// but controls in these windows should still get the active look.
|
|
if (topLevelWidget->WindowType() == eWindowType_popup) return YES;
|
|
if ([win isSheet]) return [win isKeyWindow];
|
|
return [win isMainWindow] && ![win attachedSheet];
|
|
}
|
|
|
|
// Toolbar controls and content controls respond to different window
|
|
// activeness states.
|
|
static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) {
|
|
if (aIsToolbarControl) return [NativeWindowForFrame(aFrame) isMainWindow];
|
|
return FrameIsInActiveWindow(aFrame);
|
|
}
|
|
|
|
static bool IsInSourceList(nsIFrame* aFrame) {
|
|
for (nsIFrame* frame = aFrame->GetParent(); frame; frame = frame->GetParent()) {
|
|
if (frame->StyleDisplay()->mAppearance == StyleAppearance::MozMacSourceList) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
|
|
|
|
nsNativeThemeCocoa::nsNativeThemeCocoa() {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
kMaxFocusRingWidth = nsCocoaFeatures::OnYosemiteOrLater() ? 7 : 4;
|
|
|
|
// provide a local autorelease pool, as this is called during startup
|
|
// before the main event-loop pool is in place
|
|
nsAutoreleasePool pool;
|
|
|
|
mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
|
|
[mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
|
|
[mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
|
|
[mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
|
|
|
|
mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
|
|
[mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
|
|
[mHelpButtonCell setButtonType:NSMomentaryPushInButton];
|
|
[mHelpButtonCell setHighlightsBy:NSPushInCellMask];
|
|
|
|
mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
|
|
[mPushButtonCell setButtonType:NSMomentaryPushInButton];
|
|
[mPushButtonCell setHighlightsBy:NSPushInCellMask];
|
|
|
|
mRadioButtonCell = [[RadioButtonCell alloc] initTextCell:@""];
|
|
[mRadioButtonCell setButtonType:NSRadioButton];
|
|
|
|
mCheckboxCell = [[CheckboxCell alloc] initTextCell:@""];
|
|
[mCheckboxCell setButtonType:NSSwitchButton];
|
|
[mCheckboxCell setAllowsMixedState:YES];
|
|
|
|
mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
|
|
[mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
|
|
[mSearchFieldCell setBezeled:YES];
|
|
[mSearchFieldCell setEditable:YES];
|
|
[mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
|
|
|
|
mToolbarSearchFieldCell = [[ToolbarSearchFieldCellWithFocusRing alloc] initTextCell:@""];
|
|
[mToolbarSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
|
|
[mToolbarSearchFieldCell setBezeled:YES];
|
|
[mToolbarSearchFieldCell setEditable:YES];
|
|
[mToolbarSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
|
|
|
|
mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
|
|
|
|
mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
|
|
[mComboBoxCell setBezeled:YES];
|
|
[mComboBoxCell setEditable:YES];
|
|
[mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
|
|
|
|
mProgressBarCell = [[NSProgressBarCell alloc] init];
|
|
|
|
mMeterBarCell = [[NSLevelIndicatorCell alloc]
|
|
initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
|
|
|
|
mCellDrawView = [[CellDrawView alloc] init];
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
nsNativeThemeCocoa::~nsNativeThemeCocoa() {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
[mMeterBarCell release];
|
|
[mProgressBarCell release];
|
|
[mDisclosureButtonCell release];
|
|
[mHelpButtonCell release];
|
|
[mPushButtonCell release];
|
|
[mRadioButtonCell release];
|
|
[mCheckboxCell release];
|
|
[mSearchFieldCell release];
|
|
[mToolbarSearchFieldCell release];
|
|
[mDropdownCell release];
|
|
[mComboBoxCell release];
|
|
[mCellDrawView release];
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
// Limit on the area of the target rect (in pixels^2) in
|
|
// DrawCellWithScaling() and DrawButton() and above which we
|
|
// don't draw the object into a bitmap buffer. This is to avoid crashes in
|
|
// [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
|
|
// CGContextDrawImage(), and also to avoid very poor drawing performance in
|
|
// CGContextDrawImage() when it scales the bitmap (particularly if xscale or
|
|
// yscale is less than but near 1 -- e.g. 0.9). This value was determined
|
|
// by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
|
|
// different amounts of RAM.
|
|
#define BITMAP_MAX_AREA 500000
|
|
|
|
static int GetBackingScaleFactorForRendering(CGContextRef cgContext) {
|
|
CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
|
|
CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
|
|
float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
|
|
fabs(transformedUserSpacePixel.size.height));
|
|
return maxScale > 1.0 ? 2 : 1;
|
|
}
|
|
|
|
/*
|
|
* Draw the given NSCell into the given cgContext.
|
|
*
|
|
* destRect - the size and position of the resulting control rectangle
|
|
* controlSize - the NSControlSize which will be given to the NSCell before
|
|
* asking it to render
|
|
* naturalSize - The natural dimensions of this control.
|
|
* If the control rect size is not equal to either of these, a scale
|
|
* will be applied to the context so that rendering the control at the
|
|
* natural size will result in it filling the destRect space.
|
|
* If a control has no natural dimensions in either/both axes, pass 0.0f.
|
|
* minimumSize - The minimum dimensions of this control.
|
|
* If the control rect size is less than the minimum for a given axis,
|
|
* a scale will be applied to the context so that the minimum is used
|
|
* for drawing. If a control has no minimum dimensions in either/both
|
|
* axes, pass 0.0f.
|
|
* marginSet - an array of margins; a multidimensional array of [2][3][4],
|
|
* with the first dimension being the OS version (Tiger or Leopard),
|
|
* the second being the control size (mini, small, regular), and the third
|
|
* being the 4 margin values (left, top, right, bottom).
|
|
* view - The NSView that we're drawing into. As far as I can tell, it doesn't
|
|
* matter if this is really the right view; it just has to return YES when
|
|
* asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
|
|
* mirrorHorizontal - whether to mirror the cell horizontally
|
|
*/
|
|
static void DrawCellWithScaling(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
|
|
NSControlSize controlSize, NSSize naturalSize, NSSize minimumSize,
|
|
const float marginSet[][3][4], NSView* view,
|
|
BOOL mirrorHorizontal) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
NSRect drawRect =
|
|
NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
|
|
|
|
if (naturalSize.width != 0.0f) drawRect.size.width = naturalSize.width;
|
|
if (naturalSize.height != 0.0f) drawRect.size.height = naturalSize.height;
|
|
|
|
// Keep aspect ratio when scaling if one dimension is free.
|
|
if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
|
|
drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
|
|
if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
|
|
drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
|
|
|
|
// Honor minimum sizes.
|
|
if (drawRect.size.width < minimumSize.width) drawRect.size.width = minimumSize.width;
|
|
if (drawRect.size.height < minimumSize.height) drawRect.size.height = minimumSize.height;
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
|
|
// Only skip the buffer if the area of our cell (in pixels^2) is too large.
|
|
if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
|
|
// Inflate the rect Gecko gave us by the margin for the control.
|
|
InflateControlRect(&drawRect, controlSize, marginSet);
|
|
|
|
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
|
|
[NSGraphicsContext
|
|
setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
|
|
flipped:YES]];
|
|
|
|
DrawCellIncludingFocusRing(cell, drawRect, view);
|
|
|
|
[NSGraphicsContext setCurrentContext:savedContext];
|
|
} else {
|
|
float w = ceil(drawRect.size.width);
|
|
float h = ceil(drawRect.size.height);
|
|
NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
|
|
|
|
// inflate to figure out the frame we need to tell NSCell to draw in, to get something that's
|
|
// 0,0,w,h
|
|
InflateControlRect(&tmpRect, controlSize, marginSet);
|
|
|
|
// and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
|
|
w += kMaxFocusRingWidth * 2.0;
|
|
h += kMaxFocusRingWidth * 2.0;
|
|
|
|
int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
|
|
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
|
|
CGContextRef ctx = CGBitmapContextCreate(
|
|
NULL, (int)w * backingScaleFactor, (int)h * backingScaleFactor, 8,
|
|
(int)w * backingScaleFactor * 4, rgb, kCGImageAlphaPremultipliedFirst);
|
|
CGColorSpaceRelease(rgb);
|
|
|
|
// We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
|
|
// This is the first flip transform, applied to cgContext.
|
|
CGContextScaleCTM(cgContext, 1.0f, -1.0f);
|
|
CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
|
|
if (mirrorHorizontal) {
|
|
CGContextScaleCTM(cgContext, -1.0f, 1.0f);
|
|
CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
|
|
}
|
|
|
|
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
|
|
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx
|
|
flipped:YES]];
|
|
|
|
CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
|
|
|
|
// Set the context's "base transform" to in order to get correctly-sized focus rings.
|
|
CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
|
|
|
|
// This is the second flip transform, applied to ctx.
|
|
CGContextScaleCTM(ctx, 1.0f, -1.0f);
|
|
CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
|
|
|
|
DrawCellIncludingFocusRing(cell, tmpRect, view);
|
|
|
|
[NSGraphicsContext setCurrentContext:savedContext];
|
|
|
|
CGImageRef img = CGBitmapContextCreateImage(ctx);
|
|
|
|
// Drop the image into the original destination rectangle, scaling to fit
|
|
// Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
|
|
// doesn't extend beyond the overflow rect
|
|
float xscale = destRect.size.width / drawRect.size.width;
|
|
float yscale = destRect.size.height / drawRect.size.height;
|
|
float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
|
|
float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
|
|
CGContextDrawImage(
|
|
cgContext,
|
|
CGRectMake(destRect.origin.x - scaledFocusRingX, destRect.origin.y - scaledFocusRingY,
|
|
destRect.size.width + scaledFocusRingX * 2,
|
|
destRect.size.height + scaledFocusRingY * 2),
|
|
img);
|
|
|
|
CGImageRelease(img);
|
|
CGContextRelease(ctx);
|
|
}
|
|
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
#if DRAW_IN_FRAME_DEBUG
|
|
CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
|
|
CGContextFillRect(cgContext, destRect);
|
|
#endif
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
struct CellRenderSettings {
|
|
// The natural dimensions of the control.
|
|
// If a control has no natural dimensions in either/both axes, set to 0.0f.
|
|
NSSize naturalSizes[3];
|
|
|
|
// The minimum dimensions of the control.
|
|
// If a control has no minimum dimensions in either/both axes, set to 0.0f.
|
|
NSSize minimumSizes[3];
|
|
|
|
// A three-dimensional array,
|
|
// with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
|
|
// the second being the control size (mini, small, regular), and the third
|
|
// being the 4 margin values (left, top, right, bottom).
|
|
float margins[2][3][4];
|
|
};
|
|
|
|
/*
|
|
* This is a helper method that returns the required NSControlSize given a size
|
|
* and the size of the three controls plus a tolerance.
|
|
* size - The width or the height of the element to draw.
|
|
* sizes - An array with the all the width/height of the element for its
|
|
* different sizes.
|
|
* tolerance - The tolerance as passed to DrawCellWithSnapping.
|
|
* NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
|
|
*/
|
|
static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance) {
|
|
for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
|
|
if (sizes[i] == 0) {
|
|
continue;
|
|
}
|
|
|
|
CGFloat next = 0;
|
|
// Find next value.
|
|
for (uint32_t j = i + 1; j <= regularControlSize; ++j) {
|
|
if (sizes[j] != 0) {
|
|
next = sizes[j];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If it's the latest value, we pick it.
|
|
if (next == 0) {
|
|
return CocoaSizeForEnum(i);
|
|
}
|
|
|
|
if (size <= sizes[i] + tolerance && size < next) {
|
|
return CocoaSizeForEnum(i);
|
|
}
|
|
}
|
|
|
|
// If we are here, that means sizes[] was an array with only empty values
|
|
// or the algorithm above is wrong.
|
|
// The former can happen but the later would be wrong.
|
|
NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
|
|
"We found no control! We shouldn't be there!");
|
|
return CocoaSizeForEnum(regularControlSize);
|
|
}
|
|
|
|
/*
|
|
* Draw the given NSCell into the given cgContext with a nice control size.
|
|
*
|
|
* This function is similar to DrawCellWithScaling, but it decides what
|
|
* control size to use based on the destRect's size.
|
|
* Scaling is only applied when the difference between the destRect's size
|
|
* and the next smaller natural size is greater than snapTolerance. Otherwise
|
|
* it snaps to the next smaller control size without scaling because unscaled
|
|
* controls look nicer.
|
|
*/
|
|
static void DrawCellWithSnapping(NSCell* cell, CGContextRef cgContext, const HIRect& destRect,
|
|
const CellRenderSettings settings, float verticalAlignFactor,
|
|
NSView* view, BOOL mirrorHorizontal, float snapTolerance = 2.0f) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
|
|
const NSSize* sizes = settings.naturalSizes;
|
|
const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
|
|
const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
|
|
const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
|
|
|
|
HIRect drawRect = destRect;
|
|
|
|
CGFloat controlWidths[3] = {miniSize.width, smallSize.width, regularSize.width};
|
|
NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
|
|
CGFloat controlHeights[3] = {miniSize.height, smallSize.height, regularSize.height};
|
|
NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
|
|
|
|
NSControlSize controlSize = NSRegularControlSize;
|
|
size_t sizeIndex = 0;
|
|
|
|
// At some sizes, don't scale but snap.
|
|
const NSControlSize smallerControlSize =
|
|
EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ? controlSizeX
|
|
: controlSizeY;
|
|
const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
|
|
const NSSize size = sizes[smallerControlSizeIndex];
|
|
float diffWidth = size.width ? rectWidth - size.width : 0.0f;
|
|
float diffHeight = size.height ? rectHeight - size.height : 0.0f;
|
|
if (diffWidth >= 0.0f && diffHeight >= 0.0f && diffWidth <= snapTolerance &&
|
|
diffHeight <= snapTolerance) {
|
|
// Snap to the smaller control size.
|
|
controlSize = smallerControlSize;
|
|
sizeIndex = smallerControlSizeIndex;
|
|
MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
|
|
|
|
// Resize and center the drawRect.
|
|
if (sizes[sizeIndex].width) {
|
|
drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
|
|
drawRect.size.width = sizes[sizeIndex].width;
|
|
}
|
|
if (sizes[sizeIndex].height) {
|
|
drawRect.origin.y +=
|
|
floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
|
|
drawRect.size.height = sizes[sizeIndex].height;
|
|
}
|
|
} else {
|
|
// Use the larger control size.
|
|
controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY)
|
|
? controlSizeX
|
|
: controlSizeY;
|
|
sizeIndex = EnumSizeForCocoaSize(controlSize);
|
|
}
|
|
|
|
[cell setControlSize:controlSize];
|
|
|
|
MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
|
|
const NSSize minimumSize = settings.minimumSizes[sizeIndex];
|
|
DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex], minimumSize,
|
|
settings.margins, view, mirrorHorizontal);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
@interface NSWindow (CoreUIRendererPrivate)
|
|
+ (CUIRendererRef)coreUIRenderer;
|
|
@end
|
|
|
|
static id GetAquaAppearance() {
|
|
// We only need NSAppearance on 10.10 and up.
|
|
if (nsCocoaFeatures::OnYosemiteOrLater()) {
|
|
Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
|
|
if (NSAppearanceClass && [NSAppearanceClass respondsToSelector:@selector(appearanceNamed:)]) {
|
|
return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
|
|
withObject:@"NSAppearanceNameAqua"];
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
@interface NSObject (NSAppearanceCoreUIRendering)
|
|
- (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
|
|
@end
|
|
|
|
static void RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions,
|
|
bool aSkipAreaCheck = false) {
|
|
id appearance = GetAquaAppearance();
|
|
|
|
if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
|
|
return;
|
|
}
|
|
|
|
if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
|
|
// Render through NSAppearance on Mac OS 10.10 and up. This will call
|
|
// CUIDraw with a CoreUI renderer that will give us the correct 10.10
|
|
// style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
|
|
// renders 10.9-style widgets on 10.10.
|
|
[appearance _drawInRect:aRect context:cgContext options:aOptions];
|
|
} else {
|
|
// 10.9 and below
|
|
CUIRendererRef renderer =
|
|
[NSWindow respondsToSelector:@selector(coreUIRenderer)] ? [NSWindow coreUIRenderer] : nil;
|
|
CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
|
|
}
|
|
}
|
|
|
|
static float VerticalAlignFactor(nsIFrame* aFrame) {
|
|
if (!aFrame) return 0.5f; // default: center
|
|
|
|
const auto& va = aFrame->StyleDisplay()->mVerticalAlign;
|
|
auto kw = va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Middle;
|
|
switch (kw) {
|
|
case StyleVerticalAlignKeyword::Top:
|
|
case StyleVerticalAlignKeyword::TextTop:
|
|
return 0.0f;
|
|
|
|
case StyleVerticalAlignKeyword::Sub:
|
|
case StyleVerticalAlignKeyword::Super:
|
|
case StyleVerticalAlignKeyword::Middle:
|
|
case StyleVerticalAlignKeyword::MozMiddleWithBaseline:
|
|
return 0.5f;
|
|
|
|
case StyleVerticalAlignKeyword::Baseline:
|
|
case StyleVerticalAlignKeyword::Bottom:
|
|
case StyleVerticalAlignKeyword::TextBottom:
|
|
return 1.0f;
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("invalid vertical-align");
|
|
return 0.5f;
|
|
}
|
|
}
|
|
|
|
static void ApplyControlParamsToNSCell(nsNativeThemeCocoa::ControlParams aControlParams,
|
|
NSCell* aCell) {
|
|
[aCell setEnabled:!aControlParams.disabled];
|
|
[aCell setShowsFirstResponder:(aControlParams.focused && !aControlParams.disabled &&
|
|
aControlParams.insideActiveWindow)];
|
|
[aCell setHighlighted:aControlParams.pressed];
|
|
}
|
|
|
|
// These are the sizes that Gecko needs to request to draw if it wants
|
|
// to get a standard-sized Aqua radio button drawn. Note that the rects
|
|
// that draw these are actually a little bigger.
|
|
static const CellRenderSettings radioSettings = {{
|
|
NSMakeSize(11, 11), // mini
|
|
NSMakeSize(13, 13), // small
|
|
NSMakeSize(16, 16) // regular
|
|
},
|
|
{NSZeroSize, NSZeroSize, NSZeroSize},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{0, 1, 1, 1}, // small
|
|
{0, 0, 0, 0} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 2}, // small
|
|
{0, 0, 0, 0} // regular
|
|
}}};
|
|
|
|
static const CellRenderSettings checkboxSettings = {{
|
|
NSMakeSize(11, 11), // mini
|
|
NSMakeSize(13, 13), // small
|
|
NSMakeSize(16, 16) // regular
|
|
},
|
|
{NSZeroSize, NSZeroSize, NSZeroSize},
|
|
{{
|
|
// Leopard
|
|
{0, 1, 0, 0}, // mini
|
|
{0, 1, 0, 1}, // small
|
|
{0, 1, 0, 1} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 1, 0, 0}, // mini
|
|
{0, 1, 0, 1}, // small
|
|
{0, 1, 0, 1} // regular
|
|
}}};
|
|
|
|
static NSCellStateValue CellStateForCheckboxOrRadioState(
|
|
nsNativeThemeCocoa::CheckboxOrRadioState aState) {
|
|
switch (aState) {
|
|
case nsNativeThemeCocoa::CheckboxOrRadioState::eOff:
|
|
return NSOffState;
|
|
case nsNativeThemeCocoa::CheckboxOrRadioState::eOn:
|
|
return NSOnState;
|
|
case nsNativeThemeCocoa::CheckboxOrRadioState::eIndeterminate:
|
|
return NSMixedState;
|
|
}
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
|
|
const HIRect& inBoxRect,
|
|
const CheckboxOrRadioParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
NSButtonCell* cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
|
|
ApplyControlParamsToNSCell(aParams.controlParams, cell);
|
|
|
|
[cell setState:CellStateForCheckboxOrRadioState(aParams.state)];
|
|
[cell setControlTint:(aParams.controlParams.insideActiveWindow ? [NSColor currentControlTint]
|
|
: NSClearControlTint)];
|
|
|
|
// Ensure that the control is square.
|
|
float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
|
|
HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
|
|
inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
|
|
length, length);
|
|
|
|
DrawCellWithSnapping(cell, cgContext, drawRect, inCheckbox ? checkboxSettings : radioSettings,
|
|
aParams.verticalAlignFactor, mCellDrawView, NO);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
static const CellRenderSettings searchFieldSettings = {{
|
|
NSMakeSize(0, 16), // mini
|
|
NSMakeSize(0, 19), // small
|
|
NSMakeSize(0, 22) // regular
|
|
},
|
|
{
|
|
NSMakeSize(32, 0), // mini
|
|
NSMakeSize(38, 0), // small
|
|
NSMakeSize(44, 0) // regular
|
|
},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{0, 0, 0, 0}, // small
|
|
{0, 0, 0, 0} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 0, 0}, // mini
|
|
{0, 0, 0, 0}, // small
|
|
{0, 0, 0, 0} // regular
|
|
}}};
|
|
|
|
static bool IsToolbarStyleContainer(nsIFrame* aFrame) {
|
|
nsIContent* content = aFrame->GetContent();
|
|
if (!content) {
|
|
return false;
|
|
}
|
|
|
|
if (content->IsAnyOfXULElements(nsGkAtoms::toolbar, nsGkAtoms::toolbox, nsGkAtoms::statusbar)) {
|
|
return true;
|
|
}
|
|
|
|
switch (aFrame->StyleDisplay()->mAppearance) {
|
|
case StyleAppearance::Toolbar:
|
|
case StyleAppearance::Statusbar:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool IsInsideToolbar(nsIFrame* aFrame) {
|
|
for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
|
|
if (IsToolbarStyleContainer(frame)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
nsNativeThemeCocoa::SearchFieldParams nsNativeThemeCocoa::ComputeSearchFieldParams(
|
|
nsIFrame* aFrame, EventStates aEventState) {
|
|
SearchFieldParams params;
|
|
params.insideToolbar = IsInsideToolbar(aFrame);
|
|
params.disabled = IsDisabled(aFrame, aEventState);
|
|
params.focused = IsFocused(aFrame);
|
|
params.rtl = IsFrameRTL(aFrame);
|
|
params.verticalAlignFactor = VerticalAlignFactor(aFrame);
|
|
return params;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const SearchFieldParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
NSSearchFieldCell* cell = aParams.insideToolbar ? mToolbarSearchFieldCell : mSearchFieldCell;
|
|
[cell setEnabled:!aParams.disabled];
|
|
[cell setShowsFirstResponder:aParams.focused];
|
|
|
|
// When using the 10.11 SDK, the default string will be shown if we don't
|
|
// set the placeholder string.
|
|
[cell setPlaceholderString:@""];
|
|
|
|
DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings, aParams.verticalAlignFactor,
|
|
mCellDrawView, aParams.rtl);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
nsNativeThemeCocoa::MenuBackgroundParams nsNativeThemeCocoa::ComputeMenuBackgroundParams(
|
|
nsIFrame* aFrame, EventStates aEventState) {
|
|
MenuBackgroundParams params;
|
|
params.disabled = IsDisabled(aFrame, aEventState);
|
|
bool isLeftOfParent = false;
|
|
params.submenuRightOfParent = IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent;
|
|
return params;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawMenuBackground(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
const MenuBackgroundParams& aParams) {
|
|
HIThemeMenuDrawInfo mdi;
|
|
memset(&mdi, 0, sizeof(mdi));
|
|
mdi.version = 0;
|
|
mdi.menuType = aParams.disabled ? static_cast<ThemeMenuType>(kThemeMenuTypeInactive)
|
|
: static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
|
|
|
|
if (aParams.submenuRightOfParent) {
|
|
mdi.menuType = kThemeMenuTypeHierarchical;
|
|
}
|
|
|
|
// The rounded corners draw outside the frame.
|
|
CGRect deflatedRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y + 4, inBoxRect.size.width,
|
|
inBoxRect.size.height - 8);
|
|
HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
|
|
}
|
|
|
|
static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
|
|
static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
|
|
static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
|
|
static NSString* kCheckmarkImage = @"MenuOnState";
|
|
static NSString* kMenuarrowRightImage = @"MenuSubmenu";
|
|
static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
|
|
static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
|
|
static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
|
|
static const CGFloat kMenuIconIndent = 6.0f;
|
|
|
|
NSString* nsNativeThemeCocoa::GetMenuIconName(const MenuIconParams& aParams) {
|
|
switch (aParams.icon) {
|
|
case MenuIcon::eCheckmark:
|
|
return kCheckmarkImage;
|
|
case MenuIcon::eMenuArrow:
|
|
return aParams.rtl ? kMenuarrowLeftImage : kMenuarrowRightImage;
|
|
case MenuIcon::eMenuDownScrollArrow:
|
|
return kMenuDownScrollArrowImage;
|
|
case MenuIcon::eMenuUpScrollArrow:
|
|
return kMenuUpScrollArrowImage;
|
|
}
|
|
}
|
|
|
|
NSSize nsNativeThemeCocoa::GetMenuIconSize(MenuIcon aIcon) {
|
|
switch (aIcon) {
|
|
case MenuIcon::eCheckmark:
|
|
return kCheckmarkSize;
|
|
case MenuIcon::eMenuArrow:
|
|
return kMenuarrowSize;
|
|
case MenuIcon::eMenuDownScrollArrow:
|
|
case MenuIcon::eMenuUpScrollArrow:
|
|
return kMenuScrollArrowSize;
|
|
}
|
|
}
|
|
|
|
nsNativeThemeCocoa::MenuIconParams nsNativeThemeCocoa::ComputeMenuIconParams(
|
|
nsIFrame* aFrame, EventStates aEventState, MenuIcon aIcon) {
|
|
bool isDisabled = IsDisabled(aFrame, aEventState);
|
|
|
|
MenuIconParams params;
|
|
params.icon = aIcon;
|
|
params.disabled = isDisabled;
|
|
params.insideActiveMenuItem = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
|
|
params.centerHorizontally = true;
|
|
params.rtl = IsFrameRTL(aFrame);
|
|
return params;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
|
|
const MenuIconParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
NSSize size = GetMenuIconSize(aParams.icon);
|
|
|
|
// Adjust size and position of our drawRect.
|
|
CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - size.width);
|
|
CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - size.height);
|
|
CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
|
|
CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
|
|
CGRect drawRect = CGRectMake(
|
|
aRect.origin.x + (aParams.centerHorizontally ? ceil(paddingX / 2)
|
|
: aParams.rtl ? paddingEndX : paddingStartX),
|
|
aRect.origin.y + ceil(paddingY / 2), size.width, size.height);
|
|
|
|
NSString* state =
|
|
aParams.disabled ? @"disabled" : (aParams.insideActiveMenuItem ? @"pressed" : @"normal");
|
|
|
|
NSString* imageName = GetMenuIconName(aParams);
|
|
if (!nsCocoaFeatures::OnElCapitanOrLater()) {
|
|
// Pre-10.11, image names are prefixed with "image."
|
|
imageName = [@"image." stringByAppendingString:imageName];
|
|
}
|
|
|
|
RenderWithCoreUI(
|
|
drawRect, cgContext,
|
|
[NSDictionary dictionaryWithObjectsAndKeys:@"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
|
|
imageName, @"imageNameKey", state, @"state",
|
|
@"image", @"widget", [NSNumber numberWithBool:YES],
|
|
@"is.flipped", nil]);
|
|
|
|
#if DRAW_IN_FRAME_DEBUG
|
|
CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
|
|
CGContextFillRect(cgContext, drawRect);
|
|
#endif
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
nsNativeThemeCocoa::MenuItemParams nsNativeThemeCocoa::ComputeMenuItemParams(
|
|
nsIFrame* aFrame, EventStates aEventState, bool aIsChecked) {
|
|
bool isDisabled = IsDisabled(aFrame, aEventState);
|
|
|
|
MenuItemParams params;
|
|
params.backgroundIsVibrant = VibrancyManager::SystemSupportsVibrancy();
|
|
params.checked = aIsChecked;
|
|
params.disabled = isDisabled;
|
|
params.selected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
|
|
params.rtl = IsFrameRTL(aFrame);
|
|
return params;
|
|
}
|
|
|
|
static void SetCGContextFillColor(CGContextRef cgContext, const sRGBColor& aColor) {
|
|
DeviceColor color = ToDeviceColor(aColor);
|
|
CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
|
|
}
|
|
|
|
static void SetCGContextFillColor(CGContextRef cgContext, nscolor aColor) {
|
|
DeviceColor color = ToDeviceColor(aColor);
|
|
CGContextSetRGBFillColor(cgContext, color.r, color.g, color.b, color.a);
|
|
}
|
|
|
|
static void SetCGContextStrokeColor(CGContextRef cgContext, nscolor aColor) {
|
|
DeviceColor color = ToDeviceColor(aColor);
|
|
CGContextSetRGBStrokeColor(cgContext, color.r, color.g, color.b, color.a);
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawMenuItem(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
const MenuItemParams& aParams) {
|
|
// If the background is contributed by vibrancy (which is part of the window background), we don't
|
|
// need to draw any background here.
|
|
if (!aParams.backgroundIsVibrant) {
|
|
HIThemeMenuItemDrawInfo drawInfo;
|
|
memset(&drawInfo, 0, sizeof(drawInfo));
|
|
drawInfo.version = 0;
|
|
drawInfo.itemType = kThemeMenuItemPlain;
|
|
drawInfo.state =
|
|
(aParams.disabled ? static_cast<ThemeMenuState>(kThemeMenuDisabled)
|
|
: aParams.selected ? static_cast<ThemeMenuState>(kThemeMenuSelected)
|
|
: static_cast<ThemeMenuState>(kThemeMenuActive));
|
|
|
|
HIRect ignored;
|
|
HIThemeDrawMenuItem(&inBoxRect, &inBoxRect, &drawInfo, cgContext, HITHEME_ORIENTATION,
|
|
&ignored);
|
|
}
|
|
|
|
if (aParams.checked) {
|
|
MenuIconParams params;
|
|
params.disabled = aParams.disabled;
|
|
params.insideActiveMenuItem = aParams.selected;
|
|
params.rtl = aParams.rtl;
|
|
params.icon = MenuIcon::eCheckmark;
|
|
DrawMenuIcon(cgContext, inBoxRect, params);
|
|
}
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawMenuSeparator(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
const MenuItemParams& aParams) {
|
|
ThemeMenuState menuState;
|
|
if (aParams.disabled) {
|
|
menuState = kThemeMenuDisabled;
|
|
} else {
|
|
menuState = aParams.selected ? kThemeMenuSelected : kThemeMenuActive;
|
|
}
|
|
|
|
HIThemeMenuItemDrawInfo midi = {0, kThemeMenuItemPlain, menuState};
|
|
HIThemeDrawMenuSeparator(&inBoxRect, &inBoxRect, &midi, cgContext, HITHEME_ORIENTATION);
|
|
}
|
|
|
|
nsNativeThemeCocoa::ControlParams nsNativeThemeCocoa::ComputeControlParams(
|
|
nsIFrame* aFrame, EventStates aEventState) {
|
|
ControlParams params;
|
|
params.disabled = IsDisabled(aFrame, aEventState);
|
|
params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
params.pressed = aEventState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER);
|
|
params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS);
|
|
params.rtl = IsFrameRTL(aFrame);
|
|
return params;
|
|
}
|
|
|
|
static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
|
|
static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
|
|
|
|
static const CellRenderSettings pushButtonSettings = {{
|
|
NSMakeSize(0, 16), // mini
|
|
NSMakeSize(0, 19), // small
|
|
NSMakeSize(0, 22) // regular
|
|
},
|
|
{
|
|
NSMakeSize(18, 0), // mini
|
|
NSMakeSize(26, 0), // small
|
|
NSMakeSize(30, 0) // regular
|
|
},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{4, 0, 4, 1}, // small
|
|
{5, 0, 5, 2} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 0, 0}, // mini
|
|
{4, 0, 4, 1}, // small
|
|
{5, 0, 5, 2} // regular
|
|
}}};
|
|
|
|
// The height at which we start doing square buttons instead of rounded buttons
|
|
// Rounded buttons look bad if drawn at a height greater than 26, so at that point
|
|
// we switch over to doing square buttons which looks fine at any size.
|
|
#define DO_SQUARE_BUTTON_HEIGHT 26
|
|
|
|
void nsNativeThemeCocoa::DrawRoundedBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
ControlParams aControlParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
|
|
[mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
|
|
DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
|
|
mCellDrawView, aControlParams.rtl, 1.0f);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawSquareBezelPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
ControlParams aControlParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
ApplyControlParamsToNSCell(aControlParams, mPushButtonCell);
|
|
[mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
|
|
DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSRegularControlSize, NSZeroSize,
|
|
NSMakeSize(14, 0), NULL, mCellDrawView, aControlParams.rtl);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawHelpButton(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
ControlParams aControlParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
ApplyControlParamsToNSCell(aControlParams, mHelpButtonCell);
|
|
DrawCellWithScaling(mHelpButtonCell, cgContext, inBoxRect, NSRegularControlSize, NSZeroSize,
|
|
kHelpButtonSize, NULL, mCellDrawView,
|
|
false); // Don't mirror icon in RTL.
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawDisclosureButton(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
ControlParams aControlParams,
|
|
NSCellStateValue aCellState) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
ApplyControlParamsToNSCell(aControlParams, mDisclosureButtonCell);
|
|
[mDisclosureButtonCell setState:aCellState];
|
|
DrawCellWithScaling(mDisclosureButtonCell, cgContext, inBoxRect, NSRegularControlSize, NSZeroSize,
|
|
kDisclosureButtonSize, NULL, mCellDrawView,
|
|
false); // Don't mirror icon in RTL.
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeFrameDrawInfo fdi;
|
|
fdi.version = 0;
|
|
fdi.kind = kHIThemeFrameTextFieldSquare;
|
|
fdi.state = kThemeStateActive;
|
|
fdi.isFocused = TRUE;
|
|
|
|
#if DRAW_IN_FRAME_DEBUG
|
|
CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
#endif
|
|
|
|
HIThemeDrawFrame(&inBoxRect, &fdi, cgContext, HITHEME_ORIENTATION);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect,
|
|
void* aData);
|
|
|
|
static void RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
|
|
RenderHIThemeControlFunction aFunc, void* aData,
|
|
BOOL mirrorHorizontally = NO) {
|
|
CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
|
|
CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
|
|
|
|
bool drawDirect;
|
|
HIRect drawRect = aRect;
|
|
drawRect.origin = CGPointZero;
|
|
|
|
if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f && savedCTM.c == 0.0f &&
|
|
(savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
|
|
drawDirect = TRUE;
|
|
} else {
|
|
drawDirect = FALSE;
|
|
}
|
|
|
|
// Fall back to no bitmap buffer if the area of our control (in pixels^2)
|
|
// is too large.
|
|
if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
|
|
aFunc(aCGContext, drawRect, aData);
|
|
} else {
|
|
// Inflate the buffer to capture focus rings.
|
|
int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
|
|
int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
|
|
|
|
int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
|
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
CGContextRef bitmapctx = CGBitmapContextCreate(
|
|
NULL, w * backingScaleFactor, h * backingScaleFactor, 8, w * backingScaleFactor * 4,
|
|
colorSpace, kCGImageAlphaPremultipliedFirst);
|
|
CGColorSpaceRelease(colorSpace);
|
|
|
|
CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
|
|
CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
|
|
|
|
// Set the context's "base transform" to in order to get correctly-sized focus rings.
|
|
CGContextSetBaseCTM(bitmapctx,
|
|
CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
|
|
|
|
// HITheme always wants to draw into a flipped context, or things
|
|
// get confused.
|
|
CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
|
|
CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
|
|
|
|
aFunc(bitmapctx, drawRect, aData);
|
|
|
|
CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
|
|
|
|
CGAffineTransform ctm = CGContextGetCTM(aCGContext);
|
|
|
|
// We need to unflip, so that we can do a DrawImage without getting a flipped image.
|
|
CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
|
|
CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
|
|
|
|
if (mirrorHorizontally) {
|
|
CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
|
|
CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
|
|
}
|
|
|
|
HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
|
|
CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
|
|
|
|
CGContextSetCTM(aCGContext, ctm);
|
|
|
|
CGImageRelease(bitmap);
|
|
CGContextRelease(bitmapctx);
|
|
}
|
|
|
|
CGContextSetCTM(aCGContext, savedCTM);
|
|
}
|
|
|
|
static void RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
|
|
HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
|
|
HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
|
|
}
|
|
|
|
static ThemeDrawState ToThemeDrawState(const nsNativeThemeCocoa::ControlParams& aParams) {
|
|
if (aParams.disabled) {
|
|
return kThemeStateUnavailable;
|
|
}
|
|
if (aParams.pressed) {
|
|
return kThemeStatePressed;
|
|
}
|
|
return kThemeStateActive;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawHIThemeButton(CGContextRef cgContext, const HIRect& aRect,
|
|
ThemeButtonKind aKind, ThemeButtonValue aValue,
|
|
ThemeDrawState aState, ThemeButtonAdornment aAdornment,
|
|
const ControlParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeButtonDrawInfo bdi;
|
|
bdi.version = 0;
|
|
bdi.kind = aKind;
|
|
bdi.value = aValue;
|
|
bdi.state = aState;
|
|
bdi.adornment = aAdornment;
|
|
|
|
if (aParams.focused && aParams.insideActiveWindow) {
|
|
bdi.adornment |= kThemeAdornmentFocus;
|
|
}
|
|
|
|
if ((aAdornment & kThemeAdornmentDefault) && !aParams.disabled) {
|
|
bdi.animation.time.start = 0;
|
|
bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
|
|
}
|
|
|
|
RenderTransformedHIThemeControl(cgContext, aRect, RenderButton, &bdi, aParams.rtl);
|
|
|
|
#if DRAW_IN_FRAME_DEBUG
|
|
CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
#endif
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const ButtonParams& aParams) {
|
|
ControlParams controlParams = aParams.controlParams;
|
|
|
|
switch (aParams.button) {
|
|
case ButtonType::eRegularPushButton:
|
|
case ButtonType::eDefaultPushButton: {
|
|
ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultPushButton
|
|
? kThemeAdornmentDefault
|
|
: kThemeAdornmentNone;
|
|
HIRect drawFrame = inBoxRect;
|
|
drawFrame.size.height -= 2;
|
|
if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[regularControlSize].height) {
|
|
DrawHIThemeButton(cgContext, drawFrame, kThemePushButton, kThemeButtonOff,
|
|
ToThemeDrawState(controlParams), adornment, controlParams);
|
|
return;
|
|
}
|
|
if (inBoxRect.size.height >= pushButtonSettings.naturalSizes[smallControlSize].height) {
|
|
drawFrame.origin.y -= 1;
|
|
drawFrame.origin.x += 1;
|
|
drawFrame.size.width -= 2;
|
|
DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonSmall, kThemeButtonOff,
|
|
ToThemeDrawState(controlParams), adornment, controlParams);
|
|
return;
|
|
}
|
|
DrawHIThemeButton(cgContext, drawFrame, kThemePushButtonMini, kThemeButtonOff,
|
|
ToThemeDrawState(controlParams), adornment, controlParams);
|
|
return;
|
|
}
|
|
case ButtonType::eRegularBevelButton:
|
|
case ButtonType::eDefaultBevelButton: {
|
|
ThemeButtonAdornment adornment = aParams.button == ButtonType::eDefaultBevelButton
|
|
? kThemeAdornmentDefault
|
|
: kThemeAdornmentNone;
|
|
DrawHIThemeButton(cgContext, inBoxRect, kThemeMediumBevelButton, kThemeButtonOff,
|
|
ToThemeDrawState(controlParams), adornment, controlParams);
|
|
return;
|
|
}
|
|
case ButtonType::eRoundedBezelPushButton:
|
|
DrawRoundedBezelPushButton(cgContext, inBoxRect, controlParams);
|
|
return;
|
|
case ButtonType::eSquareBezelPushButton:
|
|
DrawSquareBezelPushButton(cgContext, inBoxRect, controlParams);
|
|
return;
|
|
case ButtonType::eArrowButton:
|
|
DrawHIThemeButton(cgContext, inBoxRect, kThemeArrowButton, kThemeButtonOn,
|
|
kThemeStateUnavailable, kThemeAdornmentArrowDownArrow, controlParams);
|
|
return;
|
|
case ButtonType::eHelpButton:
|
|
DrawHelpButton(cgContext, inBoxRect, controlParams);
|
|
return;
|
|
case ButtonType::eTreeTwistyPointingRight:
|
|
DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureRight,
|
|
ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
|
|
return;
|
|
case ButtonType::eTreeTwistyPointingDown:
|
|
DrawHIThemeButton(cgContext, inBoxRect, kThemeDisclosureButton, kThemeDisclosureDown,
|
|
ToThemeDrawState(controlParams), kThemeAdornmentNone, controlParams);
|
|
return;
|
|
case ButtonType::eDisclosureButtonClosed:
|
|
DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOffState);
|
|
return;
|
|
case ButtonType::eDisclosureButtonOpen:
|
|
DrawDisclosureButton(cgContext, inBoxRect, controlParams, NSOnState);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsNativeThemeCocoa::TreeHeaderCellParams nsNativeThemeCocoa::ComputeTreeHeaderCellParams(
|
|
nsIFrame* aFrame, EventStates aEventState) {
|
|
TreeHeaderCellParams params;
|
|
params.controlParams = ComputeControlParams(aFrame, aEventState);
|
|
params.sortDirection = GetTreeSortDirection(aFrame);
|
|
params.lastTreeHeaderCell = IsLastTreeHeaderCell(aFrame);
|
|
return params;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawTreeHeaderCell(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const TreeHeaderCellParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeButtonDrawInfo bdi;
|
|
bdi.version = 0;
|
|
bdi.kind = kThemeListHeaderButton;
|
|
bdi.value = kThemeButtonOff;
|
|
bdi.adornment = kThemeAdornmentNone;
|
|
|
|
switch (aParams.sortDirection) {
|
|
case eTreeSortDirection_Natural:
|
|
break;
|
|
case eTreeSortDirection_Ascending:
|
|
bdi.value = kThemeButtonOn;
|
|
bdi.adornment = kThemeAdornmentHeaderButtonSortUp;
|
|
break;
|
|
case eTreeSortDirection_Descending:
|
|
bdi.value = kThemeButtonOn;
|
|
break;
|
|
}
|
|
|
|
if (aParams.controlParams.disabled) {
|
|
bdi.state = kThemeStateUnavailable;
|
|
} else if (aParams.controlParams.pressed) {
|
|
bdi.state = kThemeStatePressed;
|
|
} else if (!aParams.controlParams.insideActiveWindow) {
|
|
bdi.state = kThemeStateInactive;
|
|
} else {
|
|
bdi.state = kThemeStateActive;
|
|
}
|
|
|
|
CGContextClipToRect(cgContext, inBoxRect);
|
|
|
|
HIRect drawFrame = inBoxRect;
|
|
// Always remove the top border.
|
|
drawFrame.origin.y -= 1;
|
|
drawFrame.size.height += 1;
|
|
// Remove the left border in LTR mode and the right border in RTL mode.
|
|
drawFrame.size.width += 1;
|
|
if (aParams.lastTreeHeaderCell) {
|
|
drawFrame.size.width += 1; // Also remove the other border.
|
|
}
|
|
if (!aParams.controlParams.rtl || aParams.lastTreeHeaderCell) {
|
|
drawFrame.origin.x -= 1;
|
|
}
|
|
|
|
RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
|
|
aParams.controlParams.rtl);
|
|
|
|
#if DRAW_IN_FRAME_DEBUG
|
|
CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
#endif
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
static const CellRenderSettings dropdownSettings = {{
|
|
NSMakeSize(0, 16), // mini
|
|
NSMakeSize(0, 19), // small
|
|
NSMakeSize(0, 22) // regular
|
|
},
|
|
{
|
|
NSMakeSize(18, 0), // mini
|
|
NSMakeSize(38, 0), // small
|
|
NSMakeSize(44, 0) // regular
|
|
},
|
|
{{
|
|
// Leopard
|
|
{1, 1, 2, 1}, // mini
|
|
{3, 0, 3, 1}, // small
|
|
{3, 0, 3, 0} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{1, 1, 2, 1}, // mini
|
|
{3, 0, 3, 1}, // small
|
|
{3, 0, 3, 0} // regular
|
|
}}};
|
|
|
|
static const CellRenderSettings editableMenulistSettings = {{
|
|
NSMakeSize(0, 15), // mini
|
|
NSMakeSize(0, 18), // small
|
|
NSMakeSize(0, 21) // regular
|
|
},
|
|
{
|
|
NSMakeSize(18, 0), // mini
|
|
NSMakeSize(38, 0), // small
|
|
NSMakeSize(44, 0) // regular
|
|
},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 2, 2}, // mini
|
|
{0, 0, 3, 2}, // small
|
|
{0, 1, 3, 3} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 2, 2}, // mini
|
|
{0, 0, 3, 2}, // small
|
|
{0, 1, 3, 3} // regular
|
|
}}};
|
|
|
|
void nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const DropdownParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
[mDropdownCell setPullsDown:aParams.pullsDown];
|
|
NSCell* cell = aParams.editable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
|
|
|
|
ApplyControlParamsToNSCell(aParams.controlParams, cell);
|
|
|
|
if (aParams.controlParams.insideActiveWindow) {
|
|
[cell setControlTint:[NSColor currentControlTint]];
|
|
} else {
|
|
[cell setControlTint:NSClearControlTint];
|
|
}
|
|
|
|
const CellRenderSettings& settings =
|
|
aParams.editable ? editableMenulistSettings : dropdownSettings;
|
|
DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, 0.5f, mCellDrawView,
|
|
aParams.controlParams.rtl);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
static const CellRenderSettings spinnerSettings = {
|
|
{
|
|
NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
|
|
NSMakeSize(15, 22), // small
|
|
NSMakeSize(19, 27) // regular
|
|
},
|
|
{
|
|
NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
|
|
NSMakeSize(15, 22), // small
|
|
NSMakeSize(19, 27) // regular
|
|
},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{0, 0, 0, 0}, // small
|
|
{0, 0, 0, 0} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 0, 0}, // mini
|
|
{0, 0, 0, 0}, // small
|
|
{0, 0, 0, 0} // regular
|
|
}}};
|
|
|
|
HIThemeButtonDrawInfo nsNativeThemeCocoa::SpinButtonDrawInfo(ThemeButtonKind aKind,
|
|
const SpinButtonParams& aParams) {
|
|
HIThemeButtonDrawInfo bdi;
|
|
bdi.version = 0;
|
|
bdi.kind = aKind;
|
|
bdi.value = kThemeButtonOff;
|
|
bdi.adornment = kThemeAdornmentNone;
|
|
|
|
if (aParams.disabled) {
|
|
bdi.state = kThemeStateUnavailable;
|
|
} else if (aParams.insideActiveWindow && aParams.pressedButton) {
|
|
if (*aParams.pressedButton == SpinButton::eUp) {
|
|
bdi.state = kThemeStatePressedUp;
|
|
} else {
|
|
bdi.state = kThemeStatePressedDown;
|
|
}
|
|
} else {
|
|
bdi.state = kThemeStateActive;
|
|
}
|
|
|
|
return bdi;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const SpinButtonParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButton, aParams);
|
|
HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
SpinButton aDrawnButton, const SpinButtonParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeButtonDrawInfo bdi = SpinButtonDrawInfo(kThemeIncDecButtonMini, aParams);
|
|
|
|
// Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
|
|
// together as a single unit (presumably because when one button is active,
|
|
// the appearance of both changes (in different ways)). Here we have to paint
|
|
// both buttons, using clip to hide the one we don't want to paint.
|
|
HIRect drawRect = inBoxRect;
|
|
drawRect.size.height *= 2;
|
|
if (aDrawnButton == SpinButton::eDown) {
|
|
drawRect.origin.y -= inBoxRect.size.height;
|
|
}
|
|
|
|
// Shift the drawing a little to the left, since cocoa paints with more
|
|
// blank space around the visual buttons than we'd like:
|
|
drawRect.origin.x -= 1;
|
|
|
|
CGContextSaveGState(cgContext);
|
|
CGContextClipToRect(cgContext, inBoxRect);
|
|
|
|
HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
|
|
|
|
CGContextRestoreGState(cgContext);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawTextBox(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
TextBoxParams aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
|
|
#if DRAW_IN_FRAME_DEBUG
|
|
CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
#endif
|
|
|
|
if (aParams.borderless) {
|
|
return;
|
|
}
|
|
|
|
HIThemeFrameDrawInfo fdi;
|
|
fdi.version = 0;
|
|
fdi.kind = kHIThemeFrameTextFieldSquare;
|
|
|
|
// We don't ever set an inactive state for this because it doesn't
|
|
// look right (see other apps).
|
|
fdi.state = aParams.disabled ? kThemeStateUnavailable : kThemeStateActive;
|
|
fdi.isFocused = aParams.focused;
|
|
|
|
// HIThemeDrawFrame takes the rect for the content area of the frame, not
|
|
// the bounding rect for the frame. Here we reduce the size of the rect we
|
|
// will pass to make it the size of the content.
|
|
HIRect drawRect = inBoxRect;
|
|
SInt32 frameOutset = 0;
|
|
::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
|
|
drawRect.origin.x += frameOutset;
|
|
drawRect.origin.y += frameOutset;
|
|
drawRect.size.width -= frameOutset * 2;
|
|
drawRect.size.height -= frameOutset * 2;
|
|
|
|
HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
static const CellRenderSettings progressSettings[2][2] = {
|
|
// Vertical progress bar.
|
|
{// Determined settings.
|
|
{{
|
|
NSZeroSize, // mini
|
|
NSMakeSize(10, 0), // small
|
|
NSMakeSize(16, 0) // regular
|
|
},
|
|
{NSZeroSize, NSZeroSize, NSZeroSize},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{1, 1, 1, 1} // regular
|
|
}}},
|
|
// There is no horizontal margin in regular undetermined size.
|
|
{{
|
|
NSZeroSize, // mini
|
|
NSMakeSize(10, 0), // small
|
|
NSMakeSize(16, 0) // regular
|
|
},
|
|
{NSZeroSize, NSZeroSize, NSZeroSize},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{1, 0, 1, 0} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{1, 0, 1, 0} // regular
|
|
}}}},
|
|
// Horizontal progress bar.
|
|
{// Determined settings.
|
|
{{
|
|
NSZeroSize, // mini
|
|
NSMakeSize(0, 10), // small
|
|
NSMakeSize(0, 16) // regular
|
|
},
|
|
{NSZeroSize, NSZeroSize, NSZeroSize},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{1, 1, 1, 1} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{1, 1, 1, 1} // regular
|
|
}}},
|
|
// There is no horizontal margin in regular undetermined size.
|
|
{{
|
|
NSZeroSize, // mini
|
|
NSMakeSize(0, 10), // small
|
|
NSMakeSize(0, 16) // regular
|
|
},
|
|
{NSZeroSize, NSZeroSize, NSZeroSize},
|
|
{{
|
|
// Leopard
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{0, 1, 0, 1} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{0, 0, 0, 0}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{0, 1, 0, 1} // regular
|
|
}}}}};
|
|
|
|
nsNativeThemeCocoa::ProgressParams nsNativeThemeCocoa::ComputeProgressParams(
|
|
nsIFrame* aFrame, EventStates aEventState, bool aIsHorizontal) {
|
|
ProgressParams params;
|
|
params.value = GetProgressValue(aFrame);
|
|
params.max = GetProgressMaxValue(aFrame);
|
|
params.verticalAlignFactor = VerticalAlignFactor(aFrame);
|
|
params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
params.indeterminate = IsIndeterminateProgress(aFrame, aEventState);
|
|
params.horizontal = aIsHorizontal;
|
|
params.rtl = IsFrameRTL(aFrame);
|
|
return params;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const ProgressParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
NSProgressBarCell* cell = mProgressBarCell;
|
|
|
|
[cell setValue:aParams.value];
|
|
[cell setMax:aParams.max];
|
|
[cell setIndeterminate:aParams.indeterminate];
|
|
[cell setHorizontal:aParams.horizontal];
|
|
[cell setControlTint:(aParams.insideActiveWindow ? [NSColor currentControlTint]
|
|
: NSClearControlTint)];
|
|
|
|
DrawCellWithSnapping(cell, cgContext, inBoxRect,
|
|
progressSettings[aParams.horizontal][aParams.indeterminate],
|
|
aParams.verticalAlignFactor, mCellDrawView, aParams.rtl);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
static const CellRenderSettings meterSetting = {{
|
|
NSMakeSize(0, 16), // mini
|
|
NSMakeSize(0, 16), // small
|
|
NSMakeSize(0, 16) // regular
|
|
},
|
|
{NSZeroSize, NSZeroSize, NSZeroSize},
|
|
{{
|
|
// Leopard
|
|
{1, 1, 1, 1}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{1, 1, 1, 1} // regular
|
|
},
|
|
{
|
|
// Yosemite
|
|
{1, 1, 1, 1}, // mini
|
|
{1, 1, 1, 1}, // small
|
|
{1, 1, 1, 1} // regular
|
|
}}};
|
|
|
|
nsNativeThemeCocoa::MeterParams nsNativeThemeCocoa::ComputeMeterParams(nsIFrame* aFrame) {
|
|
nsIContent* content = aFrame->GetContent();
|
|
if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
|
|
return MeterParams();
|
|
}
|
|
|
|
HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
|
|
MeterParams params;
|
|
params.value = meterElement->Value();
|
|
params.min = meterElement->Min();
|
|
params.max = meterElement->Max();
|
|
EventStates states = meterElement->State();
|
|
if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
|
|
params.optimumState = OptimumState::eSubOptimum;
|
|
} else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
|
|
params.optimumState = OptimumState::eSubSubOptimum;
|
|
}
|
|
params.horizontal = !IsVerticalMeter(aFrame);
|
|
params.verticalAlignFactor = VerticalAlignFactor(aFrame);
|
|
params.rtl = IsFrameRTL(aFrame);
|
|
|
|
return params;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const MeterParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK
|
|
|
|
NSLevelIndicatorCell* cell = mMeterBarCell;
|
|
|
|
[cell setMinValue:aParams.min];
|
|
[cell setMaxValue:aParams.max];
|
|
[cell setDoubleValue:aParams.value];
|
|
|
|
/**
|
|
* The way HTML and Cocoa defines the meter/indicator widget are different.
|
|
* So, we are going to use a trick to get the Cocoa widget showing what we
|
|
* are expecting: we set the warningValue or criticalValue to the current
|
|
* value when we want to have the widget to be in the warning or critical
|
|
* state.
|
|
*/
|
|
switch (aParams.optimumState) {
|
|
case OptimumState::eOptimum:
|
|
[cell setWarningValue:aParams.max + 1];
|
|
[cell setCriticalValue:aParams.max + 1];
|
|
break;
|
|
case OptimumState::eSubOptimum:
|
|
[cell setWarningValue:aParams.value];
|
|
[cell setCriticalValue:aParams.max + 1];
|
|
break;
|
|
case OptimumState::eSubSubOptimum:
|
|
[cell setWarningValue:aParams.max + 1];
|
|
[cell setCriticalValue:aParams.value];
|
|
break;
|
|
}
|
|
|
|
HIRect rect = CGRectStandardize(inBoxRect);
|
|
BOOL vertical = !aParams.horizontal;
|
|
|
|
CGContextSaveGState(cgContext);
|
|
|
|
if (vertical) {
|
|
/**
|
|
* Cocoa doesn't provide a vertical meter bar so to show one, we have to
|
|
* show a rotated horizontal meter bar.
|
|
* Given that we want to show a vertical meter bar, we assume that the rect
|
|
* has vertical dimensions but we can't correctly draw a meter widget inside
|
|
* such a rectangle so we need to inverse width and height (and re-position)
|
|
* to get a rectangle with horizontal dimensions.
|
|
* Finally, we want to show a vertical meter so we want to rotate the result
|
|
* so it is vertical. We do that by changing the context.
|
|
*/
|
|
CGFloat tmp = rect.size.width;
|
|
rect.size.width = rect.size.height;
|
|
rect.size.height = tmp;
|
|
rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
|
|
rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
|
|
|
|
CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
|
|
CGContextRotateCTM(cgContext, -M_PI / 2.f);
|
|
CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
|
|
}
|
|
|
|
DrawCellWithSnapping(cell, cgContext, rect, meterSetting, aParams.verticalAlignFactor,
|
|
mCellDrawView, !vertical && aParams.rtl);
|
|
|
|
CGContextRestoreGState(cgContext);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
bool aIsInsideActiveWindow) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeTabPaneDrawInfo tpdi;
|
|
|
|
tpdi.version = 1;
|
|
tpdi.state = aIsInsideActiveWindow ? kThemeStateActive : kThemeStateInactive;
|
|
tpdi.direction = kThemeTabNorth;
|
|
tpdi.size = kHIThemeTabSizeNormal;
|
|
tpdi.kind = kHIThemeTabKindNormal;
|
|
|
|
HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
nsNativeThemeCocoa::ScaleParams nsNativeThemeCocoa::ComputeXULScaleParams(nsIFrame* aFrame,
|
|
EventStates aEventState,
|
|
bool aIsHorizontal) {
|
|
ScaleParams params;
|
|
params.value = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
|
|
params.min = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
|
|
params.max = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
|
|
if (!params.max) {
|
|
params.max = 100;
|
|
}
|
|
|
|
params.reverse =
|
|
aFrame->GetContent()->IsElement() &&
|
|
aFrame->GetContent()->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
|
|
NS_LITERAL_STRING("reverse"), eCaseMatters);
|
|
params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS);
|
|
params.disabled = IsDisabled(aFrame, aEventState);
|
|
params.horizontal = aIsHorizontal;
|
|
return params;
|
|
}
|
|
|
|
Maybe<nsNativeThemeCocoa::ScaleParams> nsNativeThemeCocoa::ComputeHTMLScaleParams(
|
|
nsIFrame* aFrame, EventStates aEventState) {
|
|
nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
|
|
if (!rangeFrame) {
|
|
return Nothing();
|
|
}
|
|
|
|
bool isHorizontal = IsRangeHorizontal(aFrame);
|
|
|
|
// ScaleParams requires integer min, max and value. This is purely for
|
|
// drawing, so we normalize to a range 0-1000 here.
|
|
ScaleParams params;
|
|
params.value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
|
|
params.min = 0;
|
|
params.max = 1000;
|
|
params.reverse = !isHorizontal || rangeFrame->IsRightToLeft();
|
|
params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS);
|
|
params.disabled = IsDisabled(aFrame, aEventState);
|
|
params.horizontal = isHorizontal;
|
|
return Some(params);
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const ScaleParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeTrackDrawInfo tdi;
|
|
|
|
tdi.version = 0;
|
|
tdi.kind = kThemeMediumSlider;
|
|
tdi.bounds = inBoxRect;
|
|
tdi.min = aParams.min;
|
|
tdi.max = aParams.max;
|
|
tdi.value = aParams.value;
|
|
tdi.attributes = kThemeTrackShowThumb;
|
|
if (aParams.horizontal) {
|
|
tdi.attributes |= kThemeTrackHorizontal;
|
|
}
|
|
if (aParams.reverse) {
|
|
tdi.attributes |= kThemeTrackRightToLeft;
|
|
}
|
|
if (aParams.focused) {
|
|
tdi.attributes |= kThemeTrackHasFocus;
|
|
}
|
|
if (aParams.disabled) {
|
|
tdi.enableState = kThemeTrackDisabled;
|
|
} else {
|
|
tdi.enableState = aParams.insideActiveWindow ? kThemeTrackActive : kThemeTrackInactive;
|
|
}
|
|
tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
|
|
tdi.trackInfo.slider.pressState = 0;
|
|
|
|
HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
nsIFrame* nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter) {
|
|
// Usually a separator is drawn by the segment to the right of the
|
|
// separator, but pressed and selected segments have higher priority.
|
|
if (!aBefore || !aAfter) return nullptr;
|
|
if (IsSelectedButton(aAfter)) return aAfter;
|
|
if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) return aBefore;
|
|
return aAfter;
|
|
}
|
|
|
|
static CGRect SeparatorAdjustedRect(CGRect aRect, nsNativeThemeCocoa::SegmentParams aParams) {
|
|
// A separator between two segments should always be located in the leftmost
|
|
// pixel column of the segment to the right of the separator, regardless of
|
|
// who ends up drawing it.
|
|
// CoreUI draws the separators inside the drawing rect.
|
|
if (!aParams.atLeftEnd && !aParams.drawsLeftSeparator) {
|
|
// The segment to the left of us draws the separator, so we need to make
|
|
// room for it.
|
|
aRect.origin.x += 1;
|
|
aRect.size.width -= 1;
|
|
}
|
|
if (aParams.drawsRightSeparator) {
|
|
// We draw the right separator, so we need to extend the draw rect into the
|
|
// segment to our right.
|
|
aRect.size.width += 1;
|
|
}
|
|
return aRect;
|
|
}
|
|
|
|
static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) {
|
|
if (aIsFirst) {
|
|
if (aIsLast) return @"kCUISegmentPositionOnly";
|
|
return @"kCUISegmentPositionFirst";
|
|
}
|
|
if (aIsLast) return @"kCUISegmentPositionLast";
|
|
return @"kCUISegmentPositionMiddle";
|
|
}
|
|
|
|
struct SegmentedControlRenderSettings {
|
|
const CGFloat* heights;
|
|
const NSString* widgetName;
|
|
};
|
|
|
|
static const CGFloat tabHeights[3] = {17, 20, 23};
|
|
|
|
static const SegmentedControlRenderSettings tabRenderSettings = {tabHeights, @"tab"};
|
|
|
|
static const CGFloat toolbarButtonHeights[3] = {15, 18, 22};
|
|
|
|
static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
|
|
toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve"};
|
|
|
|
nsNativeThemeCocoa::SegmentParams nsNativeThemeCocoa::ComputeSegmentParams(
|
|
nsIFrame* aFrame, EventStates aEventState, SegmentType aSegmentType) {
|
|
SegmentParams params;
|
|
params.segmentType = aSegmentType;
|
|
params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
params.pressed = IsPressedButton(aFrame);
|
|
params.selected = IsSelectedButton(aFrame);
|
|
params.focused = aEventState.HasState(NS_EVENT_STATE_FOCUS);
|
|
bool isRTL = IsFrameRTL(aFrame);
|
|
nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
|
|
nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
|
|
params.atLeftEnd = !left;
|
|
params.atRightEnd = !right;
|
|
params.drawsLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
|
|
params.drawsRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
|
|
params.rtl = isRTL;
|
|
return params;
|
|
}
|
|
|
|
static SegmentedControlRenderSettings RenderSettingsForSegmentType(
|
|
nsNativeThemeCocoa::SegmentType aSegmentType) {
|
|
switch (aSegmentType) {
|
|
case nsNativeThemeCocoa::SegmentType::eToolbarButton:
|
|
return toolbarButtonRenderSettings;
|
|
case nsNativeThemeCocoa::SegmentType::eTab:
|
|
return tabRenderSettings;
|
|
}
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const SegmentParams& aParams) {
|
|
SegmentedControlRenderSettings renderSettings = RenderSettingsForSegmentType(aParams.segmentType);
|
|
|
|
NSControlSize controlSize = FindControlSize(inBoxRect.size.height, renderSettings.heights, 4.0f);
|
|
CGRect drawRect = SeparatorAdjustedRect(inBoxRect, aParams);
|
|
|
|
NSDictionary* dict = @{
|
|
@"widget" : renderSettings.widgetName,
|
|
@"kCUIPresentationStateKey" : (aParams.insideActiveWindow ? @"kCUIPresentationStateActiveKey"
|
|
: @"kCUIPresentationStateInactive"),
|
|
@"kCUIPositionKey" : ToolbarButtonPosition(aParams.atLeftEnd, aParams.atRightEnd),
|
|
@"kCUISegmentLeadingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsLeftSeparator],
|
|
@"kCUISegmentTrailingSeparatorKey" : [NSNumber numberWithBool:aParams.drawsRightSeparator],
|
|
@"value" : [NSNumber numberWithBool:aParams.selected],
|
|
@"state" :
|
|
(aParams.pressed ? @"pressed" : (aParams.insideActiveWindow ? @"normal" : @"inactive")),
|
|
@"focus" : [NSNumber numberWithBool:aParams.focused],
|
|
@"size" : CUIControlSizeForCocoaSize(controlSize),
|
|
@"is.flipped" : [NSNumber numberWithBool:YES],
|
|
@"direction" : @"up"
|
|
};
|
|
|
|
RenderWithCoreUI(drawRect, cgContext, dict);
|
|
}
|
|
|
|
static nsIFrame* GetParentScrollbarFrame(nsIFrame* aFrame) {
|
|
// Walk our parents to find a scrollbar frame
|
|
nsIFrame* scrollbarFrame = aFrame;
|
|
do {
|
|
if (scrollbarFrame->IsScrollbarFrame()) break;
|
|
} while ((scrollbarFrame = scrollbarFrame->GetParent()));
|
|
|
|
// We return null if we can't find a parent scrollbar frame
|
|
return scrollbarFrame;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawToolbar(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
bool aIsMain) {
|
|
CGRect drawRect = inBoxRect;
|
|
|
|
// top border
|
|
drawRect.size.height = 1.0f;
|
|
DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, aIsMain);
|
|
|
|
// background
|
|
drawRect.origin.y += drawRect.size.height;
|
|
drawRect.size.height = inBoxRect.size.height - 2.0f;
|
|
DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, aIsMain);
|
|
|
|
// bottom border
|
|
drawRect.origin.y += drawRect.size.height;
|
|
drawRect.size.height = 1.0f;
|
|
DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, aIsMain);
|
|
}
|
|
|
|
static bool ToolbarCanBeUnified(const gfx::Rect& aRect, NSWindow* aWindow) {
|
|
if (![aWindow isKindOfClass:[ToolbarWindow class]]) return false;
|
|
|
|
ToolbarWindow* win = (ToolbarWindow*)aWindow;
|
|
float unifiedToolbarHeight = [win unifiedToolbarHeight];
|
|
return aRect.X() == 0 && aRect.Width() >= [win frame].size.width &&
|
|
aRect.YMost() <= unifiedToolbarHeight;
|
|
}
|
|
|
|
// By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
|
|
// upper corners. Depending on the context type, it fills the background in
|
|
// the corners with black or leaves it transparent. Unfortunately, this corner
|
|
// rounding interacts poorly with the window corner masking we apply during
|
|
// titlebar drawing and results in small remnants of the corner background
|
|
// appearing at the rounded edge.
|
|
// So we draw square corners.
|
|
static void DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
|
|
CGFloat aUnifiedHeight, BOOL aIsMain,
|
|
BOOL aIsFlipped) {
|
|
// We extend the draw rect horizontally and clip away the rounded corners.
|
|
const CGFloat extendHorizontal = 10;
|
|
CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
|
|
CGContextSaveGState(aContext);
|
|
CGContextClipToRect(aContext, aRect);
|
|
|
|
RenderWithCoreUI(
|
|
drawRect, aContext,
|
|
[NSDictionary
|
|
dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
|
|
@"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
|
|
[NSNumber numberWithDouble:aUnifiedHeight],
|
|
@"kCUIWindowFrameUnifiedTitleBarHeightKey",
|
|
[NSNumber numberWithBool:YES],
|
|
@"kCUIWindowFrameDrawTitleSeparatorKey",
|
|
[NSNumber numberWithBool:aIsFlipped], @"is.flipped", nil]);
|
|
|
|
CGContextRestoreGState(aContext);
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
const UnifiedToolbarParams& aParams) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
CGContextSaveGState(cgContext);
|
|
CGContextClipToRect(cgContext, inBoxRect);
|
|
|
|
CGFloat titlebarHeight = aParams.unifiedHeight - inBoxRect.size.height;
|
|
CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
|
|
inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
|
|
DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, aParams.unifiedHeight,
|
|
aParams.isMain, YES);
|
|
|
|
CGContextRestoreGState(cgContext);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
|
|
bool aIsMain) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (inBoxRect.size.height < 2.0f) return;
|
|
|
|
CGContextSaveGState(cgContext);
|
|
CGContextClipToRect(cgContext, inBoxRect);
|
|
|
|
// kCUIWidgetWindowFrame draws a complete window frame with both title bar
|
|
// and bottom bar. We only want the bottom bar, so we extend the draw rect
|
|
// upwards to make space for the title bar, and then we clip it away.
|
|
CGRect drawRect = inBoxRect;
|
|
const int extendUpwards = 40;
|
|
drawRect.origin.y -= extendUpwards;
|
|
drawRect.size.height += extendUpwards;
|
|
RenderWithCoreUI(
|
|
drawRect, cgContext,
|
|
[NSDictionary
|
|
dictionaryWithObjectsAndKeys:@"kCUIWidgetWindowFrame", @"widget", @"regularwin",
|
|
@"windowtype", (aIsMain ? @"normal" : @"inactive"), @"state",
|
|
[NSNumber numberWithInt:inBoxRect.size.height],
|
|
@"kCUIWindowFrameBottomBarHeightKey",
|
|
[NSNumber numberWithBool:YES],
|
|
@"kCUIWindowFrameDrawBottomBarSeparatorKey",
|
|
[NSNumber numberWithBool:YES], @"is.flipped", nil]);
|
|
|
|
CGContextRestoreGState(cgContext);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
|
|
CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped) {
|
|
CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
|
|
DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain,
|
|
aIsFlipped);
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
|
|
const UnifiedToolbarParams& aParams) {
|
|
DrawNativeTitlebar(aContext, aTitlebarRect, aParams.unifiedHeight, aParams.isMain, YES);
|
|
}
|
|
|
|
static void RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) {
|
|
HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
|
|
HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect, bool aIsRTL) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
HIThemeGrowBoxDrawInfo drawInfo;
|
|
drawInfo.version = 0;
|
|
drawInfo.state = kThemeStateActive;
|
|
drawInfo.kind = kHIThemeGrowBoxKindNormal;
|
|
drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
|
|
drawInfo.size = kHIThemeGrowBoxSizeNormal;
|
|
|
|
RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, aIsRTL);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
static bool IsSmallScrollbar(nsIFrame* aFrame) {
|
|
ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
|
|
if (style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
|
|
return true;
|
|
}
|
|
nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
|
|
if (scrollbarFrame &&
|
|
scrollbarFrame->StyleDisplay()->mAppearance == StyleAppearance::ScrollbarSmall) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
nsNativeThemeCocoa::ScrollbarParams nsNativeThemeCocoa::ComputeScrollbarParams(nsIFrame* aFrame,
|
|
bool aIsHorizontal) {
|
|
ScrollbarParams params;
|
|
params.overlay = nsLookAndFeel::UseOverlayScrollbars();
|
|
params.rolledOver = IsParentScrollbarRolledOver(aFrame);
|
|
params.small = IsSmallScrollbar(aFrame);
|
|
params.rtl = IsFrameRTL(aFrame);
|
|
params.horizontal = aIsHorizontal;
|
|
params.onDarkBackground = IsDarkBackground(aFrame);
|
|
// Don't use custom scrollbars for overlay scrollbars since they are
|
|
// generally good enough for use cases of custom scrollbars.
|
|
if (!params.overlay) {
|
|
ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
|
|
const nsStyleUI* ui = style->StyleUI();
|
|
if (ui->HasCustomScrollbars()) {
|
|
const auto& colors = ui->mScrollbarColor.AsColors();
|
|
params.custom = true;
|
|
params.trackColor = colors.track.CalcColor(*style);
|
|
params.faceColor = colors.thumb.CalcColor(*style);
|
|
}
|
|
}
|
|
return params;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawScrollbarThumb(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
ScrollbarParams aParams) {
|
|
CGRect drawRect = inBoxRect;
|
|
if (aParams.custom) {
|
|
const CGFloat kWidthRatio = 8.0f / 15.0f;
|
|
const CGFloat kLengthReductionRatio = 2.0f / 15.0f;
|
|
const CGFloat kOrthogonalDirOffset = 4.0f / 15.0f;
|
|
const CGFloat kParallelDirOffset = 1.0f / 15.0f;
|
|
CGFloat baseSize, cornerWidth;
|
|
CGRect thumbRect = inBoxRect;
|
|
if (aParams.horizontal) {
|
|
baseSize = inBoxRect.size.height;
|
|
thumbRect.size.height *= kWidthRatio;
|
|
thumbRect.size.width -= baseSize * kLengthReductionRatio;
|
|
thumbRect.origin.y += baseSize * kOrthogonalDirOffset;
|
|
thumbRect.origin.x += baseSize * kParallelDirOffset;
|
|
cornerWidth = thumbRect.size.height / 2.0f;
|
|
} else {
|
|
baseSize = inBoxRect.size.width;
|
|
thumbRect.size.width *= kWidthRatio;
|
|
thumbRect.size.height -= baseSize * kLengthReductionRatio;
|
|
thumbRect.origin.x += baseSize * kOrthogonalDirOffset;
|
|
thumbRect.origin.y += baseSize * kParallelDirOffset;
|
|
cornerWidth = thumbRect.size.width / 2.0f;
|
|
}
|
|
CGPathRef path = CGPathCreateWithRoundedRect(thumbRect, cornerWidth, cornerWidth, nullptr);
|
|
CGContextAddPath(cgContext, path);
|
|
CGPathRelease(path);
|
|
SetCGContextFillColor(cgContext, aParams.faceColor);
|
|
CGContextFillPath(cgContext);
|
|
return;
|
|
}
|
|
|
|
if (aParams.overlay && !aParams.rolledOver) {
|
|
if (aParams.horizontal) {
|
|
drawRect.origin.y += 4;
|
|
drawRect.size.height -= 4;
|
|
} else {
|
|
if (!aParams.rtl) {
|
|
drawRect.origin.x += 4;
|
|
}
|
|
drawRect.size.width -= 4;
|
|
}
|
|
}
|
|
NSDictionary* options = @{
|
|
@"widget" : (aParams.overlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"),
|
|
@"size" : (aParams.small ? @"small" : @"regular"),
|
|
@"kCUIOrientationKey" : (aParams.horizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"),
|
|
@"kCUIVariantKey" : (aParams.overlay && aParams.onDarkBackground ? @"kCUIVariantWhite" : @""),
|
|
@"indiconly" : [NSNumber numberWithBool:YES],
|
|
@"kCUIThumbProportionKey" : [NSNumber numberWithBool:YES],
|
|
@"is.flipped" : [NSNumber numberWithBool:YES],
|
|
};
|
|
if (aParams.rolledOver) {
|
|
NSMutableDictionary* mutableOptions = [options mutableCopy];
|
|
[mutableOptions setObject:@"rollover" forKey:@"state"];
|
|
options = mutableOptions;
|
|
}
|
|
RenderWithCoreUI(drawRect, cgContext, options, true);
|
|
}
|
|
|
|
struct ScrollbarTrackDecorationColors {
|
|
nscolor mInnerColor;
|
|
nscolor mShadowColor;
|
|
nscolor mOuterColor;
|
|
};
|
|
|
|
static ScrollbarTrackDecorationColors ComputeScrollbarTrackDecorationColors(nscolor aTrackColor) {
|
|
ScrollbarTrackDecorationColors result;
|
|
float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
|
|
if (luminance >= 0.5) {
|
|
result.mInnerColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836);
|
|
result.mShadowColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982);
|
|
result.mOuterColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886);
|
|
} else {
|
|
result.mInnerColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196);
|
|
result.mShadowColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018);
|
|
result.mOuterColor = RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawScrollbarTrack(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
ScrollbarParams aParams) {
|
|
if (aParams.overlay && !aParams.rolledOver) {
|
|
// Non-hovered overlay scrollbars don't have a track. Draw nothing.
|
|
return;
|
|
}
|
|
|
|
if (!aParams.custom) {
|
|
RenderWithCoreUI(
|
|
inBoxRect, cgContext,
|
|
[NSDictionary
|
|
dictionaryWithObjectsAndKeys:(aParams.overlay ? @"kCUIWidgetOverlayScrollBar"
|
|
: @"scrollbar"),
|
|
@"widget", (aParams.small ? @"small" : @"regular"),
|
|
@"size",
|
|
(aParams.horizontal ? @"kCUIOrientHorizontal"
|
|
: @"kCUIOrientVertical"),
|
|
@"kCUIOrientationKey",
|
|
(aParams.onDarkBackground ? @"kCUIVariantWhite" : @""),
|
|
@"kCUIVariantKey", [NSNumber numberWithBool:YES],
|
|
@"noindicator", [NSNumber numberWithBool:YES],
|
|
@"kCUIThumbProportionKey", [NSNumber numberWithBool:YES],
|
|
@"is.flipped", nil],
|
|
true);
|
|
return;
|
|
}
|
|
|
|
// Paint the background color
|
|
SetCGContextFillColor(cgContext, aParams.trackColor);
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
// Paint decorations
|
|
ScrollbarTrackDecorationColors colors = ComputeScrollbarTrackDecorationColors(aParams.trackColor);
|
|
CGPoint innerPoints[2];
|
|
CGPoint shadowPoints[2];
|
|
CGPoint outerPoints[2];
|
|
if (aParams.horizontal) {
|
|
innerPoints[0].x = inBoxRect.origin.x;
|
|
innerPoints[0].y = inBoxRect.origin.y + 0.5f;
|
|
innerPoints[1].x = innerPoints[0].x + inBoxRect.size.width;
|
|
innerPoints[1].y = innerPoints[0].y;
|
|
shadowPoints[0].x = innerPoints[0].x;
|
|
shadowPoints[0].y = innerPoints[0].y + 1.0f;
|
|
shadowPoints[1].x = innerPoints[1].x;
|
|
shadowPoints[1].y = shadowPoints[0].y;
|
|
outerPoints[0].x = innerPoints[0].x;
|
|
outerPoints[0].y = innerPoints[0].y + inBoxRect.size.height - 1;
|
|
outerPoints[1].x = innerPoints[1].x;
|
|
outerPoints[1].y = outerPoints[0].y;
|
|
} else {
|
|
if (aParams.rtl) {
|
|
innerPoints[0].x = inBoxRect.origin.x + inBoxRect.size.width - 0.5f;
|
|
shadowPoints[0].x = innerPoints[0].x - 1.0f;
|
|
outerPoints[0].x = inBoxRect.origin.x + 0.5f;
|
|
} else {
|
|
innerPoints[0].x = inBoxRect.origin.x + 0.5f;
|
|
shadowPoints[0].x = innerPoints[0].x + 1.0f;
|
|
outerPoints[0].x = inBoxRect.origin.x + inBoxRect.size.width - 0.5f;
|
|
}
|
|
innerPoints[0].y = inBoxRect.origin.y;
|
|
innerPoints[1].x = innerPoints[0].x;
|
|
innerPoints[1].y = innerPoints[0].y + inBoxRect.size.height;
|
|
shadowPoints[0].y = innerPoints[0].y;
|
|
shadowPoints[1].x = shadowPoints[0].x;
|
|
shadowPoints[1].y = innerPoints[1].y;
|
|
outerPoints[0].y = innerPoints[0].y;
|
|
outerPoints[1].x = outerPoints[0].x;
|
|
outerPoints[1].y = innerPoints[1].y;
|
|
}
|
|
SetCGContextStrokeColor(cgContext, colors.mInnerColor);
|
|
CGContextStrokeLineSegments(cgContext, innerPoints, 2);
|
|
SetCGContextStrokeColor(cgContext, colors.mShadowColor);
|
|
CGContextStrokeLineSegments(cgContext, shadowPoints, 2);
|
|
SetCGContextStrokeColor(cgContext, colors.mOuterColor);
|
|
CGContextStrokeLineSegments(cgContext, outerPoints, 2);
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawScrollCorner(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
ScrollbarParams aParams) {
|
|
if (aParams.overlay && !aParams.rolledOver) {
|
|
// Non-hovered overlay scrollbars don't have a corner. Draw nothing.
|
|
return;
|
|
}
|
|
|
|
if (!aParams.custom) {
|
|
// For non-custom scrollbar, just draw a white rect. It is what
|
|
// Safari does. We don't want to try painting the decorations like
|
|
// the custom case below because we don't have control over what
|
|
// the system would draw for the scrollbar.
|
|
SetCGContextFillColor(cgContext, NS_RGB(255, 255, 255));
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
return;
|
|
}
|
|
|
|
// Paint the background
|
|
SetCGContextFillColor(cgContext, aParams.trackColor);
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
// Paint the decorations
|
|
ScrollbarTrackDecorationColors colors = ComputeScrollbarTrackDecorationColors(aParams.trackColor);
|
|
CGRect shadowRect, innerRect;
|
|
if (aParams.rtl) {
|
|
shadowRect.origin.x = inBoxRect.origin.x + inBoxRect.size.width - 2;
|
|
innerRect.origin.x = shadowRect.origin.x + 1;
|
|
} else {
|
|
shadowRect.origin.x = inBoxRect.origin.x;
|
|
innerRect.origin.x = shadowRect.origin.x;
|
|
}
|
|
shadowRect.origin.y = inBoxRect.origin.y;
|
|
shadowRect.size.width = shadowRect.size.height = 2;
|
|
innerRect.origin.y = inBoxRect.origin.y;
|
|
innerRect.size.width = innerRect.size.height = 1;
|
|
SetCGContextFillColor(cgContext, colors.mShadowColor);
|
|
CGContextFillRect(cgContext, shadowRect);
|
|
SetCGContextFillColor(cgContext, colors.mInnerColor);
|
|
CGContextFillRect(cgContext, innerRect);
|
|
CGPoint outerPoints[4];
|
|
outerPoints[0].x =
|
|
aParams.rtl ? inBoxRect.origin.x + 0.5 : inBoxRect.origin.x + inBoxRect.size.width - 0.5;
|
|
outerPoints[0].y = inBoxRect.origin.y;
|
|
outerPoints[1].x = outerPoints[0].x;
|
|
outerPoints[1].y = outerPoints[0].y + inBoxRect.size.height;
|
|
outerPoints[2].x = inBoxRect.origin.x;
|
|
outerPoints[2].y = inBoxRect.origin.y + inBoxRect.size.height - 0.5;
|
|
outerPoints[3].x = outerPoints[2].x + inBoxRect.size.width - 1;
|
|
outerPoints[3].y = outerPoints[2].y;
|
|
SetCGContextStrokeColor(cgContext, colors.mOuterColor);
|
|
CGContextStrokeLineSegments(cgContext, outerPoints, 4);
|
|
}
|
|
|
|
static const sRGBColor kTooltipBackgroundColor(0.996, 1.000, 0.792, 0.950);
|
|
static const sRGBColor kMultilineTextFieldTopBorderColor(0.4510, 0.4510, 0.4510, 1.0);
|
|
static const sRGBColor kMultilineTextFieldSidesAndBottomBorderColor(0.6, 0.6, 0.6, 1.0);
|
|
static const sRGBColor kListboxTopBorderColor(0.557, 0.557, 0.557, 1.0);
|
|
static const sRGBColor kListBoxSidesAndBottomBorderColor(0.745, 0.745, 0.745, 1.0);
|
|
|
|
void nsNativeThemeCocoa::DrawMultilineTextField(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
bool aIsFocused) {
|
|
SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
|
|
|
|
CGContextFillRect(cgContext, inBoxRect);
|
|
|
|
float x = inBoxRect.origin.x, y = inBoxRect.origin.y;
|
|
float w = inBoxRect.size.width, h = inBoxRect.size.height;
|
|
SetCGContextFillColor(cgContext, kMultilineTextFieldTopBorderColor);
|
|
CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
|
|
SetCGContextFillColor(cgContext, kMultilineTextFieldSidesAndBottomBorderColor);
|
|
CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
|
|
CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
|
|
CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
|
|
|
|
if (aIsFocused) {
|
|
NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
|
|
[NSGraphicsContext
|
|
setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext
|
|
flipped:YES]];
|
|
CGContextSaveGState(cgContext);
|
|
NSSetFocusRingStyle(NSFocusRingOnly);
|
|
NSRectFill(NSRectFromCGRect(inBoxRect));
|
|
CGContextRestoreGState(cgContext);
|
|
[NSGraphicsContext setCurrentContext:savedContext];
|
|
}
|
|
}
|
|
|
|
void nsNativeThemeCocoa::DrawSourceList(CGContextRef cgContext, const CGRect& inBoxRect,
|
|
bool aIsActive) {
|
|
CGGradientRef backgroundGradient;
|
|
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
|
|
CGFloat activeGradientColors[8] = {0.9137, 0.9294, 0.9490, 1.0, 0.8196, 0.8471, 0.8784, 1.0};
|
|
CGFloat inactiveGradientColors[8] = {0.9686, 0.9686, 0.9686, 1.0, 0.9216, 0.9216, 0.9216, 1.0};
|
|
CGPoint start = inBoxRect.origin;
|
|
CGPoint end = CGPointMake(inBoxRect.origin.x, inBoxRect.origin.y + inBoxRect.size.height);
|
|
backgroundGradient = CGGradientCreateWithColorComponents(
|
|
rgb, aIsActive ? activeGradientColors : inactiveGradientColors, NULL, 2);
|
|
CGContextDrawLinearGradient(cgContext, backgroundGradient, start, end, 0);
|
|
CGGradientRelease(backgroundGradient);
|
|
CGColorSpaceRelease(rgb);
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::IsParentScrollbarRolledOver(nsIFrame* aFrame) {
|
|
nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
|
|
return nsLookAndFeel::UseOverlayScrollbars()
|
|
? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
|
|
: GetContentState(scrollbarFrame, StyleAppearance::None)
|
|
.HasState(NS_EVENT_STATE_HOVER);
|
|
}
|
|
|
|
static bool IsHiDPIContext(nsDeviceContext* aContext) {
|
|
return AppUnitsPerCSSPixel() >= 2 * aContext->AppUnitsPerDevPixelAtUnitFullZoom();
|
|
}
|
|
|
|
Maybe<nsNativeThemeCocoa::WidgetInfo> nsNativeThemeCocoa::ComputeWidgetInfo(
|
|
nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
// setup to draw into the correct port
|
|
int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
|
|
|
|
gfx::Rect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
|
|
nativeWidgetRect.Scale(1.0 / gfxFloat(p2a));
|
|
float originalHeight = nativeWidgetRect.Height();
|
|
nativeWidgetRect.Round();
|
|
if (nativeWidgetRect.IsEmpty()) {
|
|
return Nothing(); // Don't attempt to draw invisible widgets.
|
|
}
|
|
|
|
bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
|
|
if (hidpi) {
|
|
// Use high-resolution drawing.
|
|
nativeWidgetRect.Scale(0.5f);
|
|
originalHeight *= 0.5f;
|
|
}
|
|
|
|
EventStates eventState = GetContentState(aFrame, aAppearance);
|
|
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Dialog:
|
|
if (IsWindowSheet(aFrame)) {
|
|
if (VibrancyManager::SystemSupportsVibrancy()) {
|
|
return Nothing();
|
|
}
|
|
return Some(WidgetInfo::SheetBackground());
|
|
}
|
|
return Some(WidgetInfo::DialogBackground());
|
|
|
|
case StyleAppearance::Menupopup:
|
|
if (VibrancyManager::SystemSupportsVibrancy()) {
|
|
return Nothing();
|
|
}
|
|
return Some(WidgetInfo::MenuBackground(ComputeMenuBackgroundParams(aFrame, eventState)));
|
|
|
|
case StyleAppearance::Menuarrow:
|
|
return Some(
|
|
WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, MenuIcon::eMenuArrow)));
|
|
|
|
case StyleAppearance::Menuitem:
|
|
case StyleAppearance::Checkmenuitem:
|
|
return Some(WidgetInfo::MenuItem(ComputeMenuItemParams(
|
|
aFrame, eventState, aAppearance == StyleAppearance::Checkmenuitem)));
|
|
|
|
case StyleAppearance::Menuseparator:
|
|
return Some(WidgetInfo::MenuSeparator(ComputeMenuItemParams(aFrame, eventState, false)));
|
|
|
|
case StyleAppearance::ButtonArrowUp:
|
|
case StyleAppearance::ButtonArrowDown: {
|
|
MenuIcon icon = aAppearance == StyleAppearance::ButtonArrowUp
|
|
? MenuIcon::eMenuUpScrollArrow
|
|
: MenuIcon::eMenuDownScrollArrow;
|
|
return Some(WidgetInfo::MenuIcon(ComputeMenuIconParams(aFrame, eventState, icon)));
|
|
}
|
|
|
|
case StyleAppearance::Tooltip:
|
|
if (VibrancyManager::SystemSupportsVibrancy()) {
|
|
return Nothing();
|
|
}
|
|
return Some(WidgetInfo::Tooltip());
|
|
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::Radio: {
|
|
bool isCheckbox = (aAppearance == StyleAppearance::Checkbox);
|
|
|
|
CheckboxOrRadioParams params;
|
|
params.state = CheckboxOrRadioState::eOff;
|
|
if (isCheckbox && GetIndeterminate(aFrame)) {
|
|
params.state = CheckboxOrRadioState::eIndeterminate;
|
|
} else if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
|
|
params.state = CheckboxOrRadioState::eOn;
|
|
}
|
|
params.controlParams = ComputeControlParams(aFrame, eventState);
|
|
params.verticalAlignFactor = VerticalAlignFactor(aFrame);
|
|
if (isCheckbox) {
|
|
return Some(WidgetInfo::Checkbox(params));
|
|
}
|
|
return Some(WidgetInfo::Radio(params));
|
|
}
|
|
|
|
case StyleAppearance::Button:
|
|
if (IsDefaultButton(aFrame)) {
|
|
// Check whether the default button is in a document that does not
|
|
// match the :-moz-window-inactive pseudoclass. This activeness check
|
|
// is different from the other "active window" checks in this file
|
|
// because we absolutely need the button's default button appearance to
|
|
// be in sync with its text color, and the text color is changed by
|
|
// such a :-moz-window-inactive rule. (That's because on 10.10 and up,
|
|
// default buttons in active windows have blue background and white
|
|
// text, and default buttons in inactive windows have white background
|
|
// and black text.)
|
|
EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
|
|
bool isInActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
|
|
if (!IsDisabled(aFrame, eventState) && isInActiveWindow &&
|
|
!QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
|
|
NS_WARNING("Unable to animate button!");
|
|
}
|
|
bool hasDefaultButtonLook = isInActiveWindow && !eventState.HasState(NS_EVENT_STATE_ACTIVE);
|
|
ButtonType buttonType =
|
|
hasDefaultButtonLook ? ButtonType::eDefaultPushButton : ButtonType::eRegularPushButton;
|
|
ControlParams params = ComputeControlParams(aFrame, eventState);
|
|
params.insideActiveWindow = isInActiveWindow;
|
|
return Some(WidgetInfo::Button(ButtonParams{params, buttonType}));
|
|
}
|
|
if (IsButtonTypeMenu(aFrame)) {
|
|
ControlParams controlParams = ComputeControlParams(aFrame, eventState);
|
|
controlParams.focused = controlParams.focused || IsFocused(aFrame);
|
|
controlParams.pressed = IsOpenButton(aFrame);
|
|
DropdownParams params;
|
|
params.controlParams = controlParams;
|
|
params.pullsDown = true;
|
|
params.editable = false;
|
|
return Some(WidgetInfo::Dropdown(params));
|
|
}
|
|
if (originalHeight > DO_SQUARE_BUTTON_HEIGHT) {
|
|
// If the button is tall enough, draw the square button style so that
|
|
// buttons with non-standard content look good. Otherwise draw normal
|
|
// rounded aqua buttons.
|
|
// This comparison is done based on the height that is calculated without
|
|
// the top, because the snapped height can be affected by the top of the
|
|
// rect and that may result in different height depending on the top value.
|
|
return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
|
|
ButtonType::eSquareBezelPushButton}));
|
|
}
|
|
return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
|
|
ButtonType::eRoundedBezelPushButton}));
|
|
|
|
case StyleAppearance::FocusOutline:
|
|
return Some(WidgetInfo::FocusOutline());
|
|
|
|
case StyleAppearance::MozMacHelpButton:
|
|
return Some(WidgetInfo::Button(
|
|
ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eHelpButton}));
|
|
|
|
case StyleAppearance::MozMacDisclosureButtonOpen:
|
|
case StyleAppearance::MozMacDisclosureButtonClosed: {
|
|
ButtonType buttonType = (aAppearance == StyleAppearance::MozMacDisclosureButtonClosed)
|
|
? ButtonType::eDisclosureButtonClosed
|
|
: ButtonType::eDisclosureButtonOpen;
|
|
return Some(
|
|
WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType}));
|
|
}
|
|
|
|
case StyleAppearance::ButtonBevel: {
|
|
bool isDefaultButton = IsDefaultButton(aFrame);
|
|
ButtonType buttonType =
|
|
isDefaultButton ? ButtonType::eDefaultBevelButton : ButtonType::eRegularBevelButton;
|
|
return Some(
|
|
WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState), buttonType}));
|
|
}
|
|
|
|
case StyleAppearance::InnerSpinButton: {
|
|
case StyleAppearance::Spinner:
|
|
bool isSpinner = (aAppearance == StyleAppearance::Spinner);
|
|
nsIContent* content = aFrame->GetContent();
|
|
if (isSpinner && content->IsHTMLElement()) {
|
|
// In HTML the theming for the spin buttons is drawn individually into
|
|
// their own backgrounds instead of being drawn into the background of
|
|
// their spinner parent as it is for XUL.
|
|
break;
|
|
}
|
|
SpinButtonParams params;
|
|
if (content->IsElement()) {
|
|
if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
|
|
NS_LITERAL_STRING("up"), eCaseMatters)) {
|
|
params.pressedButton = Some(SpinButton::eUp);
|
|
} else if (content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
|
|
NS_LITERAL_STRING("down"), eCaseMatters)) {
|
|
params.pressedButton = Some(SpinButton::eDown);
|
|
}
|
|
}
|
|
params.disabled = IsDisabled(aFrame, eventState);
|
|
params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
|
|
return Some(WidgetInfo::SpinButtons(params));
|
|
}
|
|
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton: {
|
|
nsNumberControlFrame* numberControlFrame =
|
|
nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
|
|
if (numberControlFrame) {
|
|
SpinButtonParams params;
|
|
if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
|
|
params.pressedButton = Some(SpinButton::eUp);
|
|
} else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
|
|
params.pressedButton = Some(SpinButton::eDown);
|
|
}
|
|
params.disabled = IsDisabled(aFrame, eventState);
|
|
params.insideActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
if (aAppearance == StyleAppearance::SpinnerUpbutton) {
|
|
return Some(WidgetInfo::SpinButtonUp(params));
|
|
}
|
|
return Some(WidgetInfo::SpinButtonDown(params));
|
|
}
|
|
} break;
|
|
|
|
case StyleAppearance::Toolbarbutton: {
|
|
SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eToolbarButton);
|
|
params.insideActiveWindow = [NativeWindowForFrame(aFrame) isMainWindow];
|
|
return Some(WidgetInfo::Segment(params));
|
|
}
|
|
|
|
case StyleAppearance::Separator:
|
|
return Some(WidgetInfo::Separator());
|
|
|
|
case StyleAppearance::Toolbar: {
|
|
NSWindow* win = NativeWindowForFrame(aFrame);
|
|
bool isMain = [win isMainWindow];
|
|
if (ToolbarCanBeUnified(nativeWidgetRect, win)) {
|
|
float unifiedHeight =
|
|
std::max(float([(ToolbarWindow*)win unifiedToolbarHeight]), nativeWidgetRect.Height());
|
|
return Some(WidgetInfo::UnifiedToolbar(UnifiedToolbarParams{unifiedHeight, isMain}));
|
|
}
|
|
return Some(WidgetInfo::Toolbar(isMain));
|
|
}
|
|
|
|
case StyleAppearance::MozWindowTitlebar: {
|
|
NSWindow* win = NativeWindowForFrame(aFrame);
|
|
bool isMain = [win isMainWindow];
|
|
float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]]
|
|
? [(ToolbarWindow*)win unifiedToolbarHeight]
|
|
: nativeWidgetRect.Height();
|
|
return Some(WidgetInfo::NativeTitlebar(UnifiedToolbarParams{unifiedToolbarHeight, isMain}));
|
|
}
|
|
|
|
case StyleAppearance::Statusbar:
|
|
return Some(WidgetInfo::StatusBar(IsActive(aFrame, YES)));
|
|
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::Menulist: {
|
|
ControlParams controlParams = ComputeControlParams(aFrame, eventState);
|
|
controlParams.focused = controlParams.focused || IsFocused(aFrame);
|
|
controlParams.pressed = IsOpenButton(aFrame);
|
|
DropdownParams params;
|
|
params.controlParams = controlParams;
|
|
params.pullsDown = false;
|
|
params.editable = false;
|
|
return Some(WidgetInfo::Dropdown(params));
|
|
}
|
|
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
return Some(WidgetInfo::Button(
|
|
ButtonParams{ComputeControlParams(aFrame, eventState), ButtonType::eArrowButton}));
|
|
|
|
case StyleAppearance::Groupbox:
|
|
return Some(WidgetInfo::GroupBox());
|
|
|
|
case StyleAppearance::MenulistTextfield:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::NumberInput: {
|
|
bool isFocused = eventState.HasState(NS_EVENT_STATE_FOCUS);
|
|
// XUL textboxes set the native appearance on the containing box, while
|
|
// concrete focus is set on the html:input element within it. We can
|
|
// though, check the focused attribute of xul textboxes in this case.
|
|
// On Mac, focus rings are always shown for textboxes, so we do not need
|
|
// to check the window's focus ring state here
|
|
if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) {
|
|
isFocused = true;
|
|
}
|
|
|
|
bool isDisabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
|
|
bool borderless = (aAppearance == StyleAppearance::MenulistTextfield && !isFocused);
|
|
return Some(WidgetInfo::TextBox(TextBoxParams{isDisabled, isFocused, borderless}));
|
|
}
|
|
|
|
case StyleAppearance::Searchfield:
|
|
return Some(WidgetInfo::SearchField(ComputeSearchFieldParams(aFrame, eventState)));
|
|
|
|
case StyleAppearance::ProgressBar: {
|
|
// Don't request repaints for scrollbars at 100% because those don't animate.
|
|
if (GetProgressValue(aFrame) < GetProgressMaxValue(aFrame)) {
|
|
if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
|
|
NS_WARNING("Unable to animate progressbar!");
|
|
}
|
|
}
|
|
return Some(WidgetInfo::ProgressBar(
|
|
ComputeProgressParams(aFrame, eventState, !IsVerticalProgress(aFrame))));
|
|
}
|
|
|
|
case StyleAppearance::ProgressbarVertical:
|
|
return Some(WidgetInfo::ProgressBar(ComputeProgressParams(aFrame, eventState, false)));
|
|
|
|
case StyleAppearance::Meter:
|
|
return Some(WidgetInfo::Meter(ComputeMeterParams(aFrame)));
|
|
|
|
case StyleAppearance::Progresschunk:
|
|
case StyleAppearance::Meterchunk:
|
|
// Do nothing: progress and meter bars cases will draw chunks.
|
|
break;
|
|
|
|
case StyleAppearance::Treetwisty:
|
|
return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
|
|
ButtonType::eTreeTwistyPointingRight}));
|
|
|
|
case StyleAppearance::Treetwistyopen:
|
|
return Some(WidgetInfo::Button(ButtonParams{ComputeControlParams(aFrame, eventState),
|
|
ButtonType::eTreeTwistyPointingDown}));
|
|
|
|
case StyleAppearance::Treeheadercell:
|
|
return Some(WidgetInfo::TreeHeaderCell(ComputeTreeHeaderCellParams(aFrame, eventState)));
|
|
|
|
case StyleAppearance::Treeitem:
|
|
case StyleAppearance::Treeview:
|
|
return Some(WidgetInfo::ColorFill(sRGBColor(1.0, 1.0, 1.0, 1.0)));
|
|
|
|
case StyleAppearance::Treeheader:
|
|
// do nothing, taken care of by individual header cells
|
|
case StyleAppearance::Treeheadersortarrow:
|
|
// do nothing, taken care of by treeview header
|
|
case StyleAppearance::Treeline:
|
|
// do nothing, these lines don't exist on macos
|
|
break;
|
|
|
|
case StyleAppearance::ScaleHorizontal:
|
|
case StyleAppearance::ScaleVertical:
|
|
return Some(WidgetInfo::Scale(ComputeXULScaleParams(
|
|
aFrame, eventState, aAppearance == StyleAppearance::ScaleHorizontal)));
|
|
|
|
case StyleAppearance::ScalethumbHorizontal:
|
|
case StyleAppearance::ScalethumbVertical:
|
|
// do nothing, drawn by scale
|
|
break;
|
|
|
|
case StyleAppearance::Range: {
|
|
Maybe<ScaleParams> params = ComputeHTMLScaleParams(aFrame, eventState);
|
|
if (params) {
|
|
return Some(WidgetInfo::Scale(*params));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ScrollbarSmall:
|
|
case StyleAppearance::Scrollbar:
|
|
break;
|
|
case StyleAppearance::ScrollbarthumbVertical:
|
|
case StyleAppearance::ScrollbarthumbHorizontal:
|
|
return Some(WidgetInfo::ScrollbarThumb(ComputeScrollbarParams(
|
|
aFrame, aAppearance == StyleAppearance::ScrollbarthumbHorizontal)));
|
|
|
|
case StyleAppearance::ScrollbarbuttonUp:
|
|
case StyleAppearance::ScrollbarbuttonLeft:
|
|
case StyleAppearance::ScrollbarbuttonDown:
|
|
case StyleAppearance::ScrollbarbuttonRight:
|
|
break;
|
|
|
|
case StyleAppearance::ScrollbartrackHorizontal:
|
|
case StyleAppearance::ScrollbartrackVertical:
|
|
return Some(WidgetInfo::ScrollbarTrack(ComputeScrollbarParams(
|
|
aFrame, aAppearance == StyleAppearance::ScrollbartrackHorizontal)));
|
|
|
|
case StyleAppearance::Scrollcorner:
|
|
return Some(WidgetInfo::ScrollCorner(ComputeScrollbarParams(aFrame, false)));
|
|
|
|
case StyleAppearance::Textarea:
|
|
return Some(WidgetInfo::MultilineTextField(eventState.HasState(NS_EVENT_STATE_FOCUS)));
|
|
|
|
case StyleAppearance::Listbox:
|
|
return Some(WidgetInfo::ListBox());
|
|
|
|
case StyleAppearance::MozMacSourceList: {
|
|
if (VibrancyManager::SystemSupportsVibrancy()) {
|
|
return Nothing();
|
|
}
|
|
return Some(WidgetInfo::SourceList(FrameIsInActiveWindow(aFrame)));
|
|
}
|
|
|
|
case StyleAppearance::MozMacSourceListSelection:
|
|
case StyleAppearance::MozMacActiveSourceListSelection: {
|
|
// If we're in XUL tree, we need to rely on the source list's clear
|
|
// background display item. If we cleared the background behind the
|
|
// selections, the source list would not pick up the right font
|
|
// smoothing background. So, to simplify a bit, we only support vibrancy
|
|
// if we're in a source list.
|
|
if (VibrancyManager::SystemSupportsVibrancy() && IsInSourceList(aFrame)) {
|
|
return Nothing();
|
|
}
|
|
bool isInActiveWindow = FrameIsInActiveWindow(aFrame);
|
|
if (aAppearance == StyleAppearance::MozMacActiveSourceListSelection) {
|
|
return Some(WidgetInfo::ActiveSourceListSelection(isInActiveWindow));
|
|
}
|
|
return Some(WidgetInfo::InactiveSourceListSelection(isInActiveWindow));
|
|
}
|
|
|
|
case StyleAppearance::Tab: {
|
|
SegmentParams params = ComputeSegmentParams(aFrame, eventState, SegmentType::eTab);
|
|
params.pressed = params.pressed && !params.selected;
|
|
return Some(WidgetInfo::Segment(params));
|
|
}
|
|
|
|
case StyleAppearance::Tabpanels:
|
|
return Some(WidgetInfo::TabPanel(FrameIsInActiveWindow(aFrame)));
|
|
|
|
case StyleAppearance::Resizer:
|
|
return Some(WidgetInfo::Resizer(IsFrameRTL(aFrame)));
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return Nothing();
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(Nothing());
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeCocoa::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance, const nsRect& aRect,
|
|
const nsRect& aDirtyRect) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
|
|
|
Maybe<WidgetInfo> widgetInfo = ComputeWidgetInfo(aFrame, aAppearance, aRect);
|
|
|
|
if (!widgetInfo) {
|
|
return NS_OK;
|
|
}
|
|
|
|
int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
|
|
|
|
gfx::Rect nativeWidgetRect = NSRectToRect(aRect, p2a);
|
|
nativeWidgetRect.Round();
|
|
|
|
bool hidpi = IsHiDPIContext(aFrame->PresContext()->DeviceContext());
|
|
|
|
RenderWidget(*widgetInfo, *aContext->GetDrawTarget(), nativeWidgetRect,
|
|
NSRectToRect(aDirtyRect, p2a), hidpi ? 2.0f : 1.0f);
|
|
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
|
}
|
|
|
|
void nsNativeThemeCocoa::RenderWidget(const WidgetInfo& aWidgetInfo, DrawTarget& aDrawTarget,
|
|
const gfx::Rect& aWidgetRect, const gfx::Rect& aDirtyRect,
|
|
float aScale) {
|
|
AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
|
|
|
|
gfx::Rect dirtyRect = aDirtyRect;
|
|
gfx::Rect widgetRect = aWidgetRect;
|
|
dirtyRect.Scale(1.0f / aScale);
|
|
widgetRect.Scale(1.0f / aScale);
|
|
aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(aScale, aScale));
|
|
|
|
CGRect macRect =
|
|
CGRectMake(widgetRect.X(), widgetRect.Y(), widgetRect.Width(), widgetRect.Height());
|
|
|
|
gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, dirtyRect);
|
|
|
|
CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
|
|
if (cgContext == nullptr) {
|
|
// The Quartz surface handles 0x0 surfaces by internally
|
|
// making all operations no-ops; there's no cgcontext created for them.
|
|
// Unfortunately, this means that callers that want to render
|
|
// directly to the CGContext need to be aware of this quirk.
|
|
return;
|
|
}
|
|
|
|
// Set the context's "base transform" to in order to get correctly-sized focus rings.
|
|
CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(aScale, aScale));
|
|
|
|
switch (aWidgetInfo.Widget()) {
|
|
case Widget::eColorFill: {
|
|
sRGBColor params = aWidgetInfo.Params<sRGBColor>();
|
|
SetCGContextFillColor(cgContext, params);
|
|
CGContextFillRect(cgContext, macRect);
|
|
break;
|
|
}
|
|
case Widget::eSheetBackground: {
|
|
HIThemeSetFill(kThemeBrushSheetBackgroundTransparent, NULL, cgContext, HITHEME_ORIENTATION);
|
|
CGContextFillRect(cgContext, macRect);
|
|
break;
|
|
}
|
|
case Widget::eDialogBackground: {
|
|
HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
|
|
CGContextFillRect(cgContext, macRect);
|
|
break;
|
|
}
|
|
case Widget::eMenuBackground: {
|
|
MenuBackgroundParams params = aWidgetInfo.Params<MenuBackgroundParams>();
|
|
DrawMenuBackground(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eMenuIcon: {
|
|
MenuIconParams params = aWidgetInfo.Params<MenuIconParams>();
|
|
DrawMenuIcon(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eMenuItem: {
|
|
MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
|
|
DrawMenuItem(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eMenuSeparator: {
|
|
MenuItemParams params = aWidgetInfo.Params<MenuItemParams>();
|
|
DrawMenuSeparator(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eTooltip: {
|
|
SetCGContextFillColor(cgContext, kTooltipBackgroundColor);
|
|
CGContextFillRect(cgContext, macRect);
|
|
break;
|
|
}
|
|
case Widget::eCheckbox: {
|
|
CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
|
|
DrawCheckboxOrRadio(cgContext, true, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eRadio: {
|
|
CheckboxOrRadioParams params = aWidgetInfo.Params<CheckboxOrRadioParams>();
|
|
DrawCheckboxOrRadio(cgContext, false, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eButton: {
|
|
ButtonParams params = aWidgetInfo.Params<ButtonParams>();
|
|
DrawButton(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eDropdown: {
|
|
DropdownParams params = aWidgetInfo.Params<DropdownParams>();
|
|
DrawDropdown(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eFocusOutline: {
|
|
DrawFocusOutline(cgContext, macRect);
|
|
break;
|
|
}
|
|
case Widget::eSpinButtons: {
|
|
SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
|
|
DrawSpinButtons(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eSpinButtonUp: {
|
|
SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
|
|
DrawSpinButton(cgContext, macRect, SpinButton::eUp, params);
|
|
break;
|
|
}
|
|
case Widget::eSpinButtonDown: {
|
|
SpinButtonParams params = aWidgetInfo.Params<SpinButtonParams>();
|
|
DrawSpinButton(cgContext, macRect, SpinButton::eDown, params);
|
|
break;
|
|
}
|
|
case Widget::eSegment: {
|
|
SegmentParams params = aWidgetInfo.Params<SegmentParams>();
|
|
DrawSegment(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eSeparator: {
|
|
HIThemeSeparatorDrawInfo sdi = {0, kThemeStateActive};
|
|
HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
|
|
break;
|
|
}
|
|
case Widget::eUnifiedToolbar: {
|
|
UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
|
|
DrawUnifiedToolbar(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eToolbar: {
|
|
bool isMain = aWidgetInfo.Params<bool>();
|
|
DrawToolbar(cgContext, macRect, isMain);
|
|
break;
|
|
}
|
|
case Widget::eNativeTitlebar: {
|
|
UnifiedToolbarParams params = aWidgetInfo.Params<UnifiedToolbarParams>();
|
|
DrawNativeTitlebar(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eStatusBar: {
|
|
bool isMain = aWidgetInfo.Params<bool>();
|
|
DrawStatusBar(cgContext, macRect, isMain);
|
|
break;
|
|
}
|
|
case Widget::eGroupBox: {
|
|
HIThemeGroupBoxDrawInfo gdi = {0, kThemeStateActive, kHIThemeGroupBoxKindPrimary};
|
|
HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
|
|
break;
|
|
}
|
|
case Widget::eTextBox: {
|
|
TextBoxParams params = aWidgetInfo.Params<TextBoxParams>();
|
|
DrawTextBox(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eSearchField: {
|
|
SearchFieldParams params = aWidgetInfo.Params<SearchFieldParams>();
|
|
DrawSearchField(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eProgressBar: {
|
|
ProgressParams params = aWidgetInfo.Params<ProgressParams>();
|
|
DrawProgress(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eMeter: {
|
|
MeterParams params = aWidgetInfo.Params<MeterParams>();
|
|
DrawMeter(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eTreeHeaderCell: {
|
|
TreeHeaderCellParams params = aWidgetInfo.Params<TreeHeaderCellParams>();
|
|
DrawTreeHeaderCell(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eScale: {
|
|
ScaleParams params = aWidgetInfo.Params<ScaleParams>();
|
|
DrawScale(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eScrollbarThumb: {
|
|
ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
|
|
DrawScrollbarThumb(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eScrollbarTrack: {
|
|
ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
|
|
DrawScrollbarTrack(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eScrollCorner: {
|
|
ScrollbarParams params = aWidgetInfo.Params<ScrollbarParams>();
|
|
DrawScrollCorner(cgContext, macRect, params);
|
|
break;
|
|
}
|
|
case Widget::eMultilineTextField: {
|
|
bool isFocused = aWidgetInfo.Params<bool>();
|
|
DrawMultilineTextField(cgContext, macRect, isFocused);
|
|
break;
|
|
}
|
|
case Widget::eListBox: {
|
|
// We have to draw this by hand because kHIThemeFrameListBox drawing
|
|
// is buggy on 10.5, see bug 579259.
|
|
SetCGContextFillColor(cgContext, sRGBColor(1.0, 1.0, 1.0, 1.0));
|
|
CGContextFillRect(cgContext, macRect);
|
|
|
|
float x = macRect.origin.x, y = macRect.origin.y;
|
|
float w = macRect.size.width, h = macRect.size.height;
|
|
SetCGContextFillColor(cgContext, kListboxTopBorderColor);
|
|
CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
|
|
SetCGContextFillColor(cgContext, kListBoxSidesAndBottomBorderColor);
|
|
CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
|
|
CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
|
|
CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
|
|
break;
|
|
}
|
|
case Widget::eSourceList: {
|
|
bool isInActiveWindow = aWidgetInfo.Params<bool>();
|
|
DrawSourceList(cgContext, macRect, isInActiveWindow);
|
|
break;
|
|
}
|
|
case Widget::eActiveSourceListSelection:
|
|
case Widget::eInactiveSourceListSelection: {
|
|
bool isInActiveWindow = aWidgetInfo.Params<bool>();
|
|
BOOL isActiveSelection = aWidgetInfo.Widget() == Widget::eActiveSourceListSelection;
|
|
RenderWithCoreUI(
|
|
macRect, cgContext,
|
|
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:isActiveSelection],
|
|
@"focus", [NSNumber numberWithBool:YES],
|
|
@"is.flipped",
|
|
@"kCUIVariantGradientSideBarSelection",
|
|
@"kCUIVariantKey",
|
|
(isInActiveWindow ? @"normal" : @"inactive"),
|
|
@"state", @"gradient", @"widget", nil]);
|
|
break;
|
|
}
|
|
case Widget::eTabPanel: {
|
|
bool isInsideActiveWindow = aWidgetInfo.Params<bool>();
|
|
DrawTabPanel(cgContext, macRect, isInsideActiveWindow);
|
|
break;
|
|
}
|
|
case Widget::eResizer: {
|
|
bool isRTL = aWidgetInfo.Params<bool>();
|
|
DrawResizer(cgContext, macRect, isRTL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Reset the base CTM.
|
|
CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
|
|
|
|
nativeDrawing.EndNativeDrawing();
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::CreateWebRenderCommandsForWidget(
|
|
mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources,
|
|
const mozilla::layers::StackingContextHelper& aSc,
|
|
mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance, const nsRect& aRect) {
|
|
nsPresContext* presContext = aFrame->PresContext();
|
|
wr::LayoutRect bounds =
|
|
wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, presContext->AppUnitsPerDevPixel()));
|
|
|
|
EventStates eventState = GetContentState(aFrame, aAppearance);
|
|
|
|
// This list needs to stay consistent with the list in DrawWidgetBackground.
|
|
// For every switch case in DrawWidgetBackground, there are three choices:
|
|
// - If the case in DrawWidgetBackground draws nothing for the given widget
|
|
// type, then don't list it here. We will hit the "default: return true;"
|
|
// case.
|
|
// - If the case in DrawWidgetBackground draws something simple for the given
|
|
// widget type, imitate that drawing using WebRender commands.
|
|
// - If the case in DrawWidgetBackground draws something complicated for the
|
|
// given widget type, return false here.
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Dialog:
|
|
if (IsWindowSheet(aFrame) && VibrancyManager::SystemSupportsVibrancy()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case StyleAppearance::Menupopup:
|
|
if (VibrancyManager::SystemSupportsVibrancy()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case StyleAppearance::Menuarrow:
|
|
case StyleAppearance::Menuitem:
|
|
case StyleAppearance::Checkmenuitem:
|
|
case StyleAppearance::Menuseparator:
|
|
return false;
|
|
|
|
case StyleAppearance::ButtonArrowUp:
|
|
case StyleAppearance::ButtonArrowDown:
|
|
return false;
|
|
|
|
case StyleAppearance::Tooltip:
|
|
if (!VibrancyManager::SystemSupportsVibrancy()) {
|
|
aBuilder.PushRect(bounds, bounds, true,
|
|
wr::ToColorF(ToDeviceColor(kTooltipBackgroundColor)));
|
|
}
|
|
return true;
|
|
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Button:
|
|
case StyleAppearance::FocusOutline:
|
|
case StyleAppearance::MozMacHelpButton:
|
|
case StyleAppearance::MozMacDisclosureButtonOpen:
|
|
case StyleAppearance::MozMacDisclosureButtonClosed:
|
|
case StyleAppearance::ButtonBevel:
|
|
case StyleAppearance::Spinner:
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton:
|
|
case StyleAppearance::Toolbarbutton:
|
|
case StyleAppearance::Separator:
|
|
case StyleAppearance::Toolbar:
|
|
case StyleAppearance::MozWindowTitlebar:
|
|
case StyleAppearance::Statusbar:
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistTextfield:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
case StyleAppearance::Groupbox:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Searchfield:
|
|
case StyleAppearance::ProgressBar:
|
|
case StyleAppearance::ProgressbarVertical:
|
|
case StyleAppearance::Meter:
|
|
case StyleAppearance::Treetwisty:
|
|
case StyleAppearance::Treetwistyopen:
|
|
case StyleAppearance::Treeheadercell:
|
|
case StyleAppearance::Treeitem:
|
|
case StyleAppearance::Treeview:
|
|
case StyleAppearance::ScaleHorizontal:
|
|
case StyleAppearance::ScaleVertical:
|
|
case StyleAppearance::Range:
|
|
case StyleAppearance::ScrollbarthumbVertical:
|
|
case StyleAppearance::ScrollbarthumbHorizontal:
|
|
return false;
|
|
|
|
case StyleAppearance::Scrollcorner:
|
|
case StyleAppearance::ScrollbartrackHorizontal:
|
|
case StyleAppearance::ScrollbartrackVertical: {
|
|
BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
|
|
if (isOverlay && !IsParentScrollbarRolledOver(aFrame)) {
|
|
// There is no scrollbar track, draw nothing and return true.
|
|
return true;
|
|
}
|
|
// There is a scrollbar track and it needs to be drawn using fallback.
|
|
return false;
|
|
}
|
|
|
|
case StyleAppearance::Textarea: {
|
|
if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
|
|
// We can't draw the focus ring using webrender, so fall back to regular
|
|
// drawing if we're focused.
|
|
return false;
|
|
}
|
|
|
|
// White background
|
|
aBuilder.PushRect(bounds, bounds, true,
|
|
wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
|
|
|
|
wr::BorderSide side[4] = {
|
|
wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldTopBorderColor),
|
|
StyleBorderStyle::Solid),
|
|
wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
|
|
StyleBorderStyle::Solid),
|
|
wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
|
|
StyleBorderStyle::Solid),
|
|
wr::ToBorderSide(ToDeviceColor(kMultilineTextFieldSidesAndBottomBorderColor),
|
|
StyleBorderStyle::Solid),
|
|
};
|
|
|
|
wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
|
|
float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
|
|
wr::LayoutSideOffsets borderWidths =
|
|
wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
|
|
|
|
mozilla::Range<const wr::BorderSide> wrsides(side, 4);
|
|
aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
|
|
|
|
return true;
|
|
}
|
|
|
|
case StyleAppearance::Listbox: {
|
|
// White background
|
|
aBuilder.PushRect(bounds, bounds, true,
|
|
wr::ToColorF(ToDeviceColor(sRGBColor::OpaqueWhite())));
|
|
|
|
wr::BorderSide side[4] = {
|
|
wr::ToBorderSide(ToDeviceColor(kListboxTopBorderColor), StyleBorderStyle::Solid),
|
|
wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
|
|
StyleBorderStyle::Solid),
|
|
wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
|
|
StyleBorderStyle::Solid),
|
|
wr::ToBorderSide(ToDeviceColor(kListBoxSidesAndBottomBorderColor),
|
|
StyleBorderStyle::Solid),
|
|
};
|
|
|
|
wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
|
|
float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
|
|
wr::LayoutSideOffsets borderWidths =
|
|
wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
|
|
|
|
mozilla::Range<const wr::BorderSide> wrsides(side, 4);
|
|
aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
|
|
return true;
|
|
}
|
|
|
|
case StyleAppearance::MozMacSourceList:
|
|
if (VibrancyManager::SystemSupportsVibrancy()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case StyleAppearance::Tab:
|
|
case StyleAppearance::Tabpanels:
|
|
case StyleAppearance::Resizer:
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
LayoutDeviceIntMargin nsNativeThemeCocoa::DirectionAwareMargin(const LayoutDeviceIntMargin& aMargin,
|
|
nsIFrame* aFrame) {
|
|
// Assuming aMargin was originally specified for a horizontal LTR context,
|
|
// reinterpret the values as logical, and then map to physical coords
|
|
// according to aFrame's actual writing mode.
|
|
WritingMode wm = aFrame->GetWritingMode();
|
|
nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom, aMargin.left)
|
|
.GetPhysicalMargin(wm);
|
|
return LayoutDeviceIntMargin(m.top, m.right, m.bottom, m.left);
|
|
}
|
|
|
|
static const LayoutDeviceIntMargin kAquaDropdownBorder(1, 22, 2, 5);
|
|
static const LayoutDeviceIntMargin kAquaComboboxBorder(3, 20, 3, 4);
|
|
static const LayoutDeviceIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
|
|
|
|
LayoutDeviceIntMargin nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance) {
|
|
LayoutDeviceIntMargin result;
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Button: {
|
|
if (IsButtonTypeMenu(aFrame)) {
|
|
result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
|
|
} else {
|
|
result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 7, 3, 7), aFrame);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Toolbarbutton: {
|
|
result = DirectionAwareMargin(LayoutDeviceIntMargin(1, 4, 1, 4), aFrame);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::Radio: {
|
|
// nsCheckboxRadioFrame::GetIntrinsicWidth and nsCheckboxRadioFrame::GetIntrinsicHeight
|
|
// assume a border width of 2px.
|
|
result.SizeTo(2, 2, 2, 2);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
result = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
|
|
break;
|
|
|
|
case StyleAppearance::MenulistTextfield:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Textfield: {
|
|
SInt32 frameOutset = 0;
|
|
::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
|
|
|
|
SInt32 textPadding = 0;
|
|
::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
|
|
|
|
frameOutset += textPadding;
|
|
|
|
result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Textarea:
|
|
result.SizeTo(1, 1, 1, 1);
|
|
break;
|
|
|
|
case StyleAppearance::Searchfield:
|
|
result = DirectionAwareMargin(kAquaSearchfieldBorder, aFrame);
|
|
break;
|
|
|
|
case StyleAppearance::Listbox: {
|
|
SInt32 frameOutset = 0;
|
|
::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
|
|
result.SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ScrollbartrackHorizontal:
|
|
case StyleAppearance::ScrollbartrackVertical: {
|
|
bool isHorizontal = (aAppearance == StyleAppearance::ScrollbartrackHorizontal);
|
|
if (nsLookAndFeel::UseOverlayScrollbars()) {
|
|
if (!nsCocoaFeatures::OnYosemiteOrLater()) {
|
|
// Pre-10.10, we have to center the thumb rect in the middle of the
|
|
// scrollbar. Starting with 10.10, the expected rect for thumb
|
|
// rendering is the full width of the scrollbar.
|
|
if (isHorizontal) {
|
|
result.top = 2;
|
|
result.bottom = 1;
|
|
} else {
|
|
result.left = 2;
|
|
result.right = 1;
|
|
}
|
|
}
|
|
// Leave a bit of space at the start and the end on all OS X versions.
|
|
if (isHorizontal) {
|
|
result.left = 1;
|
|
result.right = 1;
|
|
} else {
|
|
result.top = 1;
|
|
result.bottom = 1;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Statusbar:
|
|
result.SizeTo(1, 0, 0, 0);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (IsHiDPIContext(aContext)) {
|
|
result = result + result; // doubled
|
|
}
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(result);
|
|
}
|
|
|
|
// Return false here to indicate that CSS padding values should be used. There is
|
|
// no reason to make a distinction between padding and border values, just specify
|
|
// whatever values you want in GetWidgetBorder and only use this to return true
|
|
// if you want to override CSS padding values.
|
|
bool nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
LayoutDeviceIntMargin* aResult) {
|
|
// We don't want CSS padding being used for certain widgets.
|
|
// See bug 381639 for an example of why.
|
|
switch (aAppearance) {
|
|
// 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);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance, nsRect* aOverflowRect) {
|
|
nsIntMargin overflow;
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Button:
|
|
case StyleAppearance::MozMacDisclosureButtonOpen:
|
|
case StyleAppearance::MozMacDisclosureButtonClosed:
|
|
case StyleAppearance::MozMacHelpButton:
|
|
case StyleAppearance::Toolbarbutton:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Searchfield:
|
|
case StyleAppearance::Listbox:
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
case StyleAppearance::MenulistTextfield:
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Tab:
|
|
case StyleAppearance::FocusOutline: {
|
|
overflow.SizeTo(kMaxFocusRingWidth, kMaxFocusRingWidth, kMaxFocusRingWidth,
|
|
kMaxFocusRingWidth);
|
|
break;
|
|
}
|
|
case StyleAppearance::ProgressBar: {
|
|
// Progress bars draw a 2 pixel white shadow under their progress indicators.
|
|
overflow.bottom = 2;
|
|
break;
|
|
}
|
|
case StyleAppearance::Meter: {
|
|
// Meter bars overflow their boxes by about 2 pixels.
|
|
overflow.SizeTo(2, 2, 2, 2);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (IsHiDPIContext(aContext)) {
|
|
// Double the number of device pixels.
|
|
overflow += overflow;
|
|
}
|
|
|
|
if (overflow != nsIntMargin()) {
|
|
int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
|
|
aOverflowRect->Inflate(nsMargin(
|
|
NSIntPixelsToAppUnits(overflow.top, p2a), NSIntPixelsToAppUnits(overflow.right, p2a),
|
|
NSIntPixelsToAppUnits(overflow.bottom, p2a), NSIntPixelsToAppUnits(overflow.left, p2a)));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static const int32_t kRegularScrollbarThumbMinSize = 26;
|
|
static const int32_t kSmallScrollbarThumbMinSize = 26;
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance, LayoutDeviceIntSize* aResult,
|
|
bool* aIsOverridable) {
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
|
|
|
aResult->SizeTo(0, 0);
|
|
*aIsOverridable = true;
|
|
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Button: {
|
|
aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
|
|
pushButtonSettings.naturalSizes[miniControlSize].height);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ButtonArrowUp:
|
|
case StyleAppearance::ButtonArrowDown: {
|
|
aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Menuarrow: {
|
|
aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::MozMacDisclosureButtonOpen:
|
|
case StyleAppearance::MozMacDisclosureButtonClosed: {
|
|
aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::MozMacHelpButton: {
|
|
aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Toolbarbutton: {
|
|
aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::InnerSpinButton:
|
|
case StyleAppearance::Spinner:
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton: {
|
|
SInt32 buttonHeight = 0, buttonWidth = 0;
|
|
if (aFrame->GetContent()->IsXULElement()) {
|
|
::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
|
|
::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
|
|
} else {
|
|
NSSize size = spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
|
|
buttonWidth = size.width;
|
|
buttonHeight = size.height;
|
|
if (aAppearance != StyleAppearance::Spinner) {
|
|
// the buttons are half the height of the spinner
|
|
buttonHeight /= 2;
|
|
}
|
|
}
|
|
aResult->SizeTo(buttonWidth, buttonHeight);
|
|
*aIsOverridable = true;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::MozMenulistArrowButton: {
|
|
SInt32 popupHeight = 0;
|
|
::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
|
|
aResult->SizeTo(0, popupHeight);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::MenulistTextfield:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Searchfield: {
|
|
// at minimum, we should be tall enough for 9pt text.
|
|
// I'm using hardcoded values here because the appearance manager
|
|
// values for the frame size are incorrect.
|
|
aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::MozWindowButtonBox: {
|
|
NSSize size = WindowButtonsSize(aFrame);
|
|
aResult->SizeTo(size.width, size.height);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::MozMacFullscreenButton: {
|
|
if ([NativeWindowForFrame(aFrame) respondsToSelector:@selector(toggleFullScreen:)] &&
|
|
!nsCocoaFeatures::OnYosemiteOrLater()) {
|
|
// This value is hardcoded because it's needed before we can measure the
|
|
// position and size of the fullscreen button.
|
|
aResult->SizeTo(16, 17);
|
|
}
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ProgressBar: {
|
|
SInt32 barHeight = 0;
|
|
::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
|
|
aResult->SizeTo(0, barHeight);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Separator: {
|
|
aResult->SizeTo(1, 1);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Treetwisty:
|
|
case StyleAppearance::Treetwistyopen: {
|
|
SInt32 twistyHeight = 0, twistyWidth = 0;
|
|
::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
|
|
::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
|
|
aResult->SizeTo(twistyWidth, twistyHeight);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Treeheader:
|
|
case StyleAppearance::Treeheadercell: {
|
|
SInt32 headerHeight = 0;
|
|
::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
|
|
aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Tab: {
|
|
aResult->SizeTo(0, tabHeights[miniControlSize]);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::RangeThumb: {
|
|
SInt32 width = 0;
|
|
SInt32 height = 0;
|
|
::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
|
|
::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
|
|
aResult->SizeTo(width, height);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ScaleHorizontal: {
|
|
SInt32 scaleHeight = 0;
|
|
::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
|
|
aResult->SizeTo(scaleHeight, scaleHeight);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ScaleVertical: {
|
|
SInt32 scaleWidth = 0;
|
|
::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
|
|
aResult->SizeTo(scaleWidth, scaleWidth);
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ScrollbarthumbHorizontal:
|
|
case StyleAppearance::ScrollbarthumbVertical: {
|
|
bool isSmall = IsSmallScrollbar(aFrame);
|
|
bool isHorizontal = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal);
|
|
int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
|
|
minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::Scrollbar:
|
|
case StyleAppearance::ScrollbarSmall:
|
|
case StyleAppearance::ScrollbartrackVertical:
|
|
case StyleAppearance::ScrollbartrackHorizontal: {
|
|
*aIsOverridable = false;
|
|
|
|
if (nsLookAndFeel::UseOverlayScrollbars()) {
|
|
if (IsSmallScrollbar(aFrame)) {
|
|
aResult->SizeTo(14, 14);
|
|
} else {
|
|
aResult->SizeTo(16, 16);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// yeah, i know i'm cheating a little here, but i figure that it
|
|
// really doesn't matter if the scrollbar is vertical or horizontal
|
|
// and the width metric is a really good metric for every piece
|
|
// of the scrollbar.
|
|
|
|
int32_t themeMetric =
|
|
IsSmallScrollbar(aFrame) ? kThemeMetricSmallScrollBarWidth : kThemeMetricScrollBarWidth;
|
|
SInt32 scrollbarWidth = 0;
|
|
::GetThemeMetric(themeMetric, &scrollbarWidth);
|
|
aResult->SizeTo(scrollbarWidth, scrollbarWidth);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ScrollbarNonDisappearing: {
|
|
int32_t themeMetric = kThemeMetricScrollBarWidth;
|
|
SInt32 scrollbarWidth = 0;
|
|
::GetThemeMetric(themeMetric, &scrollbarWidth);
|
|
aResult->SizeTo(scrollbarWidth, scrollbarWidth);
|
|
break;
|
|
}
|
|
|
|
case StyleAppearance::ScrollbarbuttonUp:
|
|
case StyleAppearance::ScrollbarbuttonDown:
|
|
case StyleAppearance::ScrollbarbuttonLeft:
|
|
case StyleAppearance::ScrollbarbuttonRight: {
|
|
int32_t themeMetric =
|
|
IsSmallScrollbar(aFrame) ? kThemeMetricSmallScrollBarWidth : kThemeMetricScrollBarWidth;
|
|
SInt32 scrollbarWidth = 0;
|
|
::GetThemeMetric(themeMetric, &scrollbarWidth);
|
|
|
|
// It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
|
|
if (aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
|
|
aAppearance == StyleAppearance::ScrollbarbuttonRight)
|
|
aResult->SizeTo(scrollbarWidth + 1, scrollbarWidth);
|
|
else
|
|
aResult->SizeTo(scrollbarWidth, scrollbarWidth + 1);
|
|
|
|
*aIsOverridable = false;
|
|
break;
|
|
}
|
|
case StyleAppearance::Resizer: {
|
|
HIThemeGrowBoxDrawInfo drawInfo;
|
|
drawInfo.version = 0;
|
|
drawInfo.state = kThemeStateActive;
|
|
drawInfo.kind = kHIThemeGrowBoxKindNormal;
|
|
drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
|
|
drawInfo.size = kHIThemeGrowBoxSizeNormal;
|
|
HIPoint pnt = {0, 0};
|
|
HIRect bounds;
|
|
HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
|
|
aResult->SizeTo(bounds.size.width, bounds.size.height);
|
|
*aIsOverridable = false;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (IsHiDPIContext(aPresContext->DeviceContext())) {
|
|
*aResult = *aResult * 2;
|
|
}
|
|
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
|
|
nsAtom* aAttribute, bool* aShouldRepaint,
|
|
const nsAttrValue* aOldValue) {
|
|
// Some widget types just never change state.
|
|
switch (aAppearance) {
|
|
case StyleAppearance::MozWindowTitlebar:
|
|
case StyleAppearance::Toolbox:
|
|
case StyleAppearance::Toolbar:
|
|
case StyleAppearance::Statusbar:
|
|
case StyleAppearance::Statusbarpanel:
|
|
case StyleAppearance::Resizerpanel:
|
|
case StyleAppearance::Tooltip:
|
|
case StyleAppearance::Tabpanels:
|
|
case StyleAppearance::Tabpanel:
|
|
case StyleAppearance::Dialog:
|
|
case StyleAppearance::Menupopup:
|
|
case StyleAppearance::Groupbox:
|
|
case StyleAppearance::Progresschunk:
|
|
case StyleAppearance::ProgressBar:
|
|
case StyleAppearance::ProgressbarVertical:
|
|
case StyleAppearance::Meter:
|
|
case StyleAppearance::Meterchunk:
|
|
case StyleAppearance::MozMacVibrancyLight:
|
|
case StyleAppearance::MozMacVibrancyDark:
|
|
case StyleAppearance::MozMacVibrantTitlebarLight:
|
|
case StyleAppearance::MozMacVibrantTitlebarDark:
|
|
*aShouldRepaint = false;
|
|
return NS_OK;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// XXXdwh Not sure what can really be done here. Can at least guess for
|
|
// specific widgets that they're highly unlikely to have certain states.
|
|
// For example, a toolbar doesn't care about any states.
|
|
if (!aAttribute) {
|
|
// Hover/focus/active changed. Always repaint.
|
|
*aShouldRepaint = true;
|
|
} else {
|
|
// Check the attribute to see if it's relevant.
|
|
// disabled, checked, dlgtype, default, etc.
|
|
*aShouldRepaint = false;
|
|
if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
|
|
aAttribute == nsGkAtoms::selected || aAttribute == nsGkAtoms::visuallyselected ||
|
|
aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::sortDirection ||
|
|
aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
|
|
aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover)
|
|
*aShouldRepaint = true;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeCocoa::ThemeChanged() {
|
|
// This is unimplemented because we don't care if gecko changes its theme
|
|
// and macOS system appearance changes are handled by
|
|
// nsLookAndFeel::SystemWantsDarkTheme.
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance) {
|
|
// if this is a dropdown button in a combobox the answer is always no
|
|
if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
|
|
nsIFrame* parentFrame = aFrame->GetParent();
|
|
if (parentFrame && parentFrame->IsComboboxControlFrame()) return false;
|
|
}
|
|
|
|
switch (aAppearance) {
|
|
// Combobox dropdowns don't support native theming in vertical mode.
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
case StyleAppearance::MenulistText:
|
|
if (aFrame && aFrame->GetWritingMode().IsVertical()) {
|
|
return false;
|
|
}
|
|
[[fallthrough]];
|
|
|
|
case StyleAppearance::Listbox:
|
|
case StyleAppearance::Dialog:
|
|
case StyleAppearance::Window:
|
|
case StyleAppearance::MozWindowButtonBox:
|
|
case StyleAppearance::MozWindowTitlebar:
|
|
case StyleAppearance::Checkmenuitem:
|
|
case StyleAppearance::Menupopup:
|
|
case StyleAppearance::Menuarrow:
|
|
case StyleAppearance::Menuitem:
|
|
case StyleAppearance::Menuseparator:
|
|
case StyleAppearance::MozMacFullscreenButton:
|
|
case StyleAppearance::Tooltip:
|
|
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::CheckboxContainer:
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::RadioContainer:
|
|
case StyleAppearance::Groupbox:
|
|
case StyleAppearance::MozMacHelpButton:
|
|
case StyleAppearance::MozMacDisclosureButtonOpen:
|
|
case StyleAppearance::MozMacDisclosureButtonClosed:
|
|
case StyleAppearance::Button:
|
|
case StyleAppearance::ButtonArrowUp:
|
|
case StyleAppearance::ButtonArrowDown:
|
|
case StyleAppearance::ButtonBevel:
|
|
case StyleAppearance::Toolbarbutton:
|
|
case StyleAppearance::InnerSpinButton:
|
|
case StyleAppearance::Spinner:
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton:
|
|
case StyleAppearance::Toolbar:
|
|
case StyleAppearance::Statusbar:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Searchfield:
|
|
case StyleAppearance::Toolbox:
|
|
// case StyleAppearance::Toolbarbutton:
|
|
case StyleAppearance::ProgressBar:
|
|
case StyleAppearance::ProgressbarVertical:
|
|
case StyleAppearance::Progresschunk:
|
|
case StyleAppearance::Meter:
|
|
case StyleAppearance::Meterchunk:
|
|
case StyleAppearance::Separator:
|
|
|
|
case StyleAppearance::Tabpanels:
|
|
case StyleAppearance::Tab:
|
|
|
|
case StyleAppearance::Treetwisty:
|
|
case StyleAppearance::Treetwistyopen:
|
|
case StyleAppearance::Treeview:
|
|
case StyleAppearance::Treeheader:
|
|
case StyleAppearance::Treeheadercell:
|
|
case StyleAppearance::Treeheadersortarrow:
|
|
case StyleAppearance::Treeitem:
|
|
case StyleAppearance::Treeline:
|
|
case StyleAppearance::MenulistTextfield:
|
|
case StyleAppearance::MozMacSourceList:
|
|
case StyleAppearance::MozMacSourceListSelection:
|
|
case StyleAppearance::MozMacActiveSourceListSelection:
|
|
|
|
case StyleAppearance::Range:
|
|
|
|
case StyleAppearance::ScaleHorizontal:
|
|
case StyleAppearance::ScalethumbHorizontal:
|
|
case StyleAppearance::ScaleVertical:
|
|
case StyleAppearance::ScalethumbVertical:
|
|
|
|
case StyleAppearance::Scrollbar:
|
|
case StyleAppearance::ScrollbarSmall:
|
|
case StyleAppearance::ScrollbarbuttonUp:
|
|
case StyleAppearance::ScrollbarbuttonDown:
|
|
case StyleAppearance::ScrollbarbuttonLeft:
|
|
case StyleAppearance::ScrollbarbuttonRight:
|
|
case StyleAppearance::ScrollbarthumbHorizontal:
|
|
case StyleAppearance::ScrollbarthumbVertical:
|
|
case StyleAppearance::ScrollbartrackVertical:
|
|
case StyleAppearance::ScrollbartrackHorizontal:
|
|
case StyleAppearance::ScrollbarNonDisappearing:
|
|
return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
|
|
|
|
case StyleAppearance::Scrollcorner:
|
|
return true;
|
|
|
|
case StyleAppearance::Resizer: {
|
|
nsIFrame* parentFrame = aFrame->GetParent();
|
|
if (!parentFrame || !parentFrame->IsScrollFrame()) return true;
|
|
|
|
// Note that IsWidgetStyled is not called for resizers on Mac. This is
|
|
// because for scrollable containers, the native resizer looks better
|
|
// when (non-overlay) scrollbars are present even when the style is
|
|
// overriden, and the custom transparent resizer looks better when
|
|
// scrollbars are not present.
|
|
nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
|
|
return (!nsLookAndFeel::UseOverlayScrollbars() && scrollFrame &&
|
|
scrollFrame->GetScrollbarVisibility());
|
|
}
|
|
|
|
case StyleAppearance::FocusOutline:
|
|
return true;
|
|
|
|
case StyleAppearance::MozMacVibrancyLight:
|
|
case StyleAppearance::MozMacVibrancyDark:
|
|
case StyleAppearance::MozMacVibrantTitlebarLight:
|
|
case StyleAppearance::MozMacVibrantTitlebarDark:
|
|
return VibrancyManager::SystemSupportsVibrancy();
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::WidgetIsContainer(StyleAppearance aAppearance) {
|
|
// flesh this out at some point
|
|
switch (aAppearance) {
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::ProgressBar:
|
|
case StyleAppearance::Meter:
|
|
case StyleAppearance::Range:
|
|
case StyleAppearance::MozMacHelpButton:
|
|
case StyleAppearance::MozMacDisclosureButtonOpen:
|
|
case StyleAppearance::MozMacDisclosureButtonClosed:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::Searchfield:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::Button:
|
|
case StyleAppearance::MozMacHelpButton:
|
|
case StyleAppearance::MozMacDisclosureButtonOpen:
|
|
case StyleAppearance::MozMacDisclosureButtonClosed:
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Range:
|
|
case StyleAppearance::Checkbox:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() { return false; }
|
|
|
|
bool nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Dialog:
|
|
case StyleAppearance::Groupbox:
|
|
case StyleAppearance::Tabpanels:
|
|
case StyleAppearance::ButtonArrowUp:
|
|
case StyleAppearance::ButtonArrowDown:
|
|
case StyleAppearance::Checkmenuitem:
|
|
case StyleAppearance::Menupopup:
|
|
case StyleAppearance::Menuarrow:
|
|
case StyleAppearance::Menuitem:
|
|
case StyleAppearance::Menuseparator:
|
|
case StyleAppearance::Tooltip:
|
|
case StyleAppearance::InnerSpinButton:
|
|
case StyleAppearance::Spinner:
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton:
|
|
case StyleAppearance::Separator:
|
|
case StyleAppearance::Toolbox:
|
|
case StyleAppearance::MenulistTextfield:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::Treeview:
|
|
case StyleAppearance::Treeline:
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Listbox:
|
|
case StyleAppearance::Resizer:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::IsWindowSheet(nsIFrame* aFrame) {
|
|
NSWindow* win = NativeWindowForFrame(aFrame);
|
|
id winDelegate = [win delegate];
|
|
nsIWidget* widget = [(WindowDelegate*)winDelegate geckoWidget];
|
|
if (!widget) {
|
|
return false;
|
|
}
|
|
return (widget->WindowType() == eWindowType_sheet);
|
|
}
|
|
|
|
bool nsNativeThemeCocoa::NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
|
|
StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::MozMacSourceList:
|
|
// If we're in a XUL tree, we don't want to clear the background behind the
|
|
// selections below, since that would make our source list to not pick up
|
|
// the right font smoothing background. But since we don't call this method
|
|
// in nsTreeBodyFrame::BuildDisplayList, we never get here.
|
|
case StyleAppearance::MozMacSourceListSelection:
|
|
case StyleAppearance::MozMacActiveSourceListSelection:
|
|
case StyleAppearance::MozMacVibrancyLight:
|
|
case StyleAppearance::MozMacVibrancyDark:
|
|
case StyleAppearance::MozMacVibrantTitlebarLight:
|
|
case StyleAppearance::MozMacVibrantTitlebarDark:
|
|
case StyleAppearance::Tooltip:
|
|
case StyleAppearance::Menupopup:
|
|
case StyleAppearance::Menuitem:
|
|
case StyleAppearance::Checkmenuitem:
|
|
return true;
|
|
case StyleAppearance::Dialog:
|
|
return IsWindowSheet(aFrame);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
nsITheme::ThemeGeometryType nsNativeThemeCocoa::ThemeGeometryTypeForWidget(
|
|
nsIFrame* aFrame, StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::MozWindowTitlebar:
|
|
return eThemeGeometryTypeTitlebar;
|
|
case StyleAppearance::Toolbar:
|
|
return eThemeGeometryTypeToolbar;
|
|
case StyleAppearance::Toolbox:
|
|
return eThemeGeometryTypeToolbox;
|
|
case StyleAppearance::MozWindowButtonBox:
|
|
return eThemeGeometryTypeWindowButtons;
|
|
case StyleAppearance::MozMacFullscreenButton:
|
|
return eThemeGeometryTypeFullscreenButton;
|
|
case StyleAppearance::MozMacVibrancyLight:
|
|
return eThemeGeometryTypeVibrancyLight;
|
|
case StyleAppearance::MozMacVibrancyDark:
|
|
return eThemeGeometryTypeVibrancyDark;
|
|
case StyleAppearance::MozMacVibrantTitlebarLight:
|
|
return eThemeGeometryTypeVibrantTitlebarLight;
|
|
case StyleAppearance::MozMacVibrantTitlebarDark:
|
|
return eThemeGeometryTypeVibrantTitlebarDark;
|
|
case StyleAppearance::Tooltip:
|
|
return eThemeGeometryTypeTooltip;
|
|
case StyleAppearance::Menupopup:
|
|
return eThemeGeometryTypeMenu;
|
|
case StyleAppearance::Menuitem:
|
|
case StyleAppearance::Checkmenuitem: {
|
|
EventStates eventState = GetContentState(aFrame, aAppearance);
|
|
bool isDisabled = IsDisabled(aFrame, eventState);
|
|
bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
|
|
return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
|
|
}
|
|
case StyleAppearance::Dialog:
|
|
return IsWindowSheet(aFrame) ? eThemeGeometryTypeSheet : eThemeGeometryTypeUnknown;
|
|
case StyleAppearance::MozMacSourceList:
|
|
return eThemeGeometryTypeSourceList;
|
|
case StyleAppearance::MozMacSourceListSelection:
|
|
return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
|
|
: eThemeGeometryTypeUnknown;
|
|
case StyleAppearance::MozMacActiveSourceListSelection:
|
|
return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
|
|
: eThemeGeometryTypeUnknown;
|
|
default:
|
|
return eThemeGeometryTypeUnknown;
|
|
}
|
|
}
|
|
|
|
nsITheme::Transparency nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame,
|
|
StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Menupopup:
|
|
case StyleAppearance::Tooltip:
|
|
return eTransparent;
|
|
|
|
case StyleAppearance::Dialog:
|
|
return IsWindowSheet(aFrame) ? eTransparent : eOpaque;
|
|
|
|
case StyleAppearance::ScrollbarSmall:
|
|
case StyleAppearance::Scrollbar:
|
|
case StyleAppearance::Scrollcorner: {
|
|
// We don't use custom scrollbars when using overlay scrollbars.
|
|
if (nsLookAndFeel::UseOverlayScrollbars()) {
|
|
return eTransparent;
|
|
}
|
|
const nsStyleUI* ui = nsLayoutUtils::StyleForScrollbar(aFrame)->StyleUI();
|
|
if (!ui->mScrollbarColor.IsAuto() &&
|
|
ui->mScrollbarColor.AsColors().track.MaybeTransparent()) {
|
|
return eTransparent;
|
|
}
|
|
return eOpaque;
|
|
}
|
|
|
|
case StyleAppearance::Statusbar:
|
|
// Knowing that scrollbars and statusbars are opaque improves
|
|
// performance, because we create layers for them.
|
|
return eOpaque;
|
|
|
|
case StyleAppearance::Toolbar:
|
|
return eOpaque;
|
|
|
|
default:
|
|
return eUnknownTransparency;
|
|
}
|
|
}
|
|
|
|
already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
|
|
static nsCOMPtr<nsITheme> inst;
|
|
|
|
if (!inst) {
|
|
inst = new nsNativeThemeCocoa();
|
|
ClearOnShutdown(&inst);
|
|
}
|
|
|
|
return do_AddRef(inst);
|
|
}
|