diff --git a/baRSS/AppHook.m b/baRSS/AppHook.m index 2cd8cdf..7b7168c 100644 --- a/baRSS/AppHook.m +++ b/baRSS/AppHook.m @@ -24,6 +24,7 @@ #import "BarStatusItem.h" #import "FeedDownload.h" #import "Preferences.h" +#import "DrawImage.h" @interface AppHook() @property (strong) NSWindowController *prefWindow; @@ -45,6 +46,7 @@ } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + RegisterImageViewNames(); // feed://https://feeds.feedburner.com/simpledesktops [FeedDownload registerNetworkChangeNotification]; // will call update scheduler } diff --git a/baRSS/Core Data/Feed+Ext.m b/baRSS/Core Data/Feed+Ext.m index 5577de3..be6ba67 100644 --- a/baRSS/Core Data/Feed+Ext.m +++ b/baRSS/Core Data/Feed+Ext.m @@ -228,7 +228,7 @@ } else if (self.icon.icon) { img = [[NSImage alloc] initWithData:self.icon.icon]; } else { - return [RSSIcon iconWithSize:16]; // TODO: setup imageNamed: for default rss icon? + img = [NSImage imageNamed:RSSImageDefaultRSSIcon]; } [img setSize:NSMakeSize(16, 16)]; return img; diff --git a/baRSS/Helper/DrawImage.h b/baRSS/Helper/DrawImage.h index 75e9987..83351e1 100644 --- a/baRSS/Helper/DrawImage.h +++ b/baRSS/Helper/DrawImage.h @@ -29,67 +29,18 @@ + (NSColor*)rssOrange; @end -// --------------------------------------------------------------- -// | -// | DrawImage -// | -// --------------------------------------------------------------- - -IB_DESIGNABLE -@interface DrawImage : NSView -@property (strong) IBInspectable NSColor *color; -@property (assign) IBInspectable BOOL showBackground; -/** percentage value between 0 - 100 */ -@property (assign, nonatomic) IBInspectable CGFloat roundness; -@property (assign, nonatomic) IBInspectable CGFloat contentScale; -@property (strong, readonly) NSImageView *imageView; - -- (NSImage*)drawnImage; -@end - -// --------------------------------------------------------------- -// | -// | RSSIcon -// | -// --------------------------------------------------------------- - -IB_DESIGNABLE -@interface RSSIcon : DrawImage -@property (strong) IBInspectable NSColor *barsColor; -@property (strong) IBInspectable NSColor *gradientColor; -@property (assign) IBInspectable BOOL noConnection; - -+ (NSImage*)iconWithSize:(CGFloat)size; -+ (NSImage*)systemBarIcon:(CGFloat)size tint:(NSColor*)color noConnection:(BOOL)conn; -@end - -// --------------------------------------------------------------- -// | -// | SettingsIconGlobal -// | -// --------------------------------------------------------------- - -IB_DESIGNABLE -@interface SettingsIconGlobal : DrawImage -@end - -// --------------------------------------------------------------- -// | -// | SettingsIconGroup -// | -// --------------------------------------------------------------- - -IB_DESIGNABLE -@interface SettingsIconGroup : DrawImage -@end - -// --------------------------------------------------------------- -// | -// | DrawSeparator -// | -// --------------------------------------------------------------- +/// Draw separator line in @c NSOutlineView IB_DESIGNABLE @interface DrawSeparator : NSView @end + +static NSImageName const RSSImageSettingsGlobal = @"RSSImageSettingsGlobal"; +static NSImageName const RSSImageSettingsGroup = @"RSSImageSettingsGroup"; +static NSImageName const RSSImageSettingsFeed = @"RSSImageSettingsFeed"; +static NSImageName const RSSImageDefaultRSSIcon = @"RSSImageDefaultRSSIcon"; +static NSImageName const RSSImageMenuBarIconActive = @"RSSImageMenuBarIconActive"; +static NSImageName const RSSImageMenuBarIconPaused = @"RSSImageMenuBarIconPaused"; + +void RegisterImageViewNames(void); diff --git a/baRSS/Helper/DrawImage.m b/baRSS/Helper/DrawImage.m index 4052092..b22b9c9 100644 --- a/baRSS/Helper/DrawImage.m +++ b/baRSS/Helper/DrawImage.m @@ -32,209 +32,169 @@ } /// @return Orange color that is typically used for RSS + (NSColor*)rssOrange { - return [NSColor colorWithCalibratedRed:0.984 green:0.639 blue:0.227 alpha:1.0]; + return [NSColor colorWithCalibratedRed:251/255.0 green:163/255.0 blue:58/255.0 alpha:1.0]; } @end -// ################################################################ -// # -// # DrawImage -// # -// ################################################################ - -@implementation DrawImage -@synthesize roundness = _roundness, contentScale = _contentScale; - --(id)init{self=[super init];if(self)[self initialize];return self;} --(id)initWithFrame:(CGRect)f{self=[super initWithFrame:f];if(self)[self initialize];return self;} --(id)initWithCoder:(NSCoder*)c{self=[super initWithCoder:c];if(self)[self initialize];return self;} - -//#if !TARGET_INTERFACE_BUILDER #endif -/// Prepare view content to autoresize when rescaling -- (void)initialize { - _contentScale = 1.0; - _imageView = [NSImageView imageViewWithImage:[self drawnImage]]; - [_imageView setFrameSize:self.frame.size]; - _imageView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - [self addSubview:_imageView]; -} - -/** - Designated initializer. Will add rounded corners and background color. - - @param w Square size of icon. - @param s Scaling factor of the content image. - */ -- (instancetype)initWithSize:(CGFloat)w scale:(CGFloat)s { - self = [super initWithFrame:NSMakeRect(0, 0, w, w)]; - self.roundness = 40; - self.contentScale = s; - self.showBackground = YES; - return self; -} - -/** - @return New image with drawn content. Will call @c drawImageInRect: - */ -- (NSImage*)drawnImage { - return [NSImage imageWithSize:self.frame.size flipped:NO drawingHandler:^BOOL(NSRect rect) { - [self drawImageInRect:rect]; - return YES; - }]; -} - -/// Set roundness factor for rounded corners (background). This setter ensures a percent value between 0 and 1. -- (void)setRoundness:(CGFloat)r { - _roundness = 0.5 * (r < 0 ? 0 : r > 100 ? 100 : r); -} - -/// @return MIN( width, height ) -- (CGFloat)shorterSide { - if (self.frame.size.width < self.frame.size.height) - return self.frame.size.width; - return self.frame.size.height; -} - -/** - Draw background image, rounded corners and scaled image content - */ -- (void)drawImageInRect:(NSRect)r { - const CGFloat s = [self shorterSide]; - CGContextRef c = [[NSGraphicsContext currentContext] CGContext]; - - if (_showBackground) { - CGMutablePathRef pth = CGPathCreateMutable(); - const CGFloat corner = s * (_roundness / 100.0); - if (corner > 0) { - CGPathAddRoundedRect(pth, NULL, r, corner, corner); - } else { - CGPathAddRect(pth, NULL, r); - } - CGContextSetFillColorWithColor(c, [_color CGColor]); - CGContextAddPath(c, pth); - CGPathRelease(pth); - if ([self isMemberOfClass:[DrawImage class]]) - CGContextFillPath(c); // fill only if not a subclass - } - if (_contentScale != 1.0) { - CGFloat offset = s * (1 - _contentScale) / 2; - CGContextTranslateCTM(c, offset, offset); - CGContextScaleCTM(c, _contentScale, _contentScale); - } +@implementation DrawSeparator +- (void)drawRect:(NSRect)r { + NSColor *color = [NSColor darkGrayColor]; + NSGradient *grdnt = [[NSGradient alloc] initWithStartingColor:color endingColor:[color colorWithAlphaComponent:0.0]]; + NSRect separatorRect = NSMakeRect(1, NSMidY(self.frame) - 1, NSWidth(self.frame) - 2, 2); + NSBezierPath *rounded = [NSBezierPath bezierPathWithRoundedRect:separatorRect xRadius:1 yRadius:1]; + [grdnt drawInBezierPath:rounded angle:0]; } @end -// ################################################################ -// # -// # RSSIcon -// # -// ################################################################ +#pragma mark - Helper Methods -@implementation RSSIcon // content scale 0.75 works fine -/** - @return Default RSS icon for feeds that are missing an icon. (Not used in system bar). - */ -+ (NSImage*)iconWithSize:(CGFloat)s { - RSSIcon *icon = [[RSSIcon alloc] initWithSize:s scale:0.7]; - icon.barsColor = [NSColor whiteColor]; - icon.gradientColor = [NSColor rssOrange]; - return [icon drawnImage]; +/// @return @c MIN(s.width,s.height) +NS_INLINE const CGFloat ShorterSide(NSSize s) { + return (s.width < s.height ? s.width : s.height); } -/** - Returns new @c NSImage with background (tinted or not) and connection error (if set). +/// Perform @c CGAffineTransform with custom rotation point +// CGAffineTransform RotateAroundPoint(CGAffineTransform at, CGFloat angle, CGFloat x, CGFloat y) { +// at = CGAffineTransformTranslate(at, x, y); +// at = CGAffineTransformRotate(at, angle); +// return CGAffineTransformTranslate(at, -x, -y); +//} - @param s Square image size - @param color Tint color of icon. Either untintend (white) or highlighted (rss orange). - @param conn If @c YES show small pause icon in right upper corner. - */ -+ (NSImage*)systemBarIcon:(CGFloat)s tint:(NSColor*)color noConnection:(BOOL)conn { - RSSIcon *icon = [[RSSIcon alloc] initWithSize:s scale:0.7]; - icon.color = (color ? color : [NSColor blackColor]); - icon.noConnection = conn; -// icon.showBackground = !conn; -// icon.contentScale = (conn ? 0.9 : 0.7); - return [icon drawnImage]; + +#pragma mark - CGPath Component Generators + + +/// Add circle with @c radius +NS_INLINE void PathAddCircle(CGMutablePathRef path, CGFloat radius) { + CGPathAddArc(path, NULL, radius, radius, radius, 0, M_PI * 2, YES); } +/// Add a single RSS icon radio wave +NS_INLINE void PathAddRSSArc(CGMutablePathRef path, CGFloat radius, CGFloat thickness) { + CGPathMoveToPoint(path, NULL, 0, radius + thickness); + CGPathAddArc(path, NULL, 0, 0, radius + thickness, M_PI_2, 0, YES); + CGPathAddLineToPoint(path, NULL, radius, 0); + CGPathAddArc(path, NULL, 0, 0, radius, 0, M_PI_2, NO); + CGPathCloseSubpath(path); +} + +/// Add two vertical bars representing a pause icon +NS_INLINE void PathAddPauseIcon(CGMutablePathRef path, CGAffineTransform at, CGFloat size, CGFloat thickness) { + const CGFloat off = (size - 2 * thickness) / 4; + CGPathAddRect(path, &at, CGRectMake(off, 0, thickness, size)); + CGPathAddRect(path, &at, CGRectMake(size/2 + off, 0, thickness, size)); +} + +/// Add X icon by applying a rotational affine transform and drawing a plus sign +// void PathAddXIcon(CGMutablePathRef path, CGAffineTransform at, CGFloat size, CGFloat thickness) { +// at = RotateAroundPoint(at, M_PI_4, size/2, size/2); +// const CGFloat p = size * 0.5 - thickness / 2; +// CGPathAddRect(path, &at, CGRectMake(0, p, size, thickness)); +// CGPathAddRect(path, &at, CGRectMake(p, 0, thickness, p)); +// CGPathAddRect(path, &at, CGRectMake(p, p + thickness, thickness, p)); +//} + + +#pragma mark - Full Icon Path Generators + + +/// Create @c CGPath for global icon; a menu bar and an open menu below +NS_INLINE void AddGlobalIconPath(CGContextRef c, CGFloat size) { + CGMutablePathRef menu = CGPathCreateMutable(); + CGPathAddRect(menu, NULL, CGRectMake(0, 0.8 * size, size, 0.2 * size)); + CGPathAddRect(menu, NULL, CGRectMake(0.3 * size, 0, 0.55 * size, 0.75 * size)); + CGPathAddRect(menu, NULL, CGRectMake(0.35 * size, 0.05 * size, 0.45 * size, 0.75 * size)); + + CGFloat entryHeight = 0.1 * size; // 0.075 + for (int i = 0; i < 3; i++) { // 4 + //CGPathAddRect(menu, NULL, CGRectMake(0.37 * size, (2 * i + 1) * entryHeight, 0.42 * size, entryHeight)); // uncomment path above + CGPathAddRect(menu, NULL, CGRectMake(0.35 * size, (2 * i + 1.5) * entryHeight, 0.4 * size, entryHeight * 0.8)); + } + CGContextAddPath(c, menu); + CGPathRelease(menu); +} + +/// Create @c CGPath for group icon; a folder symbol +NS_INLINE void AddGroupIconPath(CGContextRef c, CGFloat size, BOOL showBackground) { + const CGFloat r1 = size * 0.05; // corners + const CGFloat r2 = size * 0.08; // upper part, name tag + const CGFloat r3 = size * 0.15; // lower part, corners inside + const CGFloat posTop = 0.85 * size; + const CGFloat posMiddle = 0.6 * size - r3; + const CGFloat posBottom = 0.15 * size + r1; + const CGFloat posNameTag = 0.3 * size; + + CGMutablePathRef upper = CGPathCreateMutable(); + CGPathMoveToPoint(upper, NULL, 0, 0.5 * size); + CGPathAddLineToPoint(upper, NULL, 0, posTop - r1); + CGPathAddArc(upper, NULL, r1, posTop - r1, r1, M_PI, M_PI_2, YES); + CGPathAddArc(upper, NULL, posNameTag, posTop - r2, r2, M_PI_2, M_PI_4, YES); + CGPathAddArc(upper, NULL, posNameTag + 1.85 * r2, posTop, r2, M_PI + M_PI_4, -M_PI_2, NO); + CGPathAddArc(upper, NULL, size - r1, posTop - r1 - r2, r1, M_PI_2, 0, YES); + CGPathAddArc(upper, NULL, size - r1, posBottom, r1, 0, -M_PI_2, YES); + CGPathAddArc(upper, NULL, r1, posBottom, r1, -M_PI_2, M_PI, YES); + CGPathCloseSubpath(upper); + + CGMutablePathRef lower = CGPathCreateMutable(); + CGPathAddArc(lower, NULL, r3, posMiddle, r3, M_PI, M_PI_2, YES); + CGPathAddArc(lower, NULL, size - r3, posMiddle, r3, M_PI_2, 0, YES); + CGPathAddArc(lower, NULL, size - r1, posBottom, r1, 0, -M_PI_2, YES); + CGPathAddArc(lower, NULL, r1, posBottom, r1, -M_PI_2, M_PI, YES); + CGPathCloseSubpath(lower); + + CGContextAddPath(c, upper); + if (showBackground) + CGContextEOFillPath(c); + CGContextAddPath(c, lower); + CGPathRelease(upper); + CGPathRelease(lower); +} + + /** - Draw two rss bars (or paused icon) and tint color or gradient color. - */ -- (void)drawImageInRect:(NSRect)r { - [super drawImageInRect:r]; - - const CGFloat s = [self shorterSide]; - CGContextRef c = [[NSGraphicsContext currentContext] CGContext]; - CGContextSetFillColorWithColor(c, [self.color CGColor]); - +NS_INLINE Create @c CGPath for RSS icon; a circle in the lower left bottom and two radio waves going outwards. +NS_INLINE @param connection If @c NO, draw only one radio wave and a pause icon in the upper right +NS_INLINE */ +NS_INLINE void AddRSSIconPath(CGContextRef c, CGFloat size, BOOL connection) { CGMutablePathRef bars = CGPathCreateMutable(); // the rss bars - // circle - const CGFloat r1 = s * 0.125; // circle radius - CGPathAddArc(bars, NULL, r1, r1, r1, 0, M_PI * 2, YES); - // 1st bar - CGPathMoveToPoint(bars, NULL, 0, s * 0.65); - CGPathAddArc(bars, NULL, 0, 0, s * 0.65, M_PI_2, 0, YES); - CGPathAddLineToPoint(bars, NULL, s * 0.45, 0); - CGPathAddArc(bars, NULL, 0, 0, s * 0.45, 0, M_PI_2, NO); - CGPathCloseSubpath(bars); - // 2nd bar - if (_noConnection) { - CGAffineTransform at = CGAffineTransformMake(0.5, 0, 0, 0.5, s * 0.5, s * 0.5); - // X icon -// CGPathMoveToPoint(bars, &at, 0, s * 0.2); -// CGPathAddLineToPoint(bars, &at, s * 0.3, s * 0.5); -// CGPathAddLineToPoint(bars, &at, 0, s * 0.8); -// CGPathAddLineToPoint(bars, &at, s * 0.2, s); -// CGPathAddLineToPoint(bars, &at, s * 0.5, s * 0.7); -// CGPathAddLineToPoint(bars, &at, s * 0.8, s); -// CGPathAddLineToPoint(bars, &at, s, s * 0.8); -// CGPathAddLineToPoint(bars, &at, s * 0.7, s * 0.5); -// CGPathAddLineToPoint(bars, &at, s, s * 0.2); -// CGPathAddLineToPoint(bars, &at, s * 0.8, 0); -// CGPathAddLineToPoint(bars, &at, s * 0.5, s * 0.3); -// CGPathAddLineToPoint(bars, &at, s * 0.2, 0); -// CGPathCloseSubpath(bars); - // Pause icon -// CGPathMoveToPoint(bars, &at, s * 0.2, s * 0.2); - CGPathAddRect(bars, &at, CGRectMake(s*0.1, 0, s*0.3, s)); - CGPathAddRect(bars, &at, CGRectMake(s*0.6, 0, s*0.3, s)); + PathAddCircle(bars, size * 0.125); + PathAddRSSArc(bars, size * 0.45, size * 0.2); + if (connection) { + PathAddRSSArc(bars, size * 0.8, size * 0.2); } else { - CGPathMoveToPoint(bars, NULL, 0, s); - CGPathAddArc(bars, NULL, 0, 0, s, M_PI_2, 0, YES); - CGPathAddLineToPoint(bars, NULL, s * 0.8, 0); - CGPathAddArc(bars, NULL, 0, 0, s * 0.8, 0, M_PI_2, NO); - CGPathCloseSubpath(bars); + CGAffineTransform at = CGAffineTransformMake(0.5, 0, 0, 0.5, size/2, size/2); + PathAddPauseIcon(bars, at, size, size * 0.3); + //PathAddXIcon(bars, at, size, size * 0.3); } - CGContextAddPath(c, bars); - - if (_gradientColor) { - CGContextSaveGState(c); - CGContextClip(c); - [self drawGradient:c side:s / self.contentScale]; - CGContextRestoreGState(c); - } else { - CGContextEOFillPath(c); - } - - if (_barsColor) { - CGContextSetFillColorWithColor(c, [_barsColor CGColor]); - CGContextAddPath(c, bars); - CGContextEOFillPath(c); - } CGPathRelease(bars); } -/** - Apply gradient to current context clipping. - */ -- (void)drawGradient:(CGContextRef)c side:(CGFloat)w { + +#pragma mark - Icon Background Generators + + +/// Create @c CGPath with rounded corners (optional). @param roundness Value between @c 0.0 and @c 1.0 +NS_INLINE void AddRoundedBackgroundPath(CGContextRef c, CGRect r, CGFloat roundness) { + const CGFloat corner = ShorterSide(r.size) * (roundness / 2.0); + if (corner > 0) { + CGMutablePathRef pth = CGPathCreateMutable(); + CGPathAddRoundedRect(pth, NULL, r, corner, corner); + CGContextAddPath(c, pth); + CGPathRelease(pth); + } else { + CGContextAddRect(c, r); + } +} + +/// Insert and draw linear gradient with @c color saturation @c ±0.3 +NS_INLINE void DrawGradient(CGContextRef c, CGFloat size, NSColor *color) { CGFloat h = 0, s = 1, b = 1, a = 1; @try { - NSColor *rgbColor = [_gradientColor colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + NSColor *rgbColor = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; [rgbColor getHue:&h saturation:&s brightness:&b alpha:&a]; } @catch (NSException *e) {} @@ -249,128 +209,100 @@ CFArrayRef colors = CFArrayCreate(NULL, cgColors, 3, NULL); CGGradientRef gradient = CGGradientCreateWithColors(NULL, colors, NULL); - CGContextDrawLinearGradient(c, gradient, CGPointMake(0, w), CGPointMake(w, 0), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CGContextDrawLinearGradient(c, gradient, CGPointMake(0, size), CGPointMake(size, 0), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CGGradientRelease(gradient); CFRelease(colors); } -@end -// ################################################################ -// # -// # SettingsIconGlobal -// # -// ################################################################ +#pragma mark - CGContext Drawing & Manipulation -@implementation SettingsIconGlobal // content scale 0.7 works fine -/** - Draw icon for preferences; showing the status bar and an open menu. (single colors contour) - */ -- (void)drawImageInRect:(NSRect)r { - [super drawImageInRect:r]; // add path of rounded rect - - const CGFloat w = r.size.width; - const CGFloat h = r.size.height; - - CGMutablePathRef menu = CGPathCreateMutable(); - CGPathAddRect(menu, NULL, CGRectMake(0, 0.8 * h, w, 0.2 * h)); - CGPathAddRect(menu, NULL, CGRectMake(0.3 * w, 0, 0.55 * w, 0.75 * h)); - CGPathAddRect(menu, NULL, CGRectMake(0.35 * w, 0.05 * h, 0.45 * w, 0.75 * h)); - - CGFloat entryHeight = 0.1 * h; // 0.075 - for (int i = 0; i < 3; i++) { // 4 - //CGPathAddRect(menu, NULL, CGRectMake(0.37 * w, (2 * i + 1) * entryHeight, 0.42 * w, entryHeight)); // uncomment path above - CGPathAddRect(menu, NULL, CGRectMake(0.35 * w, (2 * i + 1.5) * entryHeight, 0.4 * w, entryHeight * 0.8)); + +/// Scale and translate context to the center with respect to the new scale. If @c width @c != @c length align top left. +NS_INLINE void SetContentScale(CGContextRef c, CGSize size, CGFloat scale) { + const CGFloat s = ShorterSide(size); + CGFloat offset = s * (1 - scale) / 2; + CGContextTranslateCTM(c, offset, size.height - s + offset); // top left alignment + CGContextScaleCTM(c, scale, scale); +} + +/// Helper method; set drawing color, add rounded background and prepare content scale +NS_INLINE void DrawRoundedFrame(CGContextRef c, CGRect r, CGColorRef color, BOOL background, CGFloat corner, CGFloat defaultScale, CGFloat scaling) { + CGContextSetFillColorWithColor(c, color); + CGContextSetStrokeColorWithColor(c, color); + CGFloat contentScale = defaultScale; + if (background) { + AddRoundedBackgroundPath(c, r, corner); + if (scaling != 0.0) + contentScale *= scaling; } - - CGContextRef c = [[NSGraphicsContext currentContext] CGContext]; - CGContextSetFillColorWithColor(c, [self.color CGColor]); - - CGContextAddPath(c, menu); + SetContentScale(c, r.size, contentScale); +} + + +#pragma mark - Easy Icon Drawing Methods + + +/// Draw global icon (menu bar) +NS_INLINE void DrawGlobalIcon(CGRect r, CGColorRef color, BOOL background) { + CGContextRef c = NSGraphicsContext.currentContext.CGContext; + DrawRoundedFrame(c, r, color, background, 0.4, 1.0, 0.7); + AddGlobalIconPath(c, ShorterSide(r.size)); CGContextEOFillPath(c); - CGPathRelease(menu); } -@end - -// ################################################################ -// # -// # SettingsIconGroup -// # -// ################################################################ - -@implementation SettingsIconGroup // content scale 0.8 works fine -/** - Draw icon for preferences; showing the mac typcial folder icon. (single colors contour) - */ -- (void)drawImageInRect:(NSRect)r { - [super drawImageInRect:r]; - - const CGFloat w = r.size.width; - const CGFloat h = r.size.height; - const CGFloat s = (w < h ? w : h); // shorter side - const CGFloat l = s * 0.04; // line width (half size) - const CGFloat r1 = s * 0.05; // corners - const CGFloat r2 = s * 0.08; // upper part, name tag - const CGFloat r3 = s * 0.15; // lower part, corners inside - const CGFloat posTop = 0.85 * h - l; - const CGFloat posMiddle = 0.6 * h - l - r3; - const CGFloat posBottom = 0.15 * h + l + r1; - const CGFloat posNameTag = 0.3 * w - l; - - CGMutablePathRef upper = CGPathCreateMutable(); - CGPathMoveToPoint(upper, NULL, l, 0.5 * h); - CGPathAddLineToPoint(upper, NULL, l, posTop - r1); - CGPathAddArc(upper, NULL, l + r1, posTop - r1, r1, M_PI, M_PI_2, YES); - CGPathAddArc(upper, NULL, posNameTag, posTop - r2, r2, M_PI_2, M_PI_4, YES); - CGPathAddArc(upper, NULL, posNameTag + 2 * r2, posTop, r2, M_PI + M_PI_4, -M_PI_2, NO); - CGPathAddArc(upper, NULL, w - l - r1, posTop - r1 - r2, r1, M_PI_2, 0, YES); - CGPathAddArc(upper, NULL, w - l - r1, posBottom, r1, 0, -M_PI_2, YES); - CGPathAddArc(upper, NULL, l + r1, posBottom, r1, -M_PI_2, M_PI, YES); - CGPathCloseSubpath(upper); - - CGMutablePathRef lower = CGPathCreateMutable(); - CGPathMoveToPoint(lower, NULL, l, 0.5 * h); - CGPathAddArc(lower, NULL, l + r3, posMiddle, r3, M_PI, M_PI_2, YES); - CGPathAddArc(lower, NULL, w - l - r3, posMiddle, r3, M_PI_2, 0, YES); - CGPathAddArc(lower, NULL, w - l - r1, posBottom, r1, 0, -M_PI_2, YES); - CGPathAddArc(lower, NULL, l + r1, posBottom, r1, -M_PI_2, M_PI, YES); - CGPathCloseSubpath(lower); - - CGContextRef c = [[NSGraphicsContext currentContext] CGContext]; - CGContextSetFillColorWithColor(c, [self.color CGColor]); - CGContextSetStrokeColorWithColor(c, [self.color CGColor]); - CGContextSetLineWidth(c, l * 2); - - CGContextAddPath(c, upper); - CGContextAddPath(c, lower); - if (self.showBackground) { - CGContextAddPath(c, lower); - CGContextEOFillPath(c); - CGContextSetLineWidth(c, l); // thinner line - CGContextAddPath(c, lower); - } +/// Draw group icon (folder) +NS_INLINE void DrawGroupIcon(CGRect r, CGColorRef color, BOOL background) { + CGContextRef c = NSGraphicsContext.currentContext.CGContext; + const CGFloat s = ShorterSide(r.size); + const CGFloat l = s * 0.08; // line width + DrawRoundedFrame(c, r, color, background, 0.4, 1.0 - (l / s), 0.85); + CGContextSetLineWidth(c, l * (background ? 0.5 : 1.0)); + AddGroupIconPath(c, s, background); CGContextStrokePath(c); - CGPathRelease(upper); - CGPathRelease(lower); } -@end - -// ################################################################ -// # -// # DrawSeparator -// # -// ################################################################ - -@implementation DrawSeparator -/** - Draw separator line in @c NSOutlineView - */ -- (void)drawRect:(NSRect)dirtyRect { - NSGradient *grdnt = [[NSGradient alloc] initWithStartingColor:[NSColor darkGrayColor] endingColor:[[NSColor darkGrayColor] colorWithAlphaComponent:0.0]]; - NSRect separatorRect = NSMakeRect(1, self.frame.size.height / 2.0 - 1, self.frame.size.width - 2, 2); - NSBezierPath *rounded = [NSBezierPath bezierPathWithRoundedRect:separatorRect xRadius:1 yRadius:1]; - [grdnt drawInBezierPath:rounded angle:0]; +/// Draw RSS icon (flat without gradient) +NS_INLINE void DrawRSSIcon(CGRect r, CGColorRef color, BOOL background, BOOL connection) { + CGContextRef c = NSGraphicsContext.currentContext.CGContext; + DrawRoundedFrame(c, r, color, background, 0.4, 1.0, 0.7); + AddRSSIconPath(c, ShorterSide(r.size), connection); + CGContextEOFillPath(c); +} + +/// Draw RSS icon (with orange gradient, corner @c 0.4, white radio waves) +NS_INLINE void DrawRSSGradientIcon(CGRect r) { + const CGFloat size = ShorterSide(r.size); + CGContextRef c = NSGraphicsContext.currentContext.CGContext; + DrawRoundedFrame(c, r, NSColor.whiteColor.CGColor, YES, 0.4, 1.0, 0.7); + // Gradient + CGContextSaveGState(c); + CGContextClip(c); + DrawGradient(c, size, [NSColor rssOrange]); + CGContextRestoreGState(c); + // Bars + AddRSSIconPath(c, size, YES); + CGContextEOFillPath(c); +} + + +#pragma mark - NSImage Name Registration + + +/// Add single image to @c ImageNamed cache and set accessibility description +NS_INLINE void Register(CGFloat size, NSImageName name, NSString *description, BOOL (^draw)(NSRect r)) { + NSImage *img = [NSImage imageWithSize: NSMakeSize(size, size) flipped:NO drawingHandler:draw]; + img.accessibilityDescription = description; + img.name = name; +} + +/// Register all icons that require custom drawing in @c ImageNamed cache +void RegisterImageViewNames(void) { + const CGColorRef black = [NSColor controlTextColor].CGColor; + Register(16, RSSImageSettingsGlobal, NSLocalizedString(@"Global settings", nil), ^(NSRect r) { DrawGlobalIcon(r, black, NO); return YES; }); + Register(16, RSSImageSettingsGroup, NSLocalizedString(@"Group settings", nil), ^(NSRect r) { DrawGroupIcon(r, black, NO); return YES; }); + Register(16, RSSImageSettingsFeed, NSLocalizedString(@"Feed settings", nil), ^(NSRect r) { DrawRSSIcon(r, black, NO, YES); return YES; }); + Register(16, RSSImageDefaultRSSIcon, NSLocalizedString(@"RSS icon", nil), ^(NSRect r) { DrawRSSGradientIcon(r); return YES; }); + Register(16, RSSImageMenuBarIconActive, NSLocalizedString(@"RSS menu bar icon", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor rssOrange].CGColor, YES, YES); return YES; }); + Register(16, RSSImageMenuBarIconPaused, NSLocalizedString(@"RSS menu bar icon, paused", nil), ^(NSRect r) { DrawRSSIcon(r, [NSColor rssOrange].CGColor, YES, NO); return YES; }); } -@end diff --git a/baRSS/Info.plist b/baRSS/Info.plist index bf2575a..9b2584d 100644 --- a/baRSS/Info.plist +++ b/baRSS/Info.plist @@ -32,7 +32,7 @@ CFBundleVersion - 7288 + 7539 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement diff --git a/baRSS/Preferences/Appearance Tab/SettingsAppearanceView.m b/baRSS/Preferences/Appearance Tab/SettingsAppearanceView.m index ee775b8..c0acd5e 100644 --- a/baRSS/Preferences/Appearance Tab/SettingsAppearanceView.m +++ b/baRSS/Preferences/Appearance Tab/SettingsAppearanceView.m @@ -40,9 +40,9 @@ self = [super initWithFrame: NSZeroRect]; self.row = 0; // Insert matrix header (the three icons) - [self head:0 tooltip:NSLocalizedString(@"Show in menu bar", nil) class:[SettingsIconGlobal class]]; - [self head:1 tooltip:NSLocalizedString(@"Show in group menu", nil) class:[SettingsIconGroup class]]; - [self head:2 tooltip:NSLocalizedString(@"Show in feed menu", nil) class:[RSSIcon class]]; + [self head:0 img:RSSImageSettingsGlobal tooltip:NSLocalizedString(@"Show in menu bar", nil)]; + [self head:1 img:RSSImageSettingsGroup tooltip:NSLocalizedString(@"Show in group menu", nil)]; + [self head:2 img:RSSImageSettingsFeed tooltip:NSLocalizedString(@"Show in feed menu", nil)]; // Generate checkbox matrix (checkbox state, X: default ON, O: default OFF, blank: hidden) [self entry:"X " label:NSLocalizedString(@"Tint menu bar icon on unread", nil)]; [self entry:"X " label:NSLocalizedString(@"Update all feeds", nil)]; @@ -57,8 +57,8 @@ } /// Helper method for matrix table header icons -- (void)head:(int)x tooltip:(NSString*)ttip class:(Class)cls { - [[[[cls alloc] initWithFrame:NSMakeRect(0, 0, IconSize, IconSize)] tooltip:ttip] placeIn:self x:PAD_WIN + x * colWidth yTop:PAD_WIN]; +- (void)head:(int)x img:(NSImageName)img tooltip:(NSString*)ttip { + [[[NSView imageView:img size:IconSize] tooltip:ttip] placeIn:self x:PAD_WIN + x * colWidth yTop:PAD_WIN]; } /// Create new entry with 1-3 checkboxes and a descriptive label diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeedsView.m b/baRSS/Preferences/Feeds Tab/SettingsFeedsView.m index 8755b9a..a88bdae 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeedsView.m +++ b/baRSS/Preferences/Feeds Tab/SettingsFeedsView.m @@ -199,7 +199,9 @@ NSUserInterfaceItemIdentifier const CustomCellName = @"NameColumnCell"; self = [super initWithFrame:frameRect]; self.identifier = CustomCellName; self.imageView = [[NSView imageView:nil size:16] placeIn:self x:1 yTop:1]; + self.imageView.accessibilityLabel = NSLocalizedString(@"Feed icon", nil); self.textField = [[[NSView label:@""] placeIn:self x:25 yTop:0] sizeToRight:0]; + self.textField.accessibilityLabel = NSLocalizedString(@"Feed title", nil); return self; } @@ -222,6 +224,7 @@ NSUserInterfaceItemIdentifier const CustomCellRefresh = @"RefreshColumnCell"; self = [super initWithFrame:frameRect]; self.identifier = CustomCellRefresh; self.textField = [[[[NSView label:@""] textRight] placeIn:self x:0 yTop:0] sizeToRight:0]; + self.textField.accessibilityLabel = NSLocalizedString(@"Refresh interval", nil); return self; } diff --git a/baRSS/Status Bar Menu/BarStatusItem.m b/baRSS/Status Bar Menu/BarStatusItem.m index 579bdef..3636111 100644 --- a/baRSS/Status Bar Menu/BarStatusItem.m +++ b/baRSS/Status Bar Menu/BarStatusItem.m @@ -117,18 +117,13 @@ /// Update menu bar icon and text according to unread count and user preferences. - (void)updateBarIcon { dispatch_async(dispatch_get_main_queue(), ^{ - if (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"globalUnreadCount"]) { - self.statusItem.title = [NSString stringWithFormat:@"%ld", self.unreadCountTotal]; - } else { - self.statusItem.title = @""; - } BOOL hasNet = [FeedDownload allowNetworkConnection]; - if (self.unreadCountTotal > 0 && hasNet && [UserPrefs defaultYES:@"globalTintMenuBarIcon"]) { - self.statusItem.image = [RSSIcon systemBarIcon:16 tint:[NSColor rssOrange] noConnection:!hasNet]; - } else { - self.statusItem.image = [RSSIcon systemBarIcon:16 tint:nil noConnection:!hasNet]; - self.statusItem.image.template = YES; - } + BOOL tint = (self.unreadCountTotal > 0 && hasNet && [UserPrefs defaultYES:@"globalTintMenuBarIcon"]); + self.statusItem.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)]; + self.statusItem.image.template = !tint; + + BOOL showCount = (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"globalUnreadCount"]); + self.statusItem.title = (showCount ? [NSString stringWithFormat:@"%ld", self.unreadCountTotal] : @""); }); }