QMacStyle - workaround NSButtonCell (disclose button type)

Under the 'Dark' theme as a system one, in an application forcing
'Aqua' (and thus 'Light') appearance, disclose button (drawn as
a triangle) can suddenly become transparent (apparently selecting
a 'Dark' codepath internally). Different ways to fix this (attaching
NSButton to a view, setting appearance on this button manually, etc.)
- all seems to have no effect. We resort to manually drawing this
button on macOS > 10.14 if 'effectiveAppearance' is 'Aqua'.

Change-Id: I6f54c0c4cf8fdd1ba53263ba9535e3055be46d42
Fixes: QTBUG-74515
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Timur Pocheptsov 2019-03-20 15:59:18 +01:00
parent 1a8aa81866
commit a868942b11

View File

@ -1156,6 +1156,66 @@ static QStyleHelper::WidgetSizePolicy qt_aqua_guess_size(const QWidget *widg, QS
}
#endif
static NSColor *qt_convertColorForContext(CGContextRef context, NSColor *color)
{
Q_ASSERT(color);
Q_ASSERT(context);
CGColorSpaceRef targetCGColorSpace = CGBitmapContextGetColorSpace(context);
NSColorSpace *targetNSColorSpace = [[NSColorSpace alloc] initWithCGColorSpace:targetCGColorSpace];
NSColor *adjusted = [color colorUsingColorSpace:targetNSColorSpace];
[targetNSColorSpace release];
return adjusted;
}
static NSColor *qt_colorForContext(CGContextRef context, const CGFloat (&rgba)[4])
{
Q_ASSERT(context);
auto colorSpace = CGBitmapContextGetColorSpace(context);
if (!colorSpace)
return nil;
return qt_convertColorForContext(context, [NSColor colorWithSRGBRed:rgba[0] green:rgba[1] blue:rgba[2] alpha:rgba[3]]);
}
static void qt_drawDisclosureButton(CGContextRef context, NSInteger state, bool selected, CGRect rect)
{
Q_ASSERT(context);
static const CGFloat gray[] = {0.55, 0.55, 0.55, 0.97};
static const CGFloat white[] = {1.0, 1.0, 1.0, 0.9};
NSColor *fillColor = qt_colorForContext(context, selected ? white : gray);
[fillColor setFill];
if (state == NSOffState) {
static NSBezierPath *triangle = [[NSBezierPath alloc] init];
[triangle removeAllPoints];
// In off state, a disclosure button is an equilateral triangle
// ('pointing' to the right) with a bound rect that can be described
// as NSMakeRect(0, 0, 8, 9). Inside the 'rect' it's translated by
// (2, 4).
[triangle moveToPoint:NSMakePoint(rect.origin.x + 2, rect.origin.y + 4)];
[triangle lineToPoint:NSMakePoint(rect.origin.x + 2, rect.origin.y + 4 + 9)];
[triangle lineToPoint:NSMakePoint(rect.origin.x + 2 + 8, rect.origin.y + 4 + 4.5)];
[triangle closePath];
[triangle fill];
} else {
static NSBezierPath *openTriangle = [[NSBezierPath alloc] init];
[openTriangle removeAllPoints];
// In 'on' state, the button is an equilateral triangle (looking down)
// with the bounding rect NSMakeRect(0, 0, 9, 8). Inside the 'rect'
// it's translated by (1, 4).
[openTriangle moveToPoint:NSMakePoint(rect.origin.x + 1, rect.origin.y + 4 + 8)];
[openTriangle lineToPoint:NSMakePoint(rect.origin.x + 1 + 9, rect.origin.y + 4 + 8)];
[openTriangle lineToPoint:NSMakePoint(rect.origin.x + 1 + 4.5, rect.origin.y + 4)];
[openTriangle closePath];
[openTriangle fill];
}
}
void QMacStylePrivate::drawFocusRing(QPainter *p, const QRectF &targetRect, int hMargin, int vMargin, const CocoaControl &cw) const
{
QPainterPath focusRingPath;
@ -3241,8 +3301,15 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai
CGContextScaleCTM(cg, 1, -1);
CGContextTranslateCTM(cg, -rect.origin.x, -rect.origin.y);
[triangleCell drawBezelWithFrame:NSRectFromCGRect(rect) inView:[triangleCell controlView]];
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave && !qt_mac_applicationIsInDarkMode()) {
// When the real system theme is one of the 'Dark' themes, and an application forces the 'Aqua' theme,
// under some conditions (see QTBUG-74515 for more details) NSButtonCell seems to select the 'Dark'
// code path and is becoming transparent, thus 'invisible' on the white background. To workaround this,
// we draw the disclose triangle manually:
qt_drawDisclosureButton(cg, triangleCell.state, (opt->state & State_Selected) && viewHasFocus, rect);
} else {
[triangleCell drawBezelWithFrame:NSRectFromCGRect(rect) inView:[triangleCell controlView]];
}
d->restoreNSGraphicsContext(cg);
break; }