From ac1407484ae4d49604a6d68cd5e7a0f036ed9ef8 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sun, 25 Jan 2026 17:19:07 +0100 Subject: [PATCH] fixed: outlineview arrow colors --- AppKit/CPOutlineView.j | 99 +++++++++++++++---- .../Manual/ThemeKitchenSinkA3/AppController.j | 86 +++++++++++++++- 2 files changed, 162 insertions(+), 23 deletions(-) diff --git a/AppKit/CPOutlineView.j b/AppKit/CPOutlineView.j index 4cc8ff3cd..805b8a95e 100644 --- a/AppKit/CPOutlineView.j +++ b/AppKit/CPOutlineView.j @@ -2219,20 +2219,22 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, { [self setBordered:NO]; [self setBezelStyle:CPDisclosureBezelStyle]; + _angle = 0.0; } return self; } +// 1. Tell the animation system that 'angle' must be animated via a Timer loop +// (Software animation) because it affects drawRect, not CSS. + (BOOL)needsPeriodicFrameUpdatesForKey:(CPString)aKey { if (aKey === @"angle") return YES; - return [super needsPeriodicFrameUpdatesForKey:aKey]; } -// 1. Expose angle as a property so the animator proxy can interpolate it. +// 2. Accessor for the animator to set the value - (void)setAngle:(float)anAngle { _angle = anAngle; @@ -2246,27 +2248,29 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, - (void)setState:(CPInteger)aState { + // If the state isn't changing, do nothing + if ([self state] === aState) + return; + [super setState:aState]; + // Calculate target angle: 0.0 for Expanded (Down), -PI_2 for Collapsed (Right) var targetAngle = ([self state] === CPOnState) ? 0.0 : -PI_2; - // 2. Only animate if the view is currently in a window (visible). - // This prevents rows from spinning into place during initial load or scrolling. + // Only animate if the view is visible in a window if ([self window]) { [CPAnimationContext beginGrouping]; - - // Duration of the rotation (0.2s is standard for macOS-like toggles) [[CPAnimationContext currentContext] setDuration:0.2]; - // Use the animator proxy to smoothly interpolate the 'angle' property + // Because we implemented +needsPeriodicFrameUpdatesForKey:, + // calling this on the animator automatically starts the timer loop. [[self animator] setAngle:targetAngle]; - debugger + [CPAnimationContext endGrouping]; } else { - // If not visible/loading, set immediately [self setAngle:targetAngle]; } } @@ -2279,38 +2283,95 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, height = CGRectGetHeight(bounds); CGContextBeginPath(context); + CGContextSaveGState(context); - // 3. The drawing logic uses _angle, which is now being interpolated by the animator - if (_angle) + // 3. Apply rotation centered on the view + if (_angle !== 0.0) { - var centre = CGPointMake(FLOOR(width / 2.0), FLOOR(height / 2.0)); - CGContextTranslateCTM(context, centre.x, centre.y); + var centreX = FLOOR(width / 2.0), + centreY = FLOOR(height / 2.0); + + CGContextTranslateCTM(context, centreX, centreY); CGContextRotateCTM(context, _angle); - CGContextTranslateCTM(context, -centre.x, -centre.y); + CGContextTranslateCTM(context, -centreX, -centreY); } - // Center, but crisp. + // Center the triangle visually CGContextTranslateCTM(context, FLOOR((width - 9.0) / 2.0), FLOOR((height - 8.0) / 2.0)); + // Draw the triangle CGContextMoveToPoint(context, 0.0, 0.0); CGContextAddLineToPoint(context, 9.0, 0.0); CGContextAddLineToPoint(context, 4.5, 8.0); CGContextClosePath(context); - CGContextSetFillColor(context, - colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected], - [self hasThemeState:CPThemeStateHighlighted])); + // Calculate Color based on HUD and KeyWindow states --- + var isSelected = [self hasThemeState:CPThemeStateSelected], + isHighlighted = [self hasThemeState:CPThemeStateHighlighted], + isKeyWindow = [self hasThemeState:CPThemeStateKeyWindow], + // Detect HUD style mask on the window + isHUD = [self window] && ([[self window] styleMask] & CPHUDBackgroundWindowMask), + triangleColor = nil; + + if (isHUD) + { + // 2. HUD Logic + if (isSelected) + triangleColor = [CPColor blackColor]; // Selected HUD row = White BG -> Black Triangle + else + triangleColor = [CPColor whiteColor]; // Normal HUD row = Dark BG -> White Triangle + + // Handle click highlight in HUD (dim it) + if (isHighlighted) + triangleColor = [triangleColor colorWithAlphaComponent:0.5]; + } + else + { + // Standard Logic + if (isSelected) + { + if (isKeyWindow) + triangleColor = [CPColor whiteColor]; // Key Window + Selected = Blue BG -> White Triangle + else + triangleColor = [CPColor blackColor]; // 1. Non-Key Window + Selected = Gray BG -> Black Triangle + } + else + { + // Standard Unselected + triangleColor = [CPColor colorWithCalibratedWhite:0.45 alpha: 1.0]; + } + + // Handle click highlight in Standard + if (isHighlighted) + { + if (isSelected && isKeyWindow) + triangleColor = [CPColor colorWithCalibratedWhite:0.9 alpha: 1.0]; + else if (isSelected && !isKeyWindow) + triangleColor = [CPColor colorWithCalibratedWhite:0.2 alpha: 1.0]; + else + triangleColor = [CPColor colorWithCalibratedWhite:0.25 alpha: 1.0]; + } + } + + CGContextSetFillColor(context, triangleColor); + CGContextFillPath(context); + // Draw the outline CGContextBeginPath(context); CGContextMoveToPoint(context, 0.0, 0.0); CGContextAddLineToPoint(context, 4.5, 8.0); + // Only draw the top line (completing the triangle) if not rotated (standard Cocoa look) if (_angle === 0.0) CGContextAddLineToPoint(context, 9.0, 0.0); - CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha:0.7]); + // Adjust stroke opacity for HUD/Non-Key to avoid "ghostly" white borders on light backgrounds + var strokeAlpha = (isHUD || (isSelected && !isKeyWindow)) ? 0.3 : 0.7; + CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha:strokeAlpha]); CGContextStrokePath(context); + + CGContextRestoreGState(context); } @end diff --git a/Tests/Manual/ThemeKitchenSinkA3/AppController.j b/Tests/Manual/ThemeKitchenSinkA3/AppController.j index e642596c6..fcb24d93b 100644 --- a/Tests/Manual/ThemeKitchenSinkA3/AppController.j +++ b/Tests/Manual/ThemeKitchenSinkA3/AppController.j @@ -6,6 +6,7 @@ * Refactored: Fixed RuleEditor action handling. * Refactored: Added OutlineView and reordered tabs. * Refactored: Fixed control disabling logic for all tabs. + * Added: CPAlert demonstrations wired to control buttons. */ @import @@ -196,20 +197,35 @@ // Buttons var pushButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, fieldHeight)]; - [pushButton setTitle:@"Push Button"]; + [pushButton setTitle:@"Push Button (Info Alert)"]; [pushButton setBezelStyle:CPRoundedBezelStyle]; + + // ACTION: Standard Informational Alert + [pushButton setTarget:self]; + [pushButton setAction:@selector(showStandardAlert:)]; + [leftContent addSubview:pushButton]; currentY += gapY; var gradientButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, fieldHeight)]; - [gradientButton setTitle:@"Gradient Button"]; + [gradientButton setTitle:@"Gradient (HUD Alert)"]; [gradientButton setBezelStyle:CPSmallSquareBezelStyle]; + + // ACTION: Critical / HUD Alert + [gradientButton setTarget:self]; + [gradientButton setAction:@selector(showHUDAlert:)]; + [leftContent addSubview:gradientButton]; currentY += gapY; var roundRectButton = [[CPButton alloc] initWithFrame:CGRectMake(innerX, currentY, controlWidth, fieldHeight)]; - [roundRectButton setTitle:@"Round Rect Button"]; + [roundRectButton setTitle:@"Round (Sheet Alert)"]; [roundRectButton setBezelStyle:CPRoundRectBezelStyle]; + + // ACTION: Warning Sheet + [roundRectButton setTarget:self]; + [roundRectButton setAction:@selector(showSheetAlert:)]; + [leftContent addSubview:roundRectButton]; currentY += gapY + 5.0; @@ -385,6 +401,68 @@ [self _applyHUDStateToView:containerView]; } +// -------------------------------------------------------------------------------- +// ALERT ACTIONS +// -------------------------------------------------------------------------------- + +- (void)showStandardAlert:(id)sender +{ + var alert = [[CPAlert alloc] init]; + [alert setTitle:@"Standard Alert"]; + [alert setMessageText:@"Informational Alert"]; + [alert setInformativeText:@"This is a standard CPAlert with the CPInformationalAlertStyle. It behaves like a standard modal dialog."]; + [alert setAlertStyle:CPInformationalAlertStyle]; + + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; +} + +- (void)showHUDAlert:(id)sender +{ + var alert = [[CPAlert alloc] init]; + [alert setTitle:@"HUD Alert"]; + [alert setMessageText:@"Critical HUD Alert"]; + [alert setInformativeText:@"This alert uses the CPCriticalAlertStyle and explicitly sets the HUD theme."]; + [alert setAlertStyle:CPCriticalAlertStyle]; + + // Explicitly set the HUD theme like in the example + [alert setTheme:[CPTheme defaultHudTheme]]; + + [alert addButtonWithTitle:@"Destroy"]; + [alert addButtonWithTitle:@"Cancel"]; + + // We can use a block for the return handler if desired, or standard modal + [alert runModal]; +} + +- (void)showSheetAlert:(id)sender +{ + var alert = [[CPAlert alloc] init]; + [alert setMessageText:@"Document Warning"]; + [alert setInformativeText:@"This is a Sheet (CPDocModalWindowMask). It is attached to the parent window."]; + [alert setAlertStyle:CPWarningAlertStyle]; + + [alert addButtonWithTitle:@"Save"]; + [alert addButtonWithTitle:@"Cancel"]; + + // If the parent window is HUD, make the sheet match + if (_isHUD) + [alert setTheme:[CPTheme defaultHudTheme]]; + + // Using the delegate method pattern + [alert beginSheetModalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) + contextInfo:@"SheetContext"]; +} + +- (void)alertDidEnd:(CPAlert)anAlert returnCode:(CPInteger)returnCode contextInfo:(id)context +{ + CPLog.info(@"Alert ended. Return Code: %d. Context: %@", returnCode, context); +} + +// -------------------------------------------------------------------------------- + - (void)_buildTableTab:(CPView)containerView isHUD:(BOOL)isHUD { var bounds = [containerView bounds]; @@ -503,7 +581,7 @@ [textView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; [textView setEditable:YES]; [textView setRichText:YES]; - [textView setString:@"Select text here and use the 'Format' or 'Edit' menus.\n\n(This is a Rich Text enabled CPTextView)"]; + [textView setString:@"Enter an animal name such as 'Duck' in the Textfield above in order to filter the table dynamically.\nMove the columns around!\n\nBTW: this is a Rich Text enabled CPTextView"]; [textView setFont:[CPFont fontWithName:@"Courier" size:13.0]]; [textScroll setDocumentView:textView];