From 76b5b8af3b9c28727e01c7a8b81f12219828a814 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 11 Oct 2025 18:00:42 -0400 Subject: [PATCH] Update touch bar and scrubber code --- Headers/AppKit/AppKit.h | 2 + Headers/AppKit/GSTouchBarWindow.h | 139 ++++++++ Headers/AppKit/NSScrubberItemView.h | 22 ++ Headers/AppKit/NSScrubberTouchBarItem.h | 74 +++++ Headers/AppKit/NSTouchBar.h | 97 +++++- Headers/AppKit/NSTouchBarItem.h | 48 ++- Source/GSTouchBarWindow.m | 402 ++++++++++++++++++++++++ Source/NSScrubberItemView.m | 67 ++++ Source/NSScrubberTouchBarItem.m | 105 +++++++ Source/NSTouchBar.m | 241 +++++++++++++- Source/NSTouchBarItem.m | 109 +++++++ TouchBarExample/GNUmakefile | 8 + TouchBarExample/TouchBarExample.m | 183 +++++++++++ 13 files changed, 1493 insertions(+), 4 deletions(-) create mode 100644 Headers/AppKit/GSTouchBarWindow.h create mode 100644 Headers/AppKit/NSScrubberTouchBarItem.h create mode 100644 Source/GSTouchBarWindow.m create mode 100644 Source/NSScrubberTouchBarItem.m create mode 100644 TouchBarExample/GNUmakefile create mode 100644 TouchBarExample/TouchBarExample.m diff --git a/Headers/AppKit/AppKit.h b/Headers/AppKit/AppKit.h index 8dd97ae557..95aebc28c9 100644 --- a/Headers/AppKit/AppKit.h +++ b/Headers/AppKit/AppKit.h @@ -269,6 +269,8 @@ #import #import #import +#import +#import #import #import #import diff --git a/Headers/AppKit/GSTouchBarWindow.h b/Headers/AppKit/GSTouchBarWindow.h new file mode 100644 index 0000000000..719ebe4cc6 --- /dev/null +++ b/Headers/AppKit/GSTouchBarWindow.h @@ -0,0 +1,139 @@ +/** GSTouchBarWindow + + Touch Bar fallback window class for systems without Touch Bar hardware + + GSTouchBarWindow provides a way to display NSTouchBar items in a regular + window on systems that don't have physical Touch Bar hardware, such as + Linux systems. This allows applications using Touch Bar APIs to still + function and provide their touch controls through a standard window interface. + + The fallback window system provides: + * Automatic detection of Touch Bar hardware availability + * Graceful fallback to windowed display mode + * Proper layout and presentation of Touch Bar items + * Integration with the existing NSTouchBar API + + Copyright (C) 2025 Free Software Foundation, Inc. + + By: GNUstep Contributors + Date: Sep 30 2025 + + This file is part of the GNUstep Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 USA. +*/ + +#ifndef _GSTouchBarWindow_h_GNUSTEP_GUI_INCLUDE +#define _GSTouchBarWindow_h_GNUSTEP_GUI_INCLUDE +#import + +#import + +#if OS_API_VERSION(MAC_OS_X_VERSION_10_12, GS_API_LATEST) + +#if defined(__cplusplus) +extern "C" { +#endif + +@class NSTouchBar; + +/** + * GSTouchBarWindow displays NSTouchBar items in a regular window + * for systems without physical Touch Bar hardware. + */ +APPKIT_EXPORT_CLASS +@interface GSTouchBarWindow : NSWindow +{ + NSTouchBar *_touchBar; + NSView *_itemContainerView; + NSMutableArray *_itemViews; + BOOL _autoHidesOnDeactivate; +} + +/** + * The touch bar whose items are displayed in this window. + */ +- (NSTouchBar *) touchBar; +- (void) setTouchBar: (NSTouchBar *)touchBar; + +/** + * Whether the window automatically hides when the application becomes inactive. + */ +- (BOOL) autoHidesOnDeactivate; +- (void) setAutoHidesOnDeactivate: (BOOL)autoHides; + +/** + * Creates a new touch bar window for the specified touch bar. + */ +- (id) initWithTouchBar: (NSTouchBar *)touchBar; + +/** + * Updates the window's content to reflect the current touch bar items. + */ +- (void) updateContent; + +/** + * Positions the window appropriately relative to the main window. + */ +- (void) positionRelativeToMainWindow; + +@end + +/** + * GSTouchBarFallbackManager manages the fallback display system + * for Touch Bars on systems without hardware support. + */ +APPKIT_EXPORT_CLASS +@interface GSTouchBarFallbackManager : NSObject +{ + NSMutableDictionary *_fallbackWindows; + BOOL _touchBarHardwareAvailable; +} + +/** + * Returns the shared fallback manager instance. + */ ++ (GSTouchBarFallbackManager *) sharedManager; + +/** + * Whether Touch Bar hardware is available on this system. + */ +- (BOOL) isTouchBarHardwareAvailable; + +/** + * Shows a fallback window for the specified touch bar. + */ +- (void) showFallbackWindowForTouchBar: (NSTouchBar *)touchBar; + +/** + * Hides the fallback window for the specified touch bar. + */ +- (void) hideFallbackWindowForTouchBar: (NSTouchBar *)touchBar; + +/** + * Returns the fallback window for the specified touch bar, if any. + */ +- (GSTouchBarWindow *) fallbackWindowForTouchBar: (NSTouchBar *)touchBar; + +@end + +#if defined(__cplusplus) +} +#endif + +#endif /* GS_API_MACOSX */ + +#endif /* _GSTouchBarWindow_h_GNUSTEP_GUI_INCLUDE */ \ No newline at end of file diff --git a/Headers/AppKit/NSScrubberItemView.h b/Headers/AppKit/NSScrubberItemView.h index 4eb804413f..ef8d497e95 100644 --- a/Headers/AppKit/NSScrubberItemView.h +++ b/Headers/AppKit/NSScrubberItemView.h @@ -43,6 +43,28 @@ APPKIT_EXPORT_CLASS @end +/** + * NSScrubberTextItemView displays a text label in a scrubber item. + */ +APPKIT_EXPORT_CLASS +@interface NSScrubberTextItemView : NSScrubberItemView +{ + NSTextField *_textField; +} + +/** + * The title displayed by this text item view. + */ +- (NSString *) title; +- (void) setTitle: (NSString *)title; + +/** + * The text field that displays the title. + */ +- (NSTextField *) textField; + +@end + #if defined(__cplusplus) } #endif diff --git a/Headers/AppKit/NSScrubberTouchBarItem.h b/Headers/AppKit/NSScrubberTouchBarItem.h new file mode 100644 index 0000000000..30864e5b84 --- /dev/null +++ b/Headers/AppKit/NSScrubberTouchBarItem.h @@ -0,0 +1,74 @@ +/** NSScrubberTouchBarItem + + Touch bar item that displays an NSScrubber control + + NSScrubberTouchBarItem is a specialized touch bar item that hosts + an NSScrubber control, allowing horizontal scrolling lists to be + displayed in the Touch Bar or fallback window. + + Copyright (C) 2025 Free Software Foundation, Inc. + + By: GNUstep Contributors + Date: Sep 30 2025 + + This file is part of the GNUstep Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 USA. +*/ + +#ifndef _NSScrubberTouchBarItem_h_GNUSTEP_GUI_INCLUDE +#define _NSScrubberTouchBarItem_h_GNUSTEP_GUI_INCLUDE +#import + +#import + +#if OS_API_VERSION(MAC_OS_X_VERSION_10_12, GS_API_LATEST) + +#if defined(__cplusplus) +extern "C" { +#endif + +@class NSScrubber; + +/** + * NSScrubberTouchBarItem is a touch bar item that displays an NSScrubber control. + */ +APPKIT_EXPORT_CLASS +@interface NSScrubberTouchBarItem : NSTouchBarItem +{ + NSScrubber *_scrubber; +} + +/** + * The scrubber control displayed by this item. + */ +- (NSScrubber *) scrubber; +- (void) setScrubber: (NSScrubber *)scrubber; + +/** + * Creates a new scrubber touch bar item with the specified identifier. + */ +- (id) initWithIdentifier: (NSString *)identifier; + +@end + +#if defined(__cplusplus) +} +#endif + +#endif /* GS_API_MACOSX */ + +#endif /* _NSScrubberTouchBarItem_h_GNUSTEP_GUI_INCLUDE */ \ No newline at end of file diff --git a/Headers/AppKit/NSTouchBar.h b/Headers/AppKit/NSTouchBar.h index 20c1459a30..f5ba25c383 100644 --- a/Headers/AppKit/NSTouchBar.h +++ b/Headers/AppKit/NSTouchBar.h @@ -55,6 +55,8 @@ #import #import +#import +#import #if OS_API_VERSION(MAC_OS_X_VERSION_10_12, GS_API_LATEST) @@ -62,8 +64,101 @@ extern "C" { #endif +@class NSTouchBarItem; +@class NSView; +@class NSWindow; + +/** + * NSTouchBar manages a collection of touch bar items and their presentation. + * On systems without physical touch bar hardware, it can display items in + * a fallback window. + */ APPKIT_EXPORT_CLASS -@interface NSTouchBar : NSObject +@interface NSTouchBar : NSObject +{ + NSString *_customizationIdentifier; + NSArray *_defaultItemIdentifiers; + NSArray *_itemIdentifiers; + NSString *_principalItemIdentifier; + id _delegate; + NSMutableDictionary *_items; + NSWindow *_fallbackWindow; + NSView *_fallbackContentView; + BOOL _isVisible; + BOOL _showsFallbackWindow; +} + +/** + * A string that uniquely identifies the touch bar for customization purposes. + */ +- (NSString *) customizationIdentifier; +- (void) setCustomizationIdentifier: (NSString *)identifier; + +/** + * An array of item identifiers that defines the default set of items. + */ +- (NSArray *) defaultItemIdentifiers; +- (void) setDefaultItemIdentifiers: (NSArray *)identifiers; + +/** + * An array of item identifiers for the items currently in the touch bar. + */ +- (NSArray *) itemIdentifiers; +- (void) setItemIdentifiers: (NSArray *)identifiers; + +/** + * The identifier of the principal item, which receives special treatment. + */ +- (NSString *) principalItemIdentifier; +- (void) setPrincipalItemIdentifier: (NSString *)identifier; + +/** + * The delegate object that provides touch bar items. + */ +- (id) delegate; +- (void) setDelegate: (id)delegate; + +/** + * Returns the touch bar item with the specified identifier. + */ +- (NSTouchBarItem *) itemForIdentifier: (NSString *)identifier; + +/** + * Whether the touch bar is currently visible. + */ +- (BOOL) isVisible; + +/** + * Shows or hides the touch bar fallback window on systems without hardware. + */ +- (void) setShowsFallbackWindow: (BOOL)shows; +- (BOOL) showsFallbackWindow; + +/** + * Shows the fallback window containing touch bar items. + */ +- (void) showFallbackWindow; + +/** + * Hides the fallback window. + */ +- (void) hideFallbackWindow; + +@end + +/** + * Informal protocol for NSTouchBar delegate methods. + * Delegates may implement these methods to provide items dynamically. + */ +@interface NSObject (NSTouchBarDelegate) + +/** + * Returns the touch bar item for the specified identifier. + * This method will be called when the touch bar needs an item + * that is not already cached. + */ +- (NSTouchBarItem *) touchBar: (NSTouchBar *)touchBar + makeItemForIdentifier: (NSString *)identifier; @end diff --git a/Headers/AppKit/NSTouchBarItem.h b/Headers/AppKit/NSTouchBarItem.h index 54562fa5f1..7cf7161d1c 100644 --- a/Headers/AppKit/NSTouchBarItem.h +++ b/Headers/AppKit/NSTouchBarItem.h @@ -28,6 +28,7 @@ #import #import +#import #if OS_API_VERSION(MAC_OS_X_VERSION_10_12, GS_API_LATEST) @@ -35,8 +36,53 @@ extern "C" { #endif +@class NSView; + +/** + * Standard touch bar item identifiers + */ +extern NSString * const NSTouchBarItemIdentifierFixedSpaceSmall; +extern NSString * const NSTouchBarItemIdentifierFixedSpaceLarge; +extern NSString * const NSTouchBarItemIdentifierFlexibleSpace; + +/** + * NSTouchBarItem represents a single item in a touch bar. + */ APPKIT_EXPORT_CLASS -@interface NSTouchBarItem : NSObject +@interface NSTouchBarItem : NSObject +{ + NSString *_identifier; + NSView *_view; + NSString *_customizationLabel; + BOOL _isVisible; +} + +/** + * The unique identifier for this item. + */ +- (NSString *) identifier; + +/** + * The view that represents this item's content. + */ +- (NSView *) view; +- (void) setView: (NSView *)view; + +/** + * The localized string labeling this item during customization. + */ +- (NSString *) customizationLabel; +- (void) setCustomizationLabel: (NSString *)label; + +/** + * Whether this item is currently visible. + */ +- (BOOL) isVisible; + +/** + * Creates a new touch bar item with the specified identifier. + */ +- (id) initWithIdentifier: (NSString *)identifier; @end diff --git a/Source/GSTouchBarWindow.m b/Source/GSTouchBarWindow.m new file mode 100644 index 0000000000..7527e59cda --- /dev/null +++ b/Source/GSTouchBarWindow.m @@ -0,0 +1,402 @@ +/* Implementation of class GSTouchBarWindow + Copyright (C) 2025 Free Software Foundation, Inc. + + By: GNUstep Contributors + Date: Sep 30 2025 + + This file is part of the GNUstep Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 USA. +*/ + +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import + +@implementation GSTouchBarWindow + +/* * Class methods */ + ++ (void) initialize +{ + if (self == [GSTouchBarWindow class]) + { + [self setVersion: 1]; + } +} + +/* + * Initialization and deallocation + */ + +- (id) initWithTouchBar: (NSTouchBar *)touchBar +{ + NSRect frame = NSMakeRect(0, 0, 600, 60); + NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; + + self = [super initWithContentRect: frame + styleMask: styleMask + backing: NSBackingStoreBuffered + defer: NO]; + + if (self) + { + ASSIGN(_touchBar, touchBar); + _autoHidesOnDeactivate = YES; + _itemViews = [[NSMutableArray alloc] init]; + + [self setTitle: @"Touch Bar"]; + [self setLevel: NSFloatingWindowLevel]; + [self setHidesOnDeactivate: NO]; + + [self _setupContent]; + [self updateContent]; + [self positionRelativeToMainWindow]; + + // Register for application state notifications + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver: self + selector: @selector(_applicationDidBecomeActive:) + name: NSApplicationDidBecomeActiveNotification + object: nil]; + [center addObserver: self + selector: @selector(_applicationDidResignActive:) + name: NSApplicationDidResignActiveNotification + object: nil]; + } + + return self; +} + +- (void) dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver: self]; + RELEASE(_touchBar); + RELEASE(_itemViews); + RELEASE(_itemContainerView); + [super dealloc]; +} + +/* + * Accessor methods + */ + +- (NSTouchBar *) touchBar +{ + return _touchBar; +} + +- (void) setTouchBar: (NSTouchBar *)touchBar +{ + ASSIGN(_touchBar, touchBar); + [self updateContent]; +} + +- (BOOL) autoHidesOnDeactivate +{ + return _autoHidesOnDeactivate; +} + +- (void) setAutoHidesOnDeactivate: (BOOL)autoHides +{ + _autoHidesOnDeactivate = autoHides; +} + +/* + * Content management + */ + +- (void) updateContent +{ + if (!_itemContainerView || !_touchBar) + return; + + // Remove existing items + NSEnumerator *enumerator = [_itemViews objectEnumerator]; + NSView *itemView; + + while ((itemView = [enumerator nextObject]) != nil) + { + [itemView removeFromSuperview]; + } + [_itemViews removeAllObjects]; + + // Add current touch bar items + NSArray *identifiers = [_touchBar itemIdentifiers]; + if (identifiers) + { + enumerator = [identifiers objectEnumerator]; + NSString *identifier; + + while ((identifier = [enumerator nextObject]) != nil) + { + NSTouchBarItem *item = [_touchBar itemForIdentifier: identifier]; + if (item && [item isVisible]) + { + itemView = [item view]; + if (itemView) + { + [_itemContainerView addSubview: itemView]; + [_itemViews addObject: itemView]; + } + else + { + // Create placeholder for space items + NSView *spacer = [self _createSpacerForIdentifier: identifier]; + if (spacer) + { + [_itemContainerView addSubview: spacer]; + [_itemViews addObject: spacer]; + } + } + } + } + } + + [self _layoutItemViews]; +} + +- (void) positionRelativeToMainWindow +{ + NSWindow *mainWindow = [[NSApplication sharedApplication] mainWindow]; + NSRect mainFrame, windowFrame; + + if (mainWindow) + { + mainFrame = [mainWindow frame]; + windowFrame = [self frame]; + + // Position below the main window + windowFrame.origin.x = mainFrame.origin.x + (NSWidth(mainFrame) - NSWidth(windowFrame)) / 2; + windowFrame.origin.y = mainFrame.origin.y - NSHeight(windowFrame) - 20; + + [self setFrame: windowFrame display: YES]; + } + else + { + [self center]; + } +} + +/* + * Private methods + */ + +- (void) _setupContent +{ + // Create container view for items + NSRect contentBounds = [[self contentView] bounds]; + _itemContainerView = [[NSView alloc] initWithFrame: NSInsetRect(contentBounds, 10, 10)]; + [_itemContainerView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; + [[self contentView] addSubview: _itemContainerView]; + + // Set background color to match Touch Bar appearance + NSColor *backgroundColor = [NSColor colorWithCalibratedRed: 0.2 green: 0.2 blue: 0.2 alpha: 1.0]; + [self setBackgroundColor: backgroundColor]; +} + +- (void) _layoutItemViews +{ + NSRect containerBounds = [_itemContainerView bounds]; + NSUInteger itemCount = [_itemViews count]; + + if (itemCount == 0) + return; + + CGFloat totalWidth = NSWidth(containerBounds); + CGFloat spacing = 8.0; + CGFloat availableWidth = totalWidth - (spacing * (itemCount - 1)); + CGFloat itemWidth = availableWidth / itemCount; + CGFloat currentX = 0; + + NSEnumerator *enumerator = [_itemViews objectEnumerator]; + NSView *itemView; + + while ((itemView = [enumerator nextObject]) != nil) + { + NSRect itemFrame = NSMakeRect(currentX, + (NSHeight(containerBounds) - 30) / 2, + itemWidth, + 30); + [itemView setFrame: itemFrame]; + currentX += itemWidth + spacing; + } +} + +- (NSView *) _createSpacerForIdentifier: (NSString *)identifier +{ + NSView *spacer = nil; + CGFloat width = 0; + + if ([identifier isEqualToString: NSTouchBarItemIdentifierFixedSpaceSmall]) + { + width = 16; + } + else if ([identifier isEqualToString: NSTouchBarItemIdentifierFixedSpaceLarge]) + { + width = 32; + } + else if ([identifier isEqualToString: NSTouchBarItemIdentifierFlexibleSpace]) + { + width = 20; // Will expand in stack view + } + + if (width > 0) + { + spacer = AUTORELEASE([[NSView alloc] initWithFrame: NSMakeRect(0, 0, width, 30)]); + } + + return spacer; +} + +/* + * Notification handlers + */ + +- (void) _applicationDidBecomeActive: (NSNotification *)notification +{ + if ([self isVisible]) + { + [self orderFront: nil]; + } +} + +- (void) _applicationDidResignActive: (NSNotification *)notification +{ + if (_autoHidesOnDeactivate) + { + [self orderOut: nil]; + } +} + +@end + +@implementation GSTouchBarFallbackManager + +static GSTouchBarFallbackManager *_sharedManager = nil; + +/* * Class methods */ + ++ (void) initialize +{ + if (self == [GSTouchBarFallbackManager class]) + { + [self setVersion: 1]; + } +} + ++ (GSTouchBarFallbackManager *) sharedManager +{ + if (_sharedManager == nil) + { + _sharedManager = [[GSTouchBarFallbackManager alloc] init]; + } + return _sharedManager; +} + +/* + * Initialization and deallocation + */ + +- (id) init +{ + self = [super init]; + if (self) + { + _fallbackWindows = [[NSMutableDictionary alloc] init]; + _touchBarHardwareAvailable = [self _detectTouchBarHardware]; + } + return self; +} + +- (void) dealloc +{ + [_fallbackWindows release]; + [super dealloc]; +} + +/* + * Hardware detection + */ + +- (BOOL) isTouchBarHardwareAvailable +{ + return _touchBarHardwareAvailable; +} + +- (BOOL) _detectTouchBarHardware +{ + // On Linux and most systems, Touch Bar hardware is not available + // This could be enhanced to check for specific hardware or environment variables + return NO; +} + +/* + * Fallback window management + */ + +- (void) showFallbackWindowForTouchBar: (NSTouchBar *)touchBar +{ + if (!touchBar) + return; + + NSString *key = [NSString stringWithFormat: @"%p", touchBar]; + GSTouchBarWindow *window = [_fallbackWindows objectForKey: key]; + + if (!window) + { + window = [[GSTouchBarWindow alloc] initWithTouchBar: touchBar]; + [_fallbackWindows setObject: window forKey: key]; + [window release]; // Retained by dictionary + } + + [window makeKeyAndOrderFront: nil]; +} + +- (void) hideFallbackWindowForTouchBar: (NSTouchBar *)touchBar +{ + if (!touchBar) + return; + + NSString *key = [NSString stringWithFormat: @"%p", touchBar]; + GSTouchBarWindow *window = [_fallbackWindows objectForKey: key]; + + if (window) + { + [window orderOut: nil]; + [_fallbackWindows removeObjectForKey: key]; + } +} + +- (GSTouchBarWindow *) fallbackWindowForTouchBar: (NSTouchBar *)touchBar +{ + if (!touchBar) + return nil; + + NSString *key = [NSString stringWithFormat: @"%p", touchBar]; + return [_fallbackWindows objectForKey: key]; +} + +@end \ No newline at end of file diff --git a/Source/NSScrubberItemView.m b/Source/NSScrubberItemView.m index e7ab88793d..0bfeb3c2af 100644 --- a/Source/NSScrubberItemView.m +++ b/Source/NSScrubberItemView.m @@ -23,6 +23,9 @@ */ #import "AppKit/NSScrubberItemView.h" +#import +#import +#import @implementation NSScrubberArrangedView @@ -32,3 +35,67 @@ @implementation NSScrubberItemView @end +@implementation NSScrubberTextItemView + +/* + * Class methods + */ + ++ (void) initialize +{ + if (self == [NSScrubberTextItemView class]) + { + [self setVersion: 1]; + } +} + +/* + * Initialization and deallocation + */ + +- (id) initWithFrame: (NSRect)frame +{ + self = [super initWithFrame: frame]; + if (self) + { + _textField = [[NSTextField alloc] initWithFrame: NSMakeRect(0, 0, frame.size.width, frame.size.height)]; + [_textField setBezeled: NO]; + [_textField setDrawsBackground: NO]; + [_textField setEditable: NO]; + [_textField setSelectable: NO]; + [_textField setFont: [NSFont systemFontOfSize: [NSFont smallSystemFontSize]]]; + [_textField setAlignment: NSTextAlignmentCenter]; + [_textField setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; + + [self addSubview: _textField]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_textField); + [super dealloc]; +} + +/* + * Accessor methods + */ + +- (NSString *) title +{ + return [_textField stringValue]; +} + +- (void) setTitle: (NSString *)title +{ + [_textField setStringValue: title ? title : @""]; +} + +- (NSTextField *) textField +{ + return _textField; +} + +@end + diff --git a/Source/NSScrubberTouchBarItem.m b/Source/NSScrubberTouchBarItem.m new file mode 100644 index 0000000000..9ac67237b6 --- /dev/null +++ b/Source/NSScrubberTouchBarItem.m @@ -0,0 +1,105 @@ +/* Implementation of class NSScrubberTouchBarItem + Copyright (C) 2025 Free Software Foundation, Inc. + + By: GNUstep Contributors + Date: Sep 30 2025 + + This file is part of the GNUstep Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 USA. +*/ + +#import +#import +#import +#import + +@implementation NSScrubberTouchBarItem + +/* + * Class methods + */ + ++ (void) initialize +{ + if (self == [NSScrubberTouchBarItem class]) + { + [self setVersion: 1]; + } +} + +/* + * Initialization and deallocation + */ + +- (id) initWithIdentifier: (NSString *)identifier +{ + self = [super initWithIdentifier: identifier]; + if (self) + { + _scrubber = [[NSScrubber alloc] init]; + [self setView: _scrubber]; + [self setCustomizationLabel: @"Scrubber"]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_scrubber); + [super dealloc]; +} + +/* + * NSCoding protocol implementation + */ + +- (id) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self) + { + ASSIGN(_scrubber, [coder decodeObjectForKey: @"NSScrubberTouchBarItem.scrubber"]); + if (_scrubber) + { + [self setView: _scrubber]; + } + } + return self; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + [coder encodeObject: _scrubber forKey: @"NSScrubberTouchBarItem.scrubber"]; +} + +/* + * Accessor methods + */ + +- (NSScrubber *) scrubber +{ + return _scrubber; +} + +- (void) setScrubber: (NSScrubber *)scrubber +{ + ASSIGN(_scrubber, scrubber); + [self setView: _scrubber]; +} + +@end \ No newline at end of file diff --git a/Source/NSTouchBar.m b/Source/NSTouchBar.m index f5b8e675f1..1cb37fbfd0 100644 --- a/Source/NSTouchBar.m +++ b/Source/NSTouchBar.m @@ -9,7 +9,7 @@ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. + version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -23,8 +23,245 @@ */ #import +#import +#import +#import +#import +#import +#import @implementation NSTouchBar -@end +/* + * Class methods + */ + ++ (void) initialize +{ + if (self == [NSTouchBar class]) + { + [self setVersion: 1]; + } +} + +/* + * Initialization and deallocation + */ + +- (id) init +{ + self = [super init]; + if (self) + { + _items = [[NSMutableDictionary alloc] init]; + _isVisible = NO; + _showsFallbackWindow = YES; // Default to showing fallback on Linux + _customizationIdentifier = nil; + _defaultItemIdentifiers = nil; + _itemIdentifiers = nil; + _principalItemIdentifier = nil; + _delegate = nil; + _fallbackWindow = nil; + _fallbackContentView = nil; + } + return self; +} + +- (void) dealloc +{ + [self hideFallbackWindow]; + RELEASE(_customizationIdentifier); + RELEASE(_defaultItemIdentifiers); + RELEASE(_itemIdentifiers); + RELEASE(_principalItemIdentifier); + RELEASE(_items); + RELEASE(_fallbackWindow); + RELEASE(_fallbackContentView); + [super dealloc]; +} + +/* + * NSCoding protocol implementation + */ + +- (id) initWithCoder: (NSCoder *)coder +{ + self = [super init]; + if (self) + { + ASSIGNCOPY(_customizationIdentifier, [coder decodeObjectForKey: @"NSTouchBar.customizationIdentifier"]); + ASSIGNCOPY(_defaultItemIdentifiers, [coder decodeObjectForKey: @"NSTouchBar.defaultItemIdentifiers"]); + ASSIGNCOPY(_itemIdentifiers, [coder decodeObjectForKey: @"NSTouchBar.itemIdentifiers"]); + ASSIGNCOPY(_principalItemIdentifier, [coder decodeObjectForKey: @"NSTouchBar.principalItemIdentifier"]); + _showsFallbackWindow = [coder decodeBoolForKey: @"NSTouchBar.showsFallbackWindow"]; + _items = [[NSMutableDictionary alloc] init]; + _isVisible = NO; + _delegate = nil; + _fallbackWindow = nil; + _fallbackContentView = nil; + } + return self; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [coder encodeObject: _customizationIdentifier forKey: @"NSTouchBar.customizationIdentifier"]; + [coder encodeObject: _defaultItemIdentifiers forKey: @"NSTouchBar.defaultItemIdentifiers"]; + [coder encodeObject: _itemIdentifiers forKey: @"NSTouchBar.itemIdentifiers"]; + [coder encodeObject: _principalItemIdentifier forKey: @"NSTouchBar.principalItemIdentifier"]; + [coder encodeBool: _showsFallbackWindow forKey: @"NSTouchBar.showsFallbackWindow"]; +} + +/* + * Accessor methods + */ + +- (NSString *) customizationIdentifier +{ + return _customizationIdentifier; +} + +- (void) setCustomizationIdentifier: (NSString *)identifier +{ + ASSIGNCOPY(_customizationIdentifier, identifier); +} + +- (NSArray *) defaultItemIdentifiers +{ + return _defaultItemIdentifiers; +} + +- (void) setDefaultItemIdentifiers: (NSArray *)identifiers +{ + ASSIGNCOPY(_defaultItemIdentifiers, identifiers); +} + +- (NSArray *) itemIdentifiers +{ + return _itemIdentifiers; +} + +- (void) setItemIdentifiers: (NSArray *)identifiers +{ + ASSIGNCOPY(_itemIdentifiers, identifiers); + [self _updateItems]; +} +- (NSString *) principalItemIdentifier +{ + return _principalItemIdentifier; +} + +- (void) setPrincipalItemIdentifier: (NSString *)identifier +{ + ASSIGNCOPY(_principalItemIdentifier, identifier); +} + +- (id) delegate +{ + return _delegate; +} + +- (void) setDelegate: (id)delegate +{ + _delegate = delegate; + [self _updateItems]; +} + +- (BOOL) isVisible +{ + return _isVisible; +} + +- (BOOL) showsFallbackWindow +{ + return _showsFallbackWindow; +} + +- (void) setShowsFallbackWindow: (BOOL)shows +{ + _showsFallbackWindow = shows; + if (!shows) + { + [self hideFallbackWindow]; + } +} + +/* + * Item management + */ + +- (NSTouchBarItem *) itemForIdentifier: (NSString *)identifier +{ + NSTouchBarItem *item = [_items objectForKey: identifier]; + + if (!item && _delegate && [_delegate respondsToSelector: @selector(touchBar:makeItemForIdentifier:)]) + { + item = [_delegate touchBar: self makeItemForIdentifier: identifier]; + if (item) + { + [_items setObject: item forKey: identifier]; + } + } + + return item; +} + +/* + * Fallback window management + */ + +- (void) showFallbackWindow +{ + if (!_showsFallbackWindow) + return; + + GSTouchBarFallbackManager *manager = [GSTouchBarFallbackManager sharedManager]; + + // Only show fallback window if no hardware is available + if (![manager isTouchBarHardwareAvailable]) + { + [manager showFallbackWindowForTouchBar: self]; + _isVisible = YES; + } +} + +- (void) hideFallbackWindow +{ + GSTouchBarFallbackManager *manager = [GSTouchBarFallbackManager sharedManager]; + [manager hideFallbackWindowForTouchBar: self]; + _isVisible = NO; +} + +/* + * Private methods + */ + +- (void) _updateItems +{ + // Clear existing items that are no longer needed + NSArray *currentKeys = [_items allKeys]; + NSEnumerator *keyEnumerator = [currentKeys objectEnumerator]; + NSString *key; + + while ((key = [keyEnumerator nextObject]) != nil) + { + if (![_itemIdentifiers containsObject: key]) + { + [_items removeObjectForKey: key]; + } + } + + // Update fallback window if visible + if (_isVisible) + { + GSTouchBarFallbackManager *manager = [GSTouchBarFallbackManager sharedManager]; + GSTouchBarWindow *window = [manager fallbackWindowForTouchBar: self]; + if (window) + { + [window updateContent]; + } + } +} + +@end diff --git a/Source/NSTouchBarItem.m b/Source/NSTouchBarItem.m index 2297d16f86..19f169ec5f 100644 --- a/Source/NSTouchBarItem.m +++ b/Source/NSTouchBarItem.m @@ -23,8 +23,117 @@ */ #import +#import +#import +#import + +// Standard touch bar item identifiers +NSString * const NSTouchBarItemIdentifierFixedSpaceSmall = @"NSTouchBarItemIdentifierFixedSpaceSmall"; +NSString * const NSTouchBarItemIdentifierFixedSpaceLarge = @"NSTouchBarItemIdentifierFixedSpaceLarge"; +NSString * const NSTouchBarItemIdentifierFlexibleSpace = @"NSTouchBarItemIdentifierFlexibleSpace"; @implementation NSTouchBarItem +/* + * Class methods + */ + ++ (void) initialize +{ + if (self == [NSTouchBarItem class]) + { + [self setVersion: 1]; + } +} + +/* + * Initialization and deallocation + */ + +- (id) init +{ + return [self initWithIdentifier: nil]; +} + +- (id) initWithIdentifier: (NSString *)identifier +{ + self = [super init]; + if (self) + { + ASSIGNCOPY(_identifier, identifier); + _view = nil; + _customizationLabel = nil; + _isVisible = YES; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_identifier); + RELEASE(_view); + RELEASE(_customizationLabel); + [super dealloc]; +} + +/* + * NSCoding protocol implementation + */ + +- (id) initWithCoder: (NSCoder *)coder +{ + self = [super init]; + if (self) + { + ASSIGNCOPY(_identifier, [coder decodeObjectForKey: @"NSTouchBarItem.identifier"]); + ASSIGN(_view, [coder decodeObjectForKey: @"NSTouchBarItem.view"]); + ASSIGNCOPY(_customizationLabel, [coder decodeObjectForKey: @"NSTouchBarItem.customizationLabel"]); + _isVisible = [coder decodeBoolForKey: @"NSTouchBarItem.isVisible"]; + } + return self; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [coder encodeObject: _identifier forKey: @"NSTouchBarItem.identifier"]; + [coder encodeObject: _view forKey: @"NSTouchBarItem.view"]; + [coder encodeObject: _customizationLabel forKey: @"NSTouchBarItem.customizationLabel"]; + [coder encodeBool: _isVisible forKey: @"NSTouchBarItem.isVisible"]; +} + +/* + * Accessor methods + */ + +- (NSString *) identifier +{ + return _identifier; +} + +- (NSView *) view +{ + return _view; +} + +- (void) setView: (NSView *)view +{ + ASSIGN(_view, view); +} + +- (NSString *) customizationLabel +{ + return _customizationLabel; +} + +- (void) setCustomizationLabel: (NSString *)label +{ + ASSIGNCOPY(_customizationLabel, label); +} + +- (BOOL) isVisible +{ + return _isVisible; +} + @end diff --git a/TouchBarExample/GNUmakefile b/TouchBarExample/GNUmakefile new file mode 100644 index 0000000000..9cb3661506 --- /dev/null +++ b/TouchBarExample/GNUmakefile @@ -0,0 +1,8 @@ +# Simple Makefile for TouchBarExample + +include $(GNUSTEP_MAKEFILES)/common.make + +APP_NAME = TouchBarExample +TouchBarExample_OBJC_FILES = TouchBarExample.m + +include $(GNUSTEP_MAKEFILES)/application.make \ No newline at end of file diff --git a/TouchBarExample/TouchBarExample.m b/TouchBarExample/TouchBarExample.m new file mode 100644 index 0000000000..e5d2f16f08 --- /dev/null +++ b/TouchBarExample/TouchBarExample.m @@ -0,0 +1,183 @@ +/** TouchBarExample + + Example application demonstrating NSTouchBar with NSScrubber fallback + + This example shows how to create a touch bar with an NSScrubber control + and display it in a fallback window on systems without touch bar hardware. + + Copyright (C) 2025 Free Software Foundation, Inc. + + By: GNUstep Contributors + Date: Sep 30 2025 +*/ + +#import + +@interface TouchBarExampleDelegate : NSObject +{ + NSWindow *_mainWindow; + NSTouchBar *_touchBar; + NSArray *_scrubberItems; +} + +@end + +@implementation TouchBarExampleDelegate + +- (id) init +{ + self = [super init]; + if (self) + { + ASSIGN(_scrubberItems, [NSArray arrayWithObjects: + @"Item 1", @"Item 2", @"Item 3", @"Item 4", @"Item 5", + @"Item 6", @"Item 7", @"Item 8", @"Item 9", @"Item 10", + nil]); + } + return self; +} + +- (void) dealloc +{ + RELEASE(_scrubberItems); + RELEASE(_touchBar); + RELEASE(_mainWindow); + [super dealloc]; +} + +- (void) applicationDidFinishLaunching: (NSNotification *)notification +{ + // Create main window + NSRect frame = NSMakeRect(100, 100, 400, 300); + _mainWindow = [[NSWindow alloc] + initWithContentRect: frame + styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable + backing: NSBackingStoreBuffered + defer: NO]; + + [_mainWindow setTitle: @"Touch Bar Example"]; + [_mainWindow makeKeyAndOrderFront: nil]; + + // Create touch bar + [self _createTouchBar]; + + // Show fallback window (since we don't have hardware touch bar) + [_touchBar showFallbackWindow]; +} + +- (void) _createTouchBar +{ + _touchBar = [[NSTouchBar alloc] init]; + [_touchBar setDelegate: self]; + [_touchBar setCustomizationIdentifier: @"com.example.touchbar"]; + [_touchBar setDefaultItemIdentifiers: [NSArray arrayWithObjects: + @"scrubber.example", + NSTouchBarItemIdentifierFlexibleSpace, + @"button.example", nil]]; + [_touchBar setItemIdentifiers: [_touchBar defaultItemIdentifiers]]; +} + +/* + * NSTouchBar delegate methods + */ + +- (NSTouchBarItem *) touchBar: (NSTouchBar *)touchBar makeItemForIdentifier: (NSString *)identifier +{ + if ([identifier isEqualToString: @"scrubber.example"]) + { + NSScrubberTouchBarItem *scrubberItem = + [[NSScrubberTouchBarItem alloc] initWithIdentifier: identifier]; + + NSScrubber *scrubber = [scrubberItem scrubber]; + [scrubber setDataSource: self]; + [scrubber setDelegate: self]; + + // Configure scrubber appearance + [scrubber setItemAlignment: NSScrubberAlignmentCenter]; + [scrubber setShowsArrowButtons: YES]; + + [scrubberItem setCustomizationLabel: @"Example Scrubber"]; + return AUTORELEASE(scrubberItem); + } + else if ([identifier isEqualToString: @"button.example"]) + { + NSTouchBarItem *buttonItem = [[NSTouchBarItem alloc] initWithIdentifier: identifier]; + + NSButton *button = [[NSButton alloc] init]; + [button setTitle: @"Example"]; + [button setTarget: self]; + [button setAction: @selector(buttonPressed:)]; + [button sizeToFit]; + + [buttonItem setView: button]; + [buttonItem setCustomizationLabel: @"Example Button"]; + + RELEASE(button); + return AUTORELEASE(buttonItem); + } + + return nil; +} + +/* + * NSScrubber data source methods + */ + +- (NSInteger) numberOfItemsForScrubber: (NSScrubber *)scrubber +{ + return [_scrubberItems count]; +} + +- (NSScrubberItemView *) scrubber: (NSScrubber *)scrubber + viewForItemAtIndex: (NSInteger)index +{ + NSString *identifier = @"TextItem"; + NSScrubberTextItemView *itemView = + (NSScrubberTextItemView *)[scrubber makeItemWithIdentifier: identifier owner: nil]; + + if (!itemView) + { + itemView = AUTORELEASE([[NSScrubberTextItemView alloc] init]); + } + + [itemView setTitle: [_scrubberItems objectAtIndex: index]]; + return itemView; +} + +/* + * NSScrubber delegate methods + */ + +- (void) scrubber: (NSScrubber *)scrubber didSelectItemAtIndex: (NSInteger)selectedIndex +{ + NSString *item = [_scrubberItems objectAtIndex: selectedIndex]; + NSLog(@"Selected scrubber item: %@", item); +} + +/* + * Action methods + */ + +- (void) buttonPressed: (id)sender +{ + NSLog(@"Touch Bar button pressed!"); +} + +@end + +// Main function +int main(int argc, const char *argv[]) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSApplication *app = [NSApplication sharedApplication]; + TouchBarExampleDelegate *delegate = [[TouchBarExampleDelegate alloc] init]; + [app setDelegate: delegate]; + + [app run]; + + RELEASE(delegate); + RELEASE(pool); + return 0; +} \ No newline at end of file