From 5d95dc46163a0af439ec07e240a2bea51630132c Mon Sep 17 00:00:00 2001 From: rich2014 Date: Mon, 2 Sep 2024 21:52:21 +0800 Subject: [PATCH] Cocoa/Toolbar: implement basic NSToolBar infrastructure (macOS Modern Style) 1. it's not the LCL ToolBar 2. it combines TitleBar and ToolBar 3. a modern macOS style that effective use of TitleBar area --- lcl/interfaces/cocoa/cocoa_extra.pas | 11 ++ lcl/interfaces/cocoa/cocoaconfig.pas | 28 ++++ lcl/interfaces/cocoa/cocoatoolbar.pas | 207 ++++++++++++++++++++++++++ lcl/interfaces/cocoa/cocoautils.pas | 18 +++ lcl/interfaces/cocoa/cocoawsforms.pas | 8 +- lcl/interfaces/lcl.lpk | 7 +- 6 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 lcl/interfaces/cocoa/cocoatoolbar.pas diff --git a/lcl/interfaces/cocoa/cocoa_extra.pas b/lcl/interfaces/cocoa/cocoa_extra.pas index e9094628eb..84133a24f1 100644 --- a/lcl/interfaces/cocoa/cocoa_extra.pas +++ b/lcl/interfaces/cocoa/cocoa_extra.pas @@ -425,6 +425,12 @@ type function CGContext: CGContextRef; message 'CGContext'; end; + NSImageFix = objccategory external (NSImage) + class function imageWithSystemSymbolName_accessibilityDescription( + aName: NSString; aAccessibilityDescription: NSString ): id; + message 'imageWithSystemSymbolName:accessibilityDescription:'; { available in 11.0 } + end; + NSEventFix = objccategory external (NSEvent) class function modifierFlags_: NSUInteger; message 'modifierFlags'; // available in 10.7+ @@ -464,6 +470,11 @@ type function effectiveAppearance: NSAppearance; message 'effectiveAppearance'; // 10.14 (10.13) end; + NSToolBarItemFix = objccategory external (NSToolBarItem) + procedure setBordered( newValue: Boolean ); message 'setBordered:'; { available in 10.15 } + procedure setNavigational( newValue: Boolean ); message 'setNavigational:'; { available in 11.0 } + end; + NSTableColumnFix = objccategory external (NSTableColumn) procedure setTitle(atitle: NSString); message 'setTitle:'; function title: NSString; message 'title'; diff --git a/lcl/interfaces/cocoa/cocoaconfig.pas b/lcl/interfaces/cocoa/cocoaconfig.pas index a0dace85a2..ac1afb2b21 100644 --- a/lcl/interfaces/cocoa/cocoaconfig.pas +++ b/lcl/interfaces/cocoa/cocoaconfig.pas @@ -7,10 +7,38 @@ unit CocoaConfig; interface uses + SysUtils, Menus, CocoaAll, Cocoa_Extra, CocoaConst; type + TCocoaConfigToolBarItem = record + identifier: String; + iconName: String; + title: String; + tips: String; + onClick: Pointer; + end; + + TCocoaConfigToolBarItems = Array of TCocoaConfigToolBarItem; + + TCocoaConfigToolBar = record + identifier: String; + items: TCocoaConfigToolBarItems; + defaultItemsIdentifiers: TStringArray; + allowedItemsIdentifiers: TStringArray; + itemCreator: Pointer; + end; + + TCocoaConfigForm = record + toolBar: TCocoaConfigToolBar; + end; + +var + CocoaConfigForm: TCocoaConfigForm; + +type + TCocoaConfigMenuItem = record defaultCheckImageName: NSString; defaultRadioImageName: NSString; diff --git a/lcl/interfaces/cocoa/cocoatoolbar.pas b/lcl/interfaces/cocoa/cocoatoolbar.pas new file mode 100644 index 0000000000..6bfa421f63 --- /dev/null +++ b/lcl/interfaces/cocoa/cocoatoolbar.pas @@ -0,0 +1,207 @@ +unit CocoaToolBar; + +{$mode objfpc}{$H+} +{$modeswitch objectivec2} + +interface + +uses + Classes, SysUtils, + CocoaAll, CocoaConfig, Cocoa_Extra, CocoaUtils; + +type + + TCocoaToolBarItemHandler = procedure ( Sender: id ); + + { TCocoaToolBarItem } + + TCocoaToolBarItem = objcclass( NSToolBarItem ) + private + _handler: TCocoaToolBarItemHandler; + procedure lclItemAction( sender: id ); message 'lclItemAction:'; + public + procedure lclSetHandler( handler: TCocoaToolBarItemHandler ); + message 'lclSetHandler:'; + end; + + TCocoaToolBarItemCreator = function ( identifier: String ): NSToolbarItem; + + { TCocoaToolBar } + + TCocoaToolBar = objcclass( NSToolBar, NSToolbarDelegateProtocol ) + private + _itemCreator: TCocoaToolBarItemCreator; + _defaultItemIdentifiers: NSArray; + _allowedItemIdentifiers: NSArray; + public + function initWithIdentifier( aIdentifier: NSString ): id; override; + procedure dealloc; override; + public + // NSToolbarDelegate + function toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar( + toolbar: NSToolbar; itemIdentifier: NSString; flag: ObjCBOOL ): NSToolbarItem; + function toolbarDefaultItemIdentifiers( toolbar: NSToolbar ): NSArray; + function toolbarAllowedItemIdentifiers( toolbar: NSToolbar ): NSArray; + public + procedure lclSetItemCreator( itemCreator: TCocoaToolBarItemCreator ); + message 'lclSetItemCreator:'; + procedure lclSetDefaultItemIdentifiers( identifiers: NSArray ); + message 'lclSetDefaultItemIdentifiers:'; + procedure lclSetAllowedItemIdentifiers( identifiers: NSArray ); + message 'lclSetAllowedItemIdentifiers:'; + end; + + { TCocoaToolBarUtils } + + TCocoaToolBarUtils = class + public + class procedure createToolBar( win: NSWindow ); + class function createToolBarItem( identifier: String; itemsConfig: TCocoaConfigToolBarItems ): NSToolbarItem; overload; + class function createToolBarItem( lclItemConfig: TCocoaConfigToolBarItem ): NSToolBarItem; overload; + end; + + function defaultToolBarItemCreatorImplement( identifier: String ): NSToolbarItem; + +implementation + +class procedure TCocoaToolBarUtils.createToolBar( win: NSWindow ); +var + toolBar: TCocoaToolBar; + defaultArray: NSArray; + allowedArray: NSArray; +begin + if NOT Assigned(CocoaConfigForm.toolBar.itemCreator) then + Exit; + + defaultArray:= StringArrayFromLCLToNS( CocoaConfigForm.toolBar.defaultItemsIdentifiers ); + allowedArray:= StringArrayFromLCLToNS( CocoaConfigForm.toolBar.allowedItemsIdentifiers ); + + toolBar:= TCocoaToolBar.alloc.initWithIdentifier( + StrToNSString(CocoaConfigForm.toolBar.identifier) ); + toolBar.lclSetDefaultItemIdentifiers( defaultArray ); + toolBar.lclSetAllowedItemIdentifiers( allowedArray ); + toolBar.lclSetItemCreator( TCocoaToolBarItemCreator(CocoaConfigForm.toolBar.itemCreator) ); + + win.setToolbar( toolBar ); +end; + +class function TCocoaToolBarUtils.createToolBarItem( identifier: String; + itemsConfig: TCocoaConfigToolBarItems ): NSToolbarItem; +var + i: Integer; + count: Integer; +begin + Result:= nil; + count:= length( itemsConfig ); + for i:=0 to count-1 do begin + if identifier = itemsConfig[i].identifier then begin + Result:= self.createToolBarItem( itemsConfig[i] ); + Exit; + end; + end; +end; + +class function TCocoaToolBarUtils.createToolBarItem( lclItemConfig: TCocoaConfigToolBarItem ): NSToolBarItem; +var + cocoaItem: TCocoaToolBarItem; + cocoaIdentifier: NSString; + cocoaImageName: NSString; + cocoaLabel: NSString; + cocoaTips: NSString; +begin + cocoaIdentifier:= StrToNSString( lclItemConfig.identifier ); + cocoaImageName:= StrToNSString( lclItemConfig.iconName ); + cocoaLabel:= StrToNSString( lclItemConfig.title ); + cocoaTips:= StrToNSString( lclItemConfig.tips ); + + cocoaItem:= TCocoaToolBarItem.alloc.initWithItemIdentifier( cocoaIdentifier ); + cocoaItem.setImage( NSImage.imageWithSystemSymbolName_accessibilityDescription( + cocoaImageName, nil ) ); + cocoaItem.setLabel( cocoaLabel ); + cocoaItem.setPaletteLabel( cocoaLabel ); + cocoaItem.setToolTip( cocoaTips ); + cocoaItem.setBordered( True ); + cocoaItem.setVisibilityPriority( NSToolbarItemVisibilityPriorityHigh ); + cocoaItem.setTarget( cocoaItem ); + cocoaItem.setAction( ObjCSelector('lclItemAction:') ); + cocoaItem.lclSetHandler( TCocoaToolBarItemHandler(lclItemConfig.onClick) ); + + Result:= cocoaItem; +end; + +function defaultToolBarItemCreatorImplement(identifier: String + ): NSToolbarItem; +begin + Result:= TCocoaToolBarUtils.createToolBarItem( identifier, CocoaConfigForm.toolBar.items ); +end; + +{ TCocoaToolBarItem } + +procedure TCocoaToolBarItem.lclItemAction(sender: id); +begin + _handler( sender ); +end; + +procedure TCocoaToolBarItem.lclSetHandler(handler: TCocoaToolBarItemHandler); +begin + _handler:= handler; +end; + +{ TCocoaToolBar } + +function TCocoaToolBar.initWithIdentifier( aIdentifier: NSString): id; +begin + Result:= inherited; + TCocoaToolBar(Result).setDelegate( Result ); +end; + +procedure TCocoaToolBar.dealloc; +begin + if Assigned(_defaultItemIdentifiers) then + _defaultItemIdentifiers.release; + if Assigned(_allowedItemIdentifiers) then + _allowedItemIdentifiers.release; +end; + +function TCocoaToolBar.toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar( + toolbar: NSToolbar; itemIdentifier: NSString; flag: ObjCBOOL): NSToolbarItem; +begin + Result:= _itemCreator( itemIdentifier.UTF8String ); +end; + +function TCocoaToolBar.toolbarDefaultItemIdentifiers(toolbar: NSToolbar + ): NSArray; +begin + Result:= _defaultItemIdentifiers; +end; + +function TCocoaToolBar.toolbarAllowedItemIdentifiers(toolbar: NSToolbar + ): NSArray; +begin + Result:= _allowedItemIdentifiers; +end; + +procedure TCocoaToolBar.lclSetItemCreator(itemCreator: TCocoaToolBarItemCreator + ); +begin + _itemCreator:= itemCreator; +end; + +procedure TCocoaToolBar.lclSetDefaultItemIdentifiers(identifiers: NSArray); +begin + if Assigned(_defaultItemIdentifiers) then + _defaultItemIdentifiers.release; + _defaultItemIdentifiers := identifiers; + _defaultItemIdentifiers.retain; +end; + +procedure TCocoaToolBar.lclSetAllowedItemIdentifiers(identifiers: NSArray); +begin + if Assigned(_allowedItemIdentifiers) then + _allowedItemIdentifiers.release; + _allowedItemIdentifiers := identifiers; + _allowedItemIdentifiers.retain; +end; + +end. + diff --git a/lcl/interfaces/cocoa/cocoautils.pas b/lcl/interfaces/cocoa/cocoautils.pas index fca1b52b16..9ede50485d 100644 --- a/lcl/interfaces/cocoa/cocoautils.pas +++ b/lcl/interfaces/cocoa/cocoautils.pas @@ -25,6 +25,7 @@ type const NSNullRect : NSRect = (origin:(x:0; y:0); size:(width:0; height:0)); +function StringArrayFromLCLToNS( lclArray: TStringArray ): NSArray; procedure hideAllSubviews( parent: NSView ); function GetNSSize(width, height: CGFloat): NSSize; inline; @@ -163,6 +164,23 @@ function AllocCursorFromCursorByDegrees(src: NSCursor; degrees: double): NSCurso implementation +function StringArrayFromLCLToNS( lclArray: TStringArray ): NSArray; +var + cocoaArray: NSMutableArray; + i: Integer; + count: Integer; +begin + Result:= nil; + if NOT Assigned(lclArray) then + Exit; + count:= length( lclArray ); + cocoaArray:= NSMutableArray.arrayWithCapacity( count ); + for i:=0 to count-1 do begin + cocoaArray.addObject( StrToNSString(lclArray[i]) ); + end; + Result:= cocoaArray; +end; + procedure hideAllSubviews( parent: NSView ); var view: NSView; diff --git a/lcl/interfaces/cocoa/cocoawsforms.pas b/lcl/interfaces/cocoa/cocoawsforms.pas index dd3ac59c72..af0c35f0d2 100644 --- a/lcl/interfaces/cocoa/cocoawsforms.pas +++ b/lcl/interfaces/cocoa/cocoawsforms.pas @@ -30,9 +30,9 @@ uses // Widgetset WSForms, WSLCLClasses, LCLMessageGlue, // LCL Cocoa - CocoaInt, CocoaConfig, CocoaPrivate, CocoaCallback, CocoaUtils, CocoaWSCommon, CocoaMenus, - CocoaGDIObjects, - CocoaWindows, CocoaCustomControl, CocoaScrollers, CocoaWSScrollers, cocoa_extra; + CocoaInt, CocoaConfig, CocoaPrivate, CocoaCallback, CocoaWSCommon, CocoaGDIObjects, + CocoaWindows, CocoaToolBar, CocoaCustomControl, CocoaScrollers, CocoaWSScrollers, + CocoaUtils, CocoaMenus, Cocoa_Extra; type { TLCLWindowCallback } @@ -856,6 +856,8 @@ begin end; doc.release; + TCocoaToolBarUtils.createToolBar( win ); + Result := TLCLHandle(cnt); end; diff --git a/lcl/interfaces/lcl.lpk b/lcl/interfaces/lcl.lpk index cece5a64da..7e60b0ff3f 100644 --- a/lcl/interfaces/lcl.lpk +++ b/lcl/interfaces/lcl.lpk @@ -131,7 +131,7 @@ end;"/> - + @@ -2688,6 +2688,11 @@ end;"/> + + + + +