diff --git a/AppKit/CPProgressIndicator.j b/AppKit/CPProgressIndicator.j index 61fb358d2..42fc06373 100644 --- a/AppKit/CPProgressIndicator.j +++ b/AppKit/CPProgressIndicator.j @@ -43,7 +43,6 @@ CPProgressIndicatorSpinningStyle = 1; */ CPProgressIndicatorHUDBarStyle = 2; -var CPProgressIndicatorSpinningStyleColors = []; /*! @ingroup appkit @@ -71,6 +70,35 @@ var CPProgressIndicatorSpinningStyleColors = []; BOOL _isDisplayedWhenStopped; } +// Inject CSS Keyframes for spinning animation (Standard + WebKit) ++ (void)initialize +{ + if (self !== [CPProgressIndicator class]) + return; + +#if PLATFORM(DOM) + + if (document.getElementById("cp-progress-indicator-style")) + return; + + var style = document.createElement("style"); + style.id = "cp-progress-indicator-style"; + style.type = "text/css"; + + var css = "@keyframes cp-progress-indicator-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } " + + "@-webkit-keyframes cp-progress-indicator-spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } }"; + + if (style.styleSheet) + style.styleSheet.cssText = css; + else + style.appendChild(document.createTextNode(css)); + + var head = document.getElementsByTagName("head")[0]; + if (head) + head.appendChild(style); +#endif +} + + (CPString)defaultThemeClass { return @"progress-indicator"; @@ -83,12 +111,19 @@ var CPProgressIndicatorSpinningStyleColors = []; @"bar-color": [CPNull null], @"default-height": 20, @"bezel-color": [CPNull null], - @"spinning-mini-gif": [CPNull null], - @"spinning-small-gif": [CPNull null], - @"spinning-regular-gif": [CPNull null], + + // CSS Spinner Attributes + @"spinner-color": [CPColor grayColor], + @"spinner-track-color": [CPColor colorWithWhite:0.9 alpha:1.0], + @"spinner-line-width": 3.0, + @"circular-border-color": [CPNull null], @"circular-border-size": 1, - @"circular-color": [CPNull null] + @"circular-color": [CPNull null], + + @"spinning-mini-gif": [CPNull null], + @"spinning-small-gif": [CPNull null], + @"spinning-regular-gif": [CPNull null] }; } @@ -138,6 +173,7 @@ var CPProgressIndicatorSpinningStyleColors = []; _isAnimating = YES; [self _hideOrDisplay]; + [self setNeedsLayout]; // Trigger layout to update CSS animation state } /*! @@ -149,6 +185,7 @@ var CPProgressIndicatorSpinningStyleColors = []; _isAnimating = NO; [self _hideOrDisplay]; + [self setNeedsLayout]; // Trigger layout to remove CSS animation state } /*! @@ -233,7 +270,7 @@ var CPProgressIndicatorSpinningStyleColors = []; _controlSize = aControlSize; - [self updateBackgroundColor]; + [self setNeedsLayout]; } /*! @@ -285,7 +322,7 @@ var CPProgressIndicatorSpinningStyleColors = []; _indeterminate = indeterminate; - [self updateBackgroundColor]; + [self setNeedsLayout]; } /*! @@ -307,9 +344,10 @@ var CPProgressIndicatorSpinningStyleColors = []; _style = aStyle; - [self setTheme:(_style === CPProgressIndicatorHUDBarStyle) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]]; + if (_style === CPProgressIndicatorSpinningStyle) + [self startAnimation:nil]; - [self updateBackgroundColor]; + [self setNeedsLayout]; } /*! @@ -318,7 +356,14 @@ var CPProgressIndicatorSpinningStyleColors = []; - (void)sizeToFit { if (_style == CPProgressIndicatorSpinningStyle) - [self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]]; + { + var size = 32.0; + if (_controlSize === CPMiniControlSize) size = 16.0; + else if (_controlSize === CPSmallControlSize) size = 24.0; + else if (_controlSize === CPRegularControlSize) size = 32.0; + + [self setFrameSize:CGSizeMake(size, size)]; + } else [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self valueForThemeAttribute:@"default-height"])]; } @@ -381,6 +426,7 @@ var CPProgressIndicatorSpinningStyleColors = []; - (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName { + // Handle the standard Bar View if (aViewName === @"bar-view" && _style !== CPProgressIndicatorSpinningStyle) { var width = CGRectGetWidth([self bounds]), @@ -394,41 +440,78 @@ var CPProgressIndicatorSpinningStyleColors = []; return CGRectMake(0, 0, barWidth, [self valueForThemeAttribute:@"default-height"]); } - - return nil; -} - -/* @ignore */ -- (void)updateBackgroundColor -{ - if ([CPProgressIndicatorSpinningStyleColors count] === 0) + + // Handle the Spinning View (CSS Spinner) + if (aViewName === @"spinner-view" && _style == CPProgressIndicatorSpinningStyle && _indeterminate) { - CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [self valueForThemeAttribute:@"spinning-mini-gif"]; - CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [self valueForThemeAttribute:@"spinning-small-gif"]; - CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [self valueForThemeAttribute:@"spinning-regular-gif"]; + return [self bounds]; } - [self setNeedsLayout]; + // Return nil for views that shouldn't appear in the current style + return nil; } - (void)layoutSubviews { if (YES)//_isBezeled) { + // === SPINNING STYLE === if (_style == CPProgressIndicatorSpinningStyle) { - if (!_indeterminate) - return; + // If indeterminate, use CSS spinner + if (_indeterminate) + { + var spinnerView = [self layoutEphemeralSubviewNamed:"spinner-view" + positioned:CPWindowAbove + relativeToEphemeralSubviewNamed:nil]; + + // Ensure other views are hidden + [self layoutEphemeralSubviewNamed:"bar-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil]; - // This will cause the bar view to go away due to having a nil rect when _style == CPProgressIndicatorSpinningStyle. - [self layoutEphemeralSubviewNamed:"bar-view" - positioned:CPWindowBelow - relativeToEphemeralSubviewNamed:nil]; + // Configure CSS on the subview, NOT self._DOMElement, to avoid transform conflicts + var domEl = spinnerView._DOMElement, + spinnerColor = [self currentValueForThemeAttribute:@"spinner-color"], + trackColor = [self currentValueForThemeAttribute:@"spinner-track-color"], + lineWidth = [self currentValueForThemeAttribute:@"spinner-line-width"], + widthStr = lineWidth + "px"; - [self setBackgroundColor:CPProgressIndicatorSpinningStyleColors[_controlSize]]; + domEl.style.boxSizing = "border-box"; + domEl.style.borderRadius = "50%"; + domEl.style.borderStyle = "solid"; + domEl.style.borderWidth = widthStr; + domEl.style.borderColor = [trackColor cssString]; + domEl.style.borderTopColor = [spinnerColor cssString]; + + // Animation logic + if (_isAnimating) + { + var anim = "cp-progress-indicator-spin 1s linear infinite"; + domEl.style.animation = anim; + domEl.style.WebkitAnimation = anim; + } + else + { + domEl.style.animation = "none"; + domEl.style.WebkitAnimation = "none"; + } + + // Ensure main view is transparent + [self setBackgroundColor:nil]; + } + else + { + // Determinate Spinner (Pie Chart drawn in drawRect) + // Hide CSS spinner + [self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil]; + [self setBackgroundColor:nil]; + } } + // === BAR STYLE === else { + // Hide spinner + [self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil]; + [self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]]; var barView = [self layoutEphemeralSubviewNamed:"bar-view" @@ -447,6 +530,8 @@ var CPProgressIndicatorSpinningStyleColors = []; - (void)drawRect:(CGRect)aRect { + // Handle determinate state for Spinning style (Pie chart progress) via CoreGraphics. + // If indeterminate, the CSS animation (spinner-view) takes over and we draw nothing. if (_style == CPProgressIndicatorSpinningStyle && !_indeterminate) { var context = [[CPGraphicsContext currentContext] graphicsPort], @@ -512,7 +597,10 @@ var CPProgressIndicatorSpinningStyleColors = []; _isDisplayedWhenStoppedSet = [aCoder decodeObjectForKey:@"_isDisplayedWhenStoppedSet"]; _isDisplayedWhenStopped = [aCoder decodeObjectForKey:@"_isDisplayedWhenStopped"]; - [self updateBackgroundColor]; + if (_style === CPProgressIndicatorSpinningStyle) + [self startAnimation:nil]; + + [self setNeedsLayout]; } return self; @@ -520,8 +608,6 @@ var CPProgressIndicatorSpinningStyleColors = []; - (void)encodeWithCoder:(CPCoder)aCoder { - // Don't encode the background colour. It can be recreated based on the flags - // and if encoded causes hardcoded image paths in the cib while just wasting space. var backgroundColor = [self backgroundColor]; [self setBackgroundColor:nil]; [super encodeWithCoder:aCoder]; diff --git a/AppKit/Themes/Aristo3/ThemeDescriptors.j b/AppKit/Themes/Aristo3/ThemeDescriptors.j index 3a981e867..e719a5aa0 100644 --- a/AppKit/Themes/Aristo3/ThemeDescriptors.j +++ b/AppKit/Themes/Aristo3/ThemeDescriptors.j @@ -6995,24 +6995,51 @@ var themedButtonValues = nil, + (CPProgressIndicator)themedBarProgressIndicator { - var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(0, 0, 75, 20)]; + var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(0, 0, 100, 20)]; [progressBar setDoubleValue:30]; - // Using CSS for progress bars + // --- Standard Colors --- var bezelColor = [CPColor colorWithCSSDictionary:@{ - @"border": @"1px solid " + A3ColorBorderDark, - @"background-color": A3ColorBackground - }], + @"border": @"1px solid " + A3ColorBorderDark, + @"background-color": A3ColorBackgroundWhite, + @"border-radius": @"3px", + @"box-sizing": @"border-box" + }], - barColor = [CPColor colorWithCSSDictionary:@{ - @"background-color": @"#5982DA" - }]; + barColor = [CPColor colorWithCSSDictionary:@{ + @"background-color": @"#5982DA", + @"border-radius": @"2px" + }], + + // --- HUD Colors --- + hudBezelColor = [CPColor colorWithCSSDictionary:@{ + @"background-color": @"rgba(0, 0, 0, 0.2)", + @"border": @"1px solid rgba(255, 255, 255, 0.2)", + @"border-radius": @"3px", + @"box-sizing": @"border-box", + @"box-shadow": @"inset 0 1px 2px rgba(0,0,0,0.1)" + }], + + hudBarColor = [CPColor colorWithCSSDictionary:@{ + @"background-color": @"rgba(255, 255, 255, 0.9)", + @"border-radius": @"2px", + @"box-shadow": @"0 0 4px rgba(255, 255, 255, 0.4)" + }]; themedProgressIndicator = [ - [@"bezel-color", bezelColor], - [@"bar-color", barColor], - [@"default-height", 20] + // Standard State + [@"bezel-color", bezelColor], + [@"bar-color", barColor], + + // HUD State + [@"bezel-color", hudBezelColor, CPThemeStateHUD], + [@"bar-color", hudBarColor, CPThemeStateHUD], + + // Control Sizes (Heights) + [@"default-height", 20.0], // Regular + [@"default-height", 16.0, CPThemeStateControlSizeSmall], + [@"default-height", 10.0, CPThemeStateControlSizeMini] ]; [self registerThemeValues:themedProgressIndicator forView:progressBar]; @@ -7022,24 +7049,54 @@ var themedButtonValues = nil, + (CPProgressIndicator)themedIndeterminateBarProgressIndicator { - var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(0, 0, 75, 20)]; - + var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(0, 0, 100, 20)]; [progressBar setIndeterminate:YES]; + // --- Standard Colors --- var bezelColor = [CPColor colorWithCSSDictionary:@{ - @"border": @"1px solid " + A3ColorBorderDark, - @"background-color": A3ColorBackground - }], + @"border": @"1px solid " + A3ColorBorderDark, + @"background-color": A3ColorBackgroundWhite, + @"border-radius": @"3px", + @"box-sizing": @"border-box" + }], - barColor = [CPColor colorWithCSSDictionary:@{ - @"background-color": @"#5982DA" - }]; + // Ideally this would be a striped CSS gradient animation, + // but for now we use the solid blue matching Aristo3 style + barColor = [CPColor colorWithCSSDictionary:@{ + @"background-color": @"#5982DA", + @"border-radius": @"2px", + @"opacity": @"0.8" + }], + + // --- HUD Colors --- + hudBezelColor = [CPColor colorWithCSSDictionary:@{ + @"background-color": @"rgba(0, 0, 0, 0.2)", + @"border": @"1px solid rgba(255, 255, 255, 0.2)", + @"border-radius": @"3px", + @"box-sizing": @"border-box", + @"box-shadow": @"inset 0 1px 2px rgba(0,0,0,0.1)" + }], + + hudBarColor = [CPColor colorWithCSSDictionary:@{ + @"background-color": @"rgba(255, 255, 255, 0.8)", + @"border-radius": @"2px", + @"box-shadow": @"0 0 4px rgba(255, 255, 255, 0.4)" + }]; themedIndeterminateProgressIndicator = [ - [@"bezel-color", bezelColor], - [@"indeterminate-bar-color", barColor], - [@"default-height", 20] + // Standard State + [@"bezel-color", bezelColor], + [@"indeterminate-bar-color", barColor], + + // HUD State + [@"bezel-color", hudBezelColor, CPThemeStateHUD], + [@"indeterminate-bar-color", hudBarColor, CPThemeStateHUD], + + // Control Sizes + [@"default-height", 20.0], // Regular + [@"default-height", 16.0, CPThemeStateControlSizeSmall], + [@"default-height", 10.0, CPThemeStateControlSizeMini] ]; [self registerThemeValues:themedIndeterminateProgressIndicator forView:progressBar]; @@ -7049,20 +7106,33 @@ var themedButtonValues = nil, + (CPProgressIndicator)themedSpinningProgressIndicator { - var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(0, 0, 64, 64)]; + var progressBar = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; [progressBar setStyle:CPProgressIndicatorSpinningStyle]; + [progressBar setIndeterminate:YES]; + [progressBar startAnimation:nil]; - // Keep GIFs for complex spinning animation as CSS/SVG spinning requires code support in CPProgressIndicator - // Assuming Aristo3Colors doesn't define these, we reference local resources - var spinningMini = PatternColor(@"progress-indicator-spinning-style-mini.gif", 16.0, 16.0), - spinningSmall = PatternColor(@"progress-indicator-spinning-style-small.gif", 32.0, 32.0), - spinningRegular = PatternColor(@"progress-indicator-spinning-style-regular.gif", 64.0, 64.0), + // Standard Colors (Grey) + var spinnerColor = [CPColor colorWithHexString:@"888888"], + spinnerTrackColor = [CPColor colorWithHexString:@"e6e6e6"], + + // HUD Colors (White/Transparent) + hudSpinnerColor = [CPColor whiteColor], + hudSpinnerTrackColor = [CPColor colorWithWhite:1.0 alpha:0.2]; - themeValues = + var themeValues = [ - [@"spinning-mini-gif", spinningMini], - [@"spinning-small-gif", spinningSmall], - [@"spinning-regular-gif", spinningRegular] + // --- Standard Theme --- + [@"spinner-color", spinnerColor], + [@"spinner-track-color", spinnerTrackColor], + + // Line widths based on size + [@"spinner-line-width", 4.0], // Regular + [@"spinner-line-width", 3.0, CPThemeStateControlSizeSmall], // Small + [@"spinner-line-width", 2.0, CPThemeStateControlSizeMini], // Mini + + // --- HUD Theme --- + [@"spinner-color", hudSpinnerColor, CPThemeStateHUD], + [@"spinner-track-color", hudSpinnerTrackColor, CPThemeStateHUD] ]; [self registerThemeValues:themeValues forView:progressBar]; @@ -7333,11 +7403,11 @@ var themedButtonValues = nil, var standardWindowView = [[_CPStandardWindowView alloc] initWithFrame:CGRectMake(0, 0, 200, 200) styleMask:CPClosableWindowMask], bezelHeadCssColor = [CPColor colorWithCSSDictionary:@{ - @"background-color": A3ColorWindowHeadActive, + @"background-color": A3ColorWindowHeadActive, @"border-top-color": A3ColorWindowBorder, @"border-top-style": @"solid", @"border-top-width": @"1px", - @"border-left-color": A3ColorWindowBorder, + @"border-left-color": A3ColorWindowBorder, @"border-left-style": @"solid", @"border-left-width": @"1px", @"border-right-color": A3ColorWindowBorder, @@ -7351,11 +7421,11 @@ var themedButtonValues = nil, }], inactiveBezelHeadCssColor = [CPColor colorWithCSSDictionary:@{ - @"background-color": A3ColorWindowHeadInactive, - @"border-top-color": A3ColorWindowBorder, + @"background-color": A3ColorWindowHeadInactive, + @"border-top-color": A3ColorWindowBorder, @"border-top-style": @"solid", @"border-top-width": @"1px", - @"border-left-color": A3ColorWindowBorder, + @"border-left-color": A3ColorWindowBorder, @"border-left-style": @"solid", @"border-left-width": @"1px", @"border-right-color": A3ColorWindowBorder, @@ -7374,7 +7444,7 @@ var themedButtonValues = nil, bezelCssColor = [CPColor colorWithCSSDictionary:@{ @"background-color": A3ColorBackground, - @"border-color": A3ColorWindowBorder, + @"border-color": A3ColorWindowBorder, @"border-style": @"solid", @"border-width": @"1px", @"border-top-left-radius": @"0px", @@ -7388,30 +7458,63 @@ var themedButtonValues = nil, @"background-color": A3ColorBorderMedium }], - // Pure CSS Close button + // --- BUTTON DEFINITIONS (Normal vs Highlighted/Pressed) --- + // Note: We include the 'transition' property so the color change animates when clicked. + + // 1. Close Button (Red) closeButtonImage = [CPImage imageWithCSSDictionary:@{ @"background-color": A3ColorWindowButtonClose, @"border-radius": @"50%", @"width": @"12px", - @"height": @"12px" + @"height": @"12px", + @"transition": @"background-color 0.15s ease-in-out" } size:CGSizeMake(12,12)], - // Pure CSS Minimize button + closeButtonImageHighlighted = [CPImage imageWithCSSDictionary:@{ + @"background-color": A3ColorWindowButtonCloseDark, // Darker Red + @"border-radius": @"50%", + @"width": @"12px", + @"height": @"12px", + @"transition": @"background-color 0.15s ease-in-out" + } + size:CGSizeMake(12,12)], + + // 2. Minimize Button (Yellow) minimizeButtonImage = [CPImage imageWithCSSDictionary:@{ @"background-color": A3ColorWindowButtonMin, @"border-radius": @"50%", @"width": @"12px", - @"height": @"12px" + @"height": @"12px", + @"transition": @"background-color 0.15s ease-in-out" } size:CGSizeMake(12,12)], - // Pure CSS Zoom button + minimizeButtonImageHighlighted = [CPImage imageWithCSSDictionary:@{ + @"background-color": A3ColorWindowButtonMinDark, // Darker Yellow + @"border-radius": @"50%", + @"width": @"12px", + @"height": @"12px", + @"transition": @"background-color 0.15s ease-in-out" + } + size:CGSizeMake(12,12)], + + // 3. Zoom Button (Green) zoomButtonImage = [CPImage imageWithCSSDictionary:@{ @"background-color": A3ColorWindowButtonZoom, @"border-radius": @"50%", @"width": @"12px", - @"height": @"12px" + @"height": @"12px", + @"transition": @"background-color 0.15s ease-in-out" + } + size:CGSizeMake(12,12)], + + zoomButtonImageHighlighted = [CPImage imageWithCSSDictionary:@{ + @"background-color": A3ColorWindowButtonZoomDark, // Darker Green + @"border-radius": @"50%", + @"width": @"12px", + @"height": @"12px", + @"transition": @"background-color 0.15s ease-in-out" } size:CGSizeMake(12,12)], @@ -7427,7 +7530,7 @@ var themedButtonValues = nil, [@"bezel-head-color", inactiveBezelHeadCssColor, CPThemeStateNormal], [@"bezel-head-color", bezelHeadCssColor, CPThemeStateKeyWindow], [@"bezel-head-color", bezelHeadCssColor, CPThemeStateMainWindow], - [@"bezel-head-sheet-color", [CPColor redColor]], + [@"bezel-head-sheet-color", [CPColor redColor]], [@"solid-color", solidCssColor], [@"title-font", [CPFont systemFontOfSize:CPFontCurrentSystemSize+1]], @@ -7446,10 +7549,16 @@ var themedButtonValues = nil, [@"title-margin", 4], [@"frame-outset", CGInsetMake(1, 1, 1, 1)], + // --- REGISTER NORMAL STATES --- [@"close-image-button", closeButtonImage], [@"minimize-image-button", minimizeButtonImage], [@"zoom-image-button", zoomButtonImage], + // --- REGISTER PRESSED STATES (Using CPThemeStateHighlighted) --- + [@"close-image-button", closeButtonImageHighlighted, CPThemeStateHighlighted], + [@"minimize-image-button", minimizeButtonImageHighlighted, CPThemeStateHighlighted], + [@"zoom-image-button", zoomButtonImageHighlighted, CPThemeStateHighlighted], + [@"close-image-size", CGSizeMake(12.0, 12.0)], [@"close-image-origin", CGPointMake(10.0, 10.0)], [@"minimize-image-size", CGSizeMake(12.0, 12.0)],