Cocoa: improve the settings of FoucsRing to better adapt to LCL, Merge branch 'cocoa/focusring'

on macOS, the FocusRing takes up extra space, which may cause strange display in some cases. it may block other controls, or be partially cut off.
for example, in the Lazarus IDE - About dialog, the FocusRing of the Tab of TPageControl is partially cut off. by providing a configurable infrastructure, FocusRing can be controlled for different control types.
This commit is contained in:
rich2014 2024-08-16 20:30:36 +08:00
commit fba4c8e459
9 changed files with 119 additions and 26 deletions

View File

@ -23,8 +23,8 @@ unit CocoaButtons;
interface
uses
Types, Classes, SysUtils, Graphics,
MacOSAll, CocoaAll, CocoaConst, CocoaPrivate, CocoaCallback;
Types, Classes, SysUtils, Graphics, Controls,
MacOSAll, CocoaAll, CocoaConst, CocoaWSCommon, CocoaPrivate, CocoaCallback;
const
@ -92,6 +92,8 @@ type
procedure setState(astate: NSInteger); override;
end;
TCocoaButtonNeedFocusRing = objcclass(TCocoaButton)
end;
IStepperCallback = interface(ICommonCallback)
procedure BeforeChange(var Allowed: Boolean);
@ -360,6 +362,7 @@ begin
begin
setTarget(Self);
setAction(objcselector('actionButtonClick:'));
UpdateControlFocusRing( self, TWinControl(lclGetTarget) );
// todo: find a way to release notifications below
// NSNotificationCenter.defaultCenter.addObserver_selector_name_object(Self, objcselector('boundsDidChange:'), NSViewBoundsDidChangeNotification, Result);
// NSNotificationCenter.defaultCenter.addObserver_selector_name_object(Self, objcselector('frameDidChange:'), NSViewFrameDidChangeNotification, Result);

View File

@ -9,8 +9,31 @@ uses
CocoaAll, Cocoa_Extra, CocoaConst;
type
NSColorFunction = Function(): NSColor;
// on macOS, the FocusRing takes up extra space, which may cause strange
// display in some cases. it may block other controls, or be partially cut off.
// for example, in the Lazarus IDE - About dialog, the FocusRing of the
// Tab of TPageControl is partially cut off.
// by providing a configurable infrastructure, FocusRing can be controlled
// for different control types.
{$scopedEnums on}
TCocoaFocusRingStrategy = (
default, // by macOS Default
none, // no FoucsRing
required, // have FocusRing
border // by LCL Control Border
);
// set the FocusRing strategy of the control according to ClassName
// (eg. 'TCocoaTabControl')
// APP can change the default setting of Cocoa WidgetSet
// by calling setCocoaControlFocusRingStrategy() too.
procedure setCocoaControlFocusRingStrategry( frs: TCocoaFocusRingStrategy; AClassName: NSString );
// getCocoaControlFocusRingStrategy() is mainly used internally by Cocoa WidgetSet
function getCocoaControlFocusRingStrategry( AClassName: NSString ): TCocoaFocusRingStrategy;
type
NSColorFunction = Function(): NSColor;
function getCocoaScrollerDefaultKnobColor: NSColor;
var
@ -129,6 +152,46 @@ var
implementation
var
FocusRingStrategySetting: NSMutableDictionary;
procedure setCocoaControlFocusRingStrategry( frs: TCocoaFocusRingStrategy; AClassName: NSString );
var
valueObject: NSNumber;
begin
valueObject:= NSNumber.numberWithInt( Ord(frs) );
FocusRingStrategySetting.setValue_forKey( valueObject , AClassName );
end;
function getCocoaControlFocusRingStrategry( AClassName: NSString ): TCocoaFocusRingStrategy;
var
valueObject: NSNumber;
begin
Result:= TCocoaFocusRingStrategy.default;
valueObject:= NSNumber( FocusRingStrategySetting.valueForKey(AClassName) );
if Assigned(valueObject) then
Result:= TCocoaFocusRingStrategy(valueObject.intValue);
if Result = TCocoaFocusRingStrategy.required then
Writeln( 'required' );
end;
// no need to set TCocoaFocusRingStrategy.default control
// the controls not in FocusRingStrategySetting are TCocoaFocusRingStrategy.default
procedure initDefaultFoucsRingSetting;
begin
FocusRingStrategySetting:= NSMutableDictionary.alloc.initWithCapacity( 16 );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.none, NSSTR('TCocoaTabControl') );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.none, NSSTR('TCocoaButton') );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.none, NSSTR('TCocoaTextField') );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.none, NSSTR('TCocoaComboBox') );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.none, NSSTR('TCocoaReadOnlyComboBox') );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.none, NSSTR('TCocoaTableListView') );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.none, NSSTR('TCocoaCollectionView') );
setCocoaControlFocusRingStrategry( TCocoaFocusRingStrategy.border, NSSTR('TCocoaTextView') );
end;
function getCocoaScrollerDefaultKnobColor: NSColor;
begin
Result:= NSColor.controlTextColor;
@ -137,6 +200,6 @@ end;
initialization
CocoaDefaultCheckMenuImageName:= NSSTR('NSMenuCheckmark');
CocoaDefaultRadioMenuImageName:= NSSTR('NSDatePickerCalendarHome');
initDefaultFoucsRingSetting;
end.

View File

@ -194,6 +194,8 @@ begin
_scrollView.callback:= self.callback;
self.addSubview_positioned_relativeTo( _scrollView, NSWindowBelow, nil );
ScrollViewSetBorderStyle( _scrollView, callback.getBorderStyle );
_scrollView.setFocusRingType( NSFocusRingTypeExterior );
UpdateControlFocusRing( _backendControl, TWinControl(self.lclGetTarget) );
backendControlAccess:= TCocoaListViewBackendControlProtocol(_backendControl);
backendControlAccess.backend_setCallback( self.callback );
@ -529,6 +531,7 @@ begin
field:= TCocoaTextField( aView );
field.setBezeled( False );
field.setFocusRingType( NSFocusRingTypeExterior );
field.fixedBorderStyle:= True;
NSView(self.Owner).addSubview( field ); // add to TCococListView
Result:= True;

View File

@ -428,8 +428,6 @@ begin
self.setAllowsColumnReordering(False);
self.setAllowsColumnSelection(False);
UpdateFocusRing( self, self.callback.getBorderStyle );
sz := self.intercellSpacing;
// Windows compatibility. on Windows there's no extra space between columns
sz.width := 0;

View File

@ -245,7 +245,7 @@ begin
scroll.setAutohidesScrollers(true);
ScrollViewSetBorderStyle(scroll, TCustomCheckListBox(AWinControl).BorderStyle);
UpdateFocusRing(list, TCustomCheckListBox(AWinControl).BorderStyle);
UpdateControlFocusRing(list, AWinControl);
Result := TLCLHandle(scroll);
end;

View File

@ -478,6 +478,7 @@ begin
TLCLCommonCallback(tv.callback.GetCallbackObject).BlockCocoaUpDown := true;
lControl.callback := tv.callback;
lControl.setView(tv);
UpdateControlFocusRing( tabview, AWinControl );
Result := TLCLHandle(tv);
end;

View File

@ -172,7 +172,7 @@ type
const ABorderStyle: TBorderStyle); override;
end;
procedure UpdateFocusRing(v: NSView; astyle: TBorderStyle);
procedure UpdateControlFocusRing( cocoaControl: NSView; lclControl: TWinControl );
procedure ScrollViewSetScrollStyles(AScroll: TCocoaScrollView; AStyles: TScrollStyle);
procedure ScrollViewSetBorderStyle(sv: NSScrollView; astyle: TBorderStyle);
@ -221,6 +221,9 @@ implementation
uses
Math;
type
TWinControlAccess = class(TWinControl);
var
LastMouse: TLastMouseInfo;
@ -253,15 +256,28 @@ begin
if AMouseButtons and (1 shl 4) <> 0 then Include(Result, ssExtra2);
end;
procedure UpdateFocusRing(v: NSView; astyle: TBorderStyle);
procedure UpdateControlFocusRing(cocoaControl: NSView; lclControl: TWinControl);
const
NSFocusRing : array [TBorderStyle] of NSBorderType = (
NSFocusRingTypeNone, // bsNone
NSFocusRingTypeDefault // bsSingle
);
var
frs: CocoaConfig.TCocoaFocusRingStrategy;
borderStyle: TBorderStyle;
begin
if Assigned(v) and CocoaHideFocusNoBorder then
v.setFocusRingType( NSFocusRing[astyle] );
frs:= CocoaConfig.getCocoaControlFocusRingStrategry( cocoaControl.className );
case frs of
TCocoaFocusRingStrategy.none:
cocoaControl.setFocusRingType( NSFocusRingTypeNone );
TCocoaFocusRingStrategy.required:
cocoaControl.setFocusRingType( NSFocusRingTypeExterior );
TCocoaFocusRingStrategy.border: begin
borderStyle:= TWinControlAccess(lclControl).BorderStyle;
cocoaControl.setFocusRingType( NSFocusRing[borderStyle] );
end;
// TCocoaFocusRingStrategy.default: no need to set FocusRing
end;
end;
procedure ScrollViewSetScrollStyles(AScroll: TCocoaScrollView; AStyles: TScrollStyle);

View File

@ -162,7 +162,7 @@ begin
if not Assigned(AWinControl) or not AWinControl.HandleAllocated then Exit;
cocoaListView:= TCocoaListView(AWinControl.Handle);
ScrollViewSetBorderStyle(cocoaListView.scrollView, ABorderStyle);
UpdateFocusRing(cocoaListView.documentView, ABorderStyle);
UpdateControlFocusRing(cocoaListView.documentView, AWinControl);
end;
class procedure TCocoaWSCustomListView.ColumnDelete(const ALV: TCustomListView;

View File

@ -399,9 +399,17 @@ implementation
function AllocButton(const ATarget: TWinControl; const ACallBackClass: TLCLButtonCallBackClass; const AParams: TCreateParams; btnBezel: NSBezelStyle; btnType: NSButtonType): TCocoaButton;
begin
Result := TCocoaButton.alloc.lclInitWithCreateParams(AParams);
if Assigned(Result) then
begin
case btnType of
NSMomentaryLightButton,
NSMomentaryChangeButton,
NSMomentaryPushInButton:
Result:= TCocoaButton.alloc;
else
Result:= TCocoaButtonNeedFocusRing.alloc;
end;
if Assigned(Result) then begin
Result:= Result.lclInitWithCreateParams(AParams);
TCocoaButton(Result).callback := ACallBackClass.Create(Result, ATarget);
Result.setTitle(ControlTitleToNSStr(AParams.Caption));
@ -848,7 +856,7 @@ class function TCocoaWSButton.CreateHandle(const AWinControl: TWinControl;
var
btn: TCocoaButton;
begin
btn := AllocButton(AWinControl, TLCLButtonCallback, AParams, NSRoundedBezelStyle, NSMomentaryPushInButton);
btn := AllocButton(AWinControl, TLCLButtonCallback, AParams, NSRoundedBezelStyle, NSMomentaryLightButton);
btn.smallHeight := PUSHBTN_SMALL_HEIGHT;
btn.miniHeight := PUSHBTN_MINI_HEIGHT;
btn.adjustFontToControlSize:=true;
@ -1082,12 +1090,12 @@ end;
class function TCocoaWSCustomEdit.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLHandle;
var
field : NSTextField;
field : TCocoaTextField;
cell : NSTextFieldCell;
begin
if TCustomEdit(AWinControl).PasswordChar=#0
then field:=NSTextField(AllocTextField(AWinControl, AParams))
else field:=NSTextField(AllocSecureTextField(AWinControl, AParams));
then field:=TCocoaTextField(AllocTextField(AWinControl, AParams))
else field:=TCocoaTextField(AllocSecureTextField(AWinControl, AParams));
if (field.respondsToSelector(ObjCSelector('cell'))) and Assigned(field.cell) then
begin
cell := NSTextFieldCell(field.cell);
@ -1097,7 +1105,8 @@ begin
if NOT TCocoaTextField(field).fixedBorderStyle then
TextFieldSetBorderStyle(field, TCustomEdit(AWinControl).BorderStyle);
TextFieldSetAllignment(field, TCustomEdit(AWinControl).Alignment);
UpdateFocusRing(field, TCustomEdit(AWinControl).BorderStyle);
if NOT field.fixedBorderStyle then
UpdateControlFocusRing( field, AWinControl );
Result:=TLCLHandle(field);
end;
@ -1142,7 +1151,7 @@ begin
field.setBordered( ABorderStyle <> bsNone );
field.setBezeled( ABorderStyle <> bsNone );
{$endif}
UpdateFocusRing(field, ABorderStyle);
UpdateControlFocusRing( field, AWinControl );
end;
class function TCocoaWSCustomEdit.GetSelStart(const ACustomEdit: TCustomEdit): integer;
@ -1637,7 +1646,7 @@ begin
scr.setDrawsBackground(false);
ScrollViewSetBorderStyle(scr, TCustomMemo(AWinControl).BorderStyle);
UpdateFocusRing(txt, TCustomMemo(AWinControl).BorderStyle);
scr.setFocusRingType( NSFocusRingTypeExterior );
nr:=scr.documentVisibleRect;
txt.setFrame(nr);
@ -1660,7 +1669,7 @@ begin
// This makes NSTextView to be responsive to theme color change (Mojave 10.14)
txt.setTextColor(NSColor.textColor);
txt.setBackgroundColor(NSColor.textBackgroundColor);
scr.setFocusRingType(NSFocusRingTypeExterior);
UpdateControlFocusRing(txt, AWinControl);
lcl := TLCLCommonCallback.Create(txt, AWinControl);
lcl.ForceReturnKeyDown := true;
@ -1730,7 +1739,7 @@ begin
if not Assigned(sv) then Exit;
ScrollViewSetBorderStyle(sv, ABorderStyle);
UpdateFocusRing(NSView(sv.documentView), ABorderStyle);
UpdateControlFocusRing(sv.documentView, AWinControl);
end;
class function TCocoaWSCustomMemo.GetCaretPos(const ACustomEdit: TCustomEdit): TPoint;
@ -2512,7 +2521,7 @@ begin
scroll.setHasHorizontalScroller(true);
scroll.setAutohidesScrollers(true);
ScrollViewSetBorderStyle(scroll, lclListBox.BorderStyle);
UpdateFocusRing(list, lclListBox.BorderStyle);
UpdateControlFocusRing(list, lclListBox);
Result := TLCLHandle(scroll);
end;
@ -2634,7 +2643,7 @@ begin
if not Assigned(list) then Exit;
ScrollViewSetBorderStyle(list.enclosingScrollView, ABorderStyle);
UpdateFocusRing(list, ABorderStyle);
UpdateControlFocusRing(list, AWinControl);
end;
class procedure TCocoaWSCustomListBox.SetItemIndex(const ACustomListBox: TCustomListBox; const AIndex: integer);