View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0038280 | Lazarus | Widgetset | public | 2020-12-30 02:16 | 2021-01-31 07:08 |
Reporter | Zoë Peterson | Assigned To | Dmitry Boyarintsev | ||
Priority | normal | Severity | minor | Reproducibility | always |
Status | resolved | Resolution | fixed | ||
Product Version | 2.0.10 | ||||
Summary | 0038280: LCLCOCOA: Draw text using NSColor constants to fix disabled TLabel | ||||
Description | This patch makes two changes: 1) Turn on ThemeServices.ThemesEnabled for Cocoa 2) Text painting now uses original alpha transparent NSColors for system colors like clGrayText rather than converting to RGB with a flattened alpha The primary purpose of the changes is to make disabled TLabel draw using NSColor.disabledControlTextColor so it looks the same as other disabled controls like TCheckbox when it's drawn in dark mode. See the attached screenshot. The patch itself is large, but most of it is moving things around. SysColorToNSColor is moved from cocoawinapi.inc to cocoautils.pas, which necessitated moving the various IsDark* routines there from CocoaThemes as well. The core of the patch is: 1) Enabling TCocoaThemeServices.InitThemes, UseThemes, and ThemedControlsEnabled, which makes TThemeServices.ThemesEnabled return true, which makes TCustomLabel.Paint use clGrayText for painting instead of the etched appearance. That change alone makes the drawing go from the first to second variant in the attached screenshot. 2) TCocoaContext.SetTextColor maintains the incoming TColor rather than immediately converting it to an RGB value, and then TCocoaTextLayout.UpdateColor tries to convert to a known NSColor before doing the ColorToRGB calls. That makes the drawing go from the second variant to the third one, which matches the disabled TCheckbox. 3) TCocoaWidgetSet.GetTextColor uses ColorToRGB to convert the stored one to RGB before returning it so places that expect it to be a COLORREF get it correctly. It's done there instead of TCocoaContext.GetTextColor so TCocoaContext.SaveDCData/RestoreDCData aren't lossy. | ||||
Steps To Reproduce | 1) Create a new LCLCOCOA application 2) Add a TLabel and a TCheckbox to the form and set both of their Enabled properties too False. 3) Run the application with the system appearance set to "Dark" With the above, the label and checkbox text will be different colors. With the patch they appear identical. Screenshot taken on macOS 10.14.6 Mojave. | ||||
Tags | No tags attached. | ||||
Fixed in Revision | 64452 | ||||
LazTarget | - | ||||
Widgetset | Cocoa | ||||
Attached Files |
|
|
0001-LCLCOCOA-Draw-text-using-NSColor-constants-to-fix-di.patch (17,501 bytes)
diff --git a/lcl/interfaces/cocoa/cocoagdiobjects.pas b/lcl/interfaces/cocoa/cocoagdiobjects.pas index 25c668b10b..fd09228bf8 100644 --- a/lcl/interfaces/cocoa/cocoagdiobjects.pas +++ b/lcl/interfaces/cocoa/cocoagdiobjects.pas @@ -1312,8 +1312,13 @@ begin end; procedure TCocoaTextLayout.UpdateColor; +var + lForegroundColor: NSColor; begin - FTextStorage.addAttribute_value_range(NSForegroundColorAttributeName, ColorToNSColor(ForegroundColor), GetTextRange); + lForegroundColor := SysColorToNSColor(SysColorToSysColorIndex(ForegroundColor)); + if lForegroundColor = nil then + lForegroundColor := ColorToNSColor(ColorToRGB(ForegroundColor)); + FTextStorage.addAttribute_value_range(NSForegroundColorAttributeName, lForegroundColor, GetTextRange); FTextStorage.addAttribute_value_range(NSBackgroundColorAttributeName, ColorToNSColor(BackgroundColor), GetTextRange); end; @@ -1686,7 +1691,7 @@ end; procedure TCocoaContext.SetTextColor(AValue: TColor); begin - FText.ForegroundColor := TColor(ColorToRGB(AValue)); + FText.ForegroundColor := AValue; end; procedure TCocoaContext.UpdateContextOfs(const AWindowOfs, AViewOfs: TPoint); diff --git a/lcl/interfaces/cocoa/cocoathemes.pas b/lcl/interfaces/cocoa/cocoathemes.pas index c9d84fb37e..d0fbbefbe6 100644 --- a/lcl/interfaces/cocoa/cocoathemes.pas +++ b/lcl/interfaces/cocoa/cocoathemes.pas @@ -39,10 +39,10 @@ type procedure CellDrawFrame(cell: NSCell; const dst: NSRect); procedure CellDrawEnd(dst: TCocoaContext; cur: NSGraphicsContext); -(* function InitThemes: Boolean; override; function UseThemes: Boolean; override; function ThemedControlsEnabled: Boolean; override; +(* procedure InternalDrawParentBackground({%H-}Window: HWND; {%H-}Target: HDC; {%H-}Bounds: PRect); override; *) function GetDrawState(Details: TThemedElementDetails): ThemeDrawState; @@ -73,25 +73,6 @@ type function GetOption(AOption: TThemeOption): Integer; override; end; -// "dark" is not a good reference, as Apple might add more and more themes -function IsDarkPossible: Boolean; inline; - -// returns if the application appearance is set to dark -function IsAppDark: Boolean; - -// returns if the window appearance is set to dark -function IsWinDark(win: NSWindow): Boolean; - -// Returns the appearance object that is active on the current thread. -// returns true, if currently drawn (Painted) UI control is in Dark theme. -function IsPaintDark: Boolean; - -// returns true, if Appear is assigned and bears name of Dark theme -function IsAppearDark(Appear: NSAppearance): Boolean; inline; - -// weak-referenced NSAppearnceClass. Returns nil on any OS prior to 10.13 -function NSAppearanceClass: pobjc_class; - implementation type @@ -105,80 +86,6 @@ type const IntBool : array [Boolean] of NSInteger = (0,1); -var - _NSAppearanceClass : pobjc_class = nil; - _NSAppearanceClassRead: Boolean = false; - -const - DarkName = 'NSAppearanceNameDarkAqua'; // used in 10.14 - DarkNameVibrant = 'NSAppearanceNameVibrantDark'; // used in 10.13 - -function NSAppearanceClass: pobjc_class; -begin - if not _NSAppearanceClassRead then - begin - _NSAppearanceClass := objc_getClass('NSAppearance'); - _NSAppearanceClassRead := true; - end; - Result := _NSAppearanceClass; -end; - -function IsAppearDark(Appear: NSAppearance): Boolean; inline; -begin - Result := Assigned(Appear) - and ( - Appear.name.isEqualToString(NSSTR(DarkName)) - or - Appear.name.isEqualToString(NSSTR(DarkNameVibrant)) - ) -end; - -function IsDarkPossible: Boolean; inline; -begin - Result := NSAppKitVersionNumber > NSAppKitVersionNumber10_12; -end; - -function IsAppDark: Boolean; -var - Appear: NSAppearance; -begin - if not isDarkPossible then - begin - Result := false; - Exit; - end; - if (not NSApplication(NSApp).respondsToSelector(ObjCSelector('effectiveAppearance'))) then begin - Result := false; - Exit; - end; - - Result := IsAppearDark(NSApplication(NSApp).effectiveAppearance); -end; - -function IsWinDark(win: NSWindow): Boolean; -begin - if not Assigned(win) or not isDarkPossible then - begin - Result := false; - Exit; - end; - if (not win.respondsToSelector(ObjCSelector('effectiveAppearance'))) then begin - Result := false; - Exit; - end; - - Result := IsAppearDark(win.effectiveAppearance); -end; - -function IsPaintDark: Boolean; -var - cls : pobjc_class; -begin - cls := NSAppearanceClass; - if not Assigned(cls) then Exit; - Result := IsAppearDark(objc_msgSend(cls, ObjCSelector('currentAppearance'))); -end; - { TCocoaThemeServices } {------------------------------------------------------------------------------ @@ -713,34 +620,34 @@ begin Result := CGRectToRect(BtnRect); OffsetRect(Result, Offset.X, Offset.Y); end; - +*) {------------------------------------------------------------------------------ - Method: TCarbonThemeServices.InitThemes + Method: TCocoaThemeServices.InitThemes Returns: If the themes are initialized ------------------------------------------------------------------------------} -function TCarbonThemeServices.InitThemes: Boolean; +function TCocoaThemeServices.InitThemes: Boolean; begin Result := True; end; {------------------------------------------------------------------------------ - Method: TCarbonThemeServices.InitThemes + Method: TCocoaThemeServices.InitThemes Returns: If the themes have to be used ------------------------------------------------------------------------------} -function TCarbonThemeServices.UseThemes: Boolean; +function TCocoaThemeServices.UseThemes: Boolean; begin Result := True; end; {------------------------------------------------------------------------------ - Method: TCarbonThemeServices.ThemedControlsEnabled + Method: TCocoaThemeServices.ThemedControlsEnabled Returns: If the themed controls are enabled ------------------------------------------------------------------------------} -function TCarbonThemeServices.ThemedControlsEnabled: Boolean; +function TCocoaThemeServices.ThemedControlsEnabled: Boolean; begin Result := True; end; - +(* {------------------------------------------------------------------------------ Method: TCarbonThemeServices.ContentRect Params: DC - Carbon device context diff --git a/lcl/interfaces/cocoa/cocoautils.pas b/lcl/interfaces/cocoa/cocoautils.pas index 6205783aae..a2bfb41020 100644 --- a/lcl/interfaces/cocoa/cocoautils.pas +++ b/lcl/interfaces/cocoa/cocoautils.pas @@ -65,6 +65,27 @@ function NSColorToRGB(const Color: NSColor): TColorRef; inline; // extract ColorRef from any NSColor function NSColorToColorRef(const Color: NSColor): TColorRef; function ColorToNSColor(const Color: TColorRef): NSColor; inline; +// convert to known NSColor or nil +function SysColorToNSColor(nIndex: Integer): NSColor; + +// "dark" is not a good reference, as Apple might add more and more themes +function IsDarkPossible: Boolean; inline; + +// returns if the application appearance is set to dark +function IsAppDark: Boolean; + +// returns if the window appearance is set to dark +function IsWinDark(win: NSWindow): Boolean; + +// Returns the appearance object that is active on the current thread. +// returns true, if currently drawn (Painted) UI control is in Dark theme. +function IsPaintDark: Boolean; + +// returns true, if Appear is assigned and bears name of Dark theme +function IsAppearDark(Appear: NSAppearance): Boolean; inline; + +// weak-referenced NSAppearnceClass. Returns nil on any OS prior to 10.13 +function NSAppearanceClass: pobjc_class; const DEFAULT_CFSTRING_ENCODING = kCFStringEncodingUTF8; @@ -445,6 +466,173 @@ begin ((Color shr 16) and $FF) / $FF, 1); end; +function SysColorToNSColor(nIndex: Integer): NSColor; +const + ToolTipBack = $C9FCF9; + ToolTipBack1010 = $EDEDED; + ToolTipBack1014 = $f0f0f0; + ToolTipBack1014Dark = $343434; +begin + case NIndex of + COLOR_GRADIENTACTIVECAPTION, COLOR_ACTIVECAPTION, + COLOR_WINDOWFRAME, COLOR_ACTIVEBORDER: + Result := NSColor.windowFrameColor; + COLOR_GRADIENTINACTIVECAPTION, COLOR_INACTIVECAPTION, COLOR_INACTIVEBORDER: + Result := NSColor.windowBackgroundColor; + COLOR_CAPTIONTEXT, + COLOR_INACTIVECAPTIONTEXT: + Result := NSColor.windowFrameTextColor; + COLOR_WINDOW: + Result := NSColor.textBackgroundColor; + COLOR_BACKGROUND, + COLOR_FORM: + Result := NSColor.windowBackgroundColor; + COLOR_MENU: + Result := NSColor.controlBackgroundColor; + COLOR_MENUTEXT: + Result := NSColor.controlTextColor; + COLOR_MENUBAR: + Result := NSColor.selectedTextBackgroundColor; + COLOR_MENUHILIGHT: + Result := NSColor.selectedMenuItemColor; + COLOR_WINDOWTEXT: + Result := NSColor.controlTextColor; + COLOR_APPWORKSPACE: + Result := NSColor.windowBackgroundColor; + COLOR_HIGHLIGHT: + Result := NSColor.selectedControlColor; + COLOR_HOTLIGHT: + Result := NSColor.alternateSelectedControlColor; + COLOR_HIGHLIGHTTEXT: + Result := NSColor.selectedControlTextColor; + COLOR_SCROLLBAR: + Result := NSColor.scrollBarColor; + COLOR_BTNFACE: + Result := NSColor.controlBackgroundColor; + COLOR_BTNSHADOW: // COLOR_3DSHADOW + if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then + Result := NSColor.controlColor.shadowWithLevel(0.5) + else + Result := NSColor.controlShadowColor; + COLOR_BTNHIGHLIGHT: + if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then + Result := NSColor.controlColor.shadowWithLevel(0.0) + else + Result := NSColor.controlLightHighlightColor;//controlHighlightColor has no contrast with COLOR_BTNFACE which affects TBevel. In Win32 this has value white + COLOR_BTNTEXT: + Result := NSColor.controlTextColor; + COLOR_GRAYTEXT: + Result := NSColor.disabledControlTextColor; + COLOR_3DDKSHADOW: + if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then + Result := NSColor.controlColor.shadowWithLevel(0.75) + else + Result := NSColor.controlDarkShadowColor; + COLOR_3DLIGHT: + if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then + Result := NSColor.controlColor.shadowWithLevel(0.25) + else + Result := NSColor.controlHighlightColor;// makes a more consistent result (a very light gray) than controlLightHighlightColor (which is white) + + // macOS doesn't provide any API to get the hint window colors. + // default = macosx10.4 yellow color. (See InitInternals below) + // it's likely the tooltip color will change in future. + // Thus the variable is left public, so a user of LCL + // would be able to initialize it properly on start + COLOR_INFOTEXT: + Result := NSColor.controlTextColor; + COLOR_INFOBK: + begin + if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then + begin + if IsPaintDark then + Result := ColorToNSColor(ToolTipBack1014Dark) + else + Result := ColorToNSColor(ToolTipBack1014); + end else if NSAppKitVersionNumber >= NSAppKitVersionNumber10_10 then + Result := ColorToNSColor(ToolTipBack1010) + else + Result := ColorToNSColor(ToolTipBack); + end; + else + Result := nil; + end; +end; + +var + _NSAppearanceClass : pobjc_class = nil; + _NSAppearanceClassRead: Boolean = false; + +const + DarkName = 'NSAppearanceNameDarkAqua'; // used in 10.14 + DarkNameVibrant = 'NSAppearanceNameVibrantDark'; // used in 10.13 + +function NSAppearanceClass: pobjc_class; +begin + if not _NSAppearanceClassRead then + begin + _NSAppearanceClass := objc_getClass('NSAppearance'); + _NSAppearanceClassRead := true; + end; + Result := _NSAppearanceClass; +end; + +function IsAppearDark(Appear: NSAppearance): Boolean; inline; +begin + Result := Assigned(Appear) + and ( + Appear.name.isEqualToString(NSSTR(DarkName)) + or + Appear.name.isEqualToString(NSSTR(DarkNameVibrant)) + ) +end; + +function IsDarkPossible: Boolean; inline; +begin + Result := NSAppKitVersionNumber > NSAppKitVersionNumber10_12; +end; + +function IsAppDark: Boolean; +var + Appear: NSAppearance; +begin + if not isDarkPossible then + begin + Result := false; + Exit; + end; + if (not NSApplication(NSApp).respondsToSelector(ObjCSelector('effectiveAppearance'))) then begin + Result := false; + Exit; + end; + + Result := IsAppearDark(NSApplication(NSApp).effectiveAppearance); +end; + +function IsWinDark(win: NSWindow): Boolean; +begin + if not Assigned(win) or not isDarkPossible then + begin + Result := false; + Exit; + end; + if (not win.respondsToSelector(ObjCSelector('effectiveAppearance'))) then begin + Result := false; + Exit; + end; + + Result := IsAppearDark(win.effectiveAppearance); +end; + +function IsPaintDark: Boolean; +var + cls : pobjc_class; +begin + cls := NSAppearanceClass; + if not Assigned(cls) then Exit; + Result := IsAppearDark(objc_msgSend(cls, ObjCSelector('currentAppearance'))); +end; + function CFStringToString(AString: CFStringRef): String; begin result:=CFStringToStr(AString); diff --git a/lcl/interfaces/cocoa/cocoawinapi.inc b/lcl/interfaces/cocoa/cocoawinapi.inc index d486188887..1c43cb17dd 100644 --- a/lcl/interfaces/cocoa/cocoawinapi.inc +++ b/lcl/interfaces/cocoa/cocoawinapi.inc @@ -1701,99 +1701,6 @@ begin end; end; -function SysColorToNSColor(nIndex: Integer): NSColor; -const - ToolTipBack = $C9FCF9; - ToolTipBack1010 = $EDEDED; - ToolTipBack1014 = $f0f0f0; - ToolTipBack1014Dark = $343434; -begin - case NIndex of - COLOR_GRADIENTACTIVECAPTION, COLOR_ACTIVECAPTION, - COLOR_WINDOWFRAME, COLOR_ACTIVEBORDER: - Result := NSColor.windowFrameColor; - COLOR_GRADIENTINACTIVECAPTION, COLOR_INACTIVECAPTION, COLOR_INACTIVEBORDER: - Result := NSColor.windowBackgroundColor; - COLOR_CAPTIONTEXT, - COLOR_INACTIVECAPTIONTEXT: - Result := NSColor.windowFrameTextColor; - COLOR_WINDOW: - Result := NSColor.textBackgroundColor; - COLOR_BACKGROUND, - COLOR_FORM: - Result := NSColor.windowBackgroundColor; - COLOR_MENU: - Result := NSColor.controlBackgroundColor; - COLOR_MENUTEXT: - Result := NSColor.controlTextColor; - COLOR_MENUBAR: - Result := NSColor.selectedTextBackgroundColor; - COLOR_MENUHILIGHT: - Result := NSColor.selectedMenuItemColor; - COLOR_WINDOWTEXT: - Result := NSColor.controlTextColor; - COLOR_APPWORKSPACE: - Result := NSColor.windowBackgroundColor; - COLOR_HIGHLIGHT: - Result := NSColor.selectedControlColor; - COLOR_HOTLIGHT: - Result := NSColor.alternateSelectedControlColor; - COLOR_HIGHLIGHTTEXT: - Result := NSColor.selectedControlTextColor; - COLOR_SCROLLBAR: - Result := NSColor.scrollBarColor; - COLOR_BTNFACE: - Result := NSColor.controlBackgroundColor; - COLOR_BTNSHADOW: // COLOR_3DSHADOW - if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then - Result := NSColor.controlColor.shadowWithLevel(0.5) - else - Result := NSColor.controlShadowColor; - COLOR_BTNHIGHLIGHT: - if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then - Result := NSColor.controlColor.shadowWithLevel(0.0) - else - Result := NSColor.controlLightHighlightColor;//controlHighlightColor has no contrast with COLOR_BTNFACE which affects TBevel. In Win32 this has value white - COLOR_BTNTEXT: - Result := NSColor.controlTextColor; - COLOR_GRAYTEXT: - Result := NSColor.disabledControlTextColor; - COLOR_3DDKSHADOW: - if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then - Result := NSColor.controlColor.shadowWithLevel(0.75) - else - Result := NSColor.controlDarkShadowColor; - COLOR_3DLIGHT: - if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then - Result := NSColor.controlColor.shadowWithLevel(0.25) - else - Result := NSColor.controlHighlightColor;// makes a more consistent result (a very light gray) than controlLightHighlightColor (which is white) - - // macOS doesn't provide any API to get the hint window colors. - // default = macosx10.4 yellow color. (See InitInternals below) - // it's likely the tooltip color will change in future. - // Thus the variable is left public, so a user of LCL - // would be able to initialize it properly on start - COLOR_INFOTEXT: - Result := NSColor.controlTextColor; - COLOR_INFOBK: - begin - if NSAppKitVersionNumber >= NSAppKitVersionNumber10_14 then - begin - if IsPaintDark then - Result := ColorToNSColor(ToolTipBack1014Dark) - else - Result := ColorToNSColor(ToolTipBack1014); - end else if NSAppKitVersionNumber >= NSAppKitVersionNumber10_10 then - Result := ColorToNSColor(ToolTipBack1010) - else - Result := ColorToNSColor(ToolTipBack); - end; - else - Result := nil; - end; -end; - function TCocoaWidgetSet.GetSysColor(nIndex: Integer): DWORD; var Color: NSColor; @@ -2759,7 +2666,7 @@ var begin ctx := CheckDC(DC); if Assigned(ctx) then - Result := ctx.TextColor + Result := ColorToRGB(ctx.TextColor) else Result := CLR_INVALID; end; -- 2.21.1 (Apple Git-122.3) |
|
My bug report and the screenshots were focused on dark mode because that's what I'm currently working on, but this is an issue in light mode too. Disabled labels should be a dull gray there, but currently have a 3D etched appearance. The patch fixes that too. |
|
Thank you, Zoe. |
|
thanks for the patch, applied please test and close if ok |
Date Modified | Username | Field | Change |
---|---|---|---|
2020-12-30 02:16 | Zoë Peterson | New Issue | |
2020-12-30 02:16 | Zoë Peterson | File Added: 0001-LCLCOCOA-Draw-text-using-NSColor-constants-to-fix-di.patch | |
2020-12-30 02:16 | Zoë Peterson | File Added: DisabledLabel.png | |
2020-12-30 02:30 | Zoë Peterson | Note Added: 0127926 | |
2020-12-30 02:30 | Zoë Peterson | File Added: Screen Shot 2020-12-29 at 7.29.06 PM.png | |
2021-01-02 15:39 | CudaText man_ | Note Added: 0128023 | |
2021-01-04 19:39 | Dmitry Boyarintsev | Assigned To | => Dmitry Boyarintsev |
2021-01-04 19:39 | Dmitry Boyarintsev | Status | new => assigned |
2021-01-31 07:08 | Dmitry Boyarintsev | Status | assigned => resolved |
2021-01-31 07:08 | Dmitry Boyarintsev | Resolution | open => fixed |
2021-01-31 07:08 | Dmitry Boyarintsev | Fixed in Revision | => 64452 |
2021-01-31 07:08 | Dmitry Boyarintsev | LazTarget | => - |
2021-01-31 07:08 | Dmitry Boyarintsev | Widgetset | Cocoa => Cocoa |
2021-01-31 07:08 | Dmitry Boyarintsev | Note Added: 0128699 |