fixed: outlineview arrow colors

This commit is contained in:
daboe01
2026-01-25 17:19:07 +01:00
parent a531dc6fc0
commit ac1407484a
2 changed files with 162 additions and 23 deletions
+80 -19
View File
@@ -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
@@ -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 <Foundation/Foundation.j>
@@ -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];