Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ set(BRO_SRCS
src/bro_closed_tabs.mm
src/bro_downloads.mm
src/bro_favicon.mm
src/bro_geometry.h
src/bro_handler.cc
src/bro_handler.h
src/bro_handler_mac.mm
Expand All @@ -72,8 +73,10 @@ set(BRO_SRCS
src/bro_motion.mm
src/bro_persist.h
src/bro_persist.mm
src/bro_tabsearch.mm
src/bro_palette.mm
src/bro_tabstrip.mm
src/bro_text_morph.h
src/bro_text_morph.mm
src/bro_toolbar.mm
src/bro_updater.h
src/bro_updater.mm
Expand Down
116 changes: 90 additions & 26 deletions src/bro_downloads.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "bro_handler.h"
#import "bro_mac_internal.h"
#import "bro_motion.h"
#import "radix_icons.h"

#pragma mark - BroDownloadEntry
Expand Down Expand Up @@ -52,6 +53,46 @@ @implementation BroDownloadEntry
static const CGFloat kDownloadsFooterHeight = 36.0;
static const CGFloat kDownloadsEmptyHeight = 52.0;

@interface BroDownloadRow : NSView
@property(nonatomic, assign) BOOL interactive;
@property(nonatomic, weak) BroHoverHighlightGroup* highlightGroup;
@property(nonatomic, weak) id target;
@property(nonatomic, assign) SEL action;
@property(nonatomic, assign) NSUInteger downloadIndex;
@end

@implementation BroDownloadRow

- (void)mouseEntered:(NSEvent*)event {
if (_interactive) {
[_highlightGroup hoverOnView:self];
}
}

- (void)mouseExited:(NSEvent*)event {
if (_interactive) {
[_highlightGroup hoverOffView:self];
}
}

- (void)resetCursorRects {
if (_interactive) {
[self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]];
}
}

- (void)mouseDown:(NSEvent*)event {
}

- (void)mouseUp:(NSEvent*)event {
NSPoint point = [self convertPoint:event.locationInWindow fromView:nil];
if (_interactive && NSPointInRect(point, self.bounds)) {
[NSApp sendAction:_action to:_target from:self];
}
}

@end

@interface BroDownloadsPopover : NSView
// Rebuilds the rows from the downloads list and resizes self to fit, showing
// at most as many rows as fit in |maxHeight| (newest kept, oldest dropped).
Expand All @@ -68,6 +109,7 @@ @implementation BroDownloadsPopover {
// Entries backing the visible rows; magnifier buttons index into this.
NSMutableArray<BroDownloadEntry*>* rowEntries_;
CGFloat lastMaxHeight_;
BroHoverHighlightGroup* rowHighlightGroup_;
}

static NSAttributedString* BroDownloadsButtonTitle(NSString* text,
Expand All @@ -91,12 +133,10 @@ - (instancetype)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.wantsLayer = YES;
self.layer.cornerRadius = 8.0;
self.layer.backgroundColor =
[NSColor colorWithWhite:0.18 alpha:0.96].CGColor;
self.layer.borderWidth = 1.0;
self.layer.borderColor =
[NSColor colorWithWhite:1.0 alpha:kWindowBorderAlpha].CGColor;
self.layer.cornerRadius = kSurfaceCornerRadius;
BroApplyElevation(self, BroElevationPanel);
rowHighlightGroup_ =
[[BroHoverHighlightGroup alloc] initWithContainerView:self];

headerLabel_ = BroHoverCardLabel(BroUIFontBold(10.0), 0.55);
headerLabel_.attributedStringValue = [[NSAttributedString alloc]
Expand Down Expand Up @@ -153,6 +193,7 @@ - (void)mouseDown:(NSEvent*)event {

- (void)reloadWithMaxHeight:(CGFloat)maxHeight {
lastMaxHeight_ = maxHeight;
[rowHighlightGroup_ dismissImmediately];
for (NSView* row in rowViews_) {
[row removeFromSuperview];
}
Expand Down Expand Up @@ -224,10 +265,28 @@ - (void)reloadWithMaxHeight:(CGFloat)maxHeight {
}

- (NSView*)makeRowForEntry:(BroDownloadEntry*)entry index:(NSUInteger)index {
NSView* row = [[NSView alloc] initWithFrame:NSZeroRect];
BroDownloadRow* row = [[BroDownloadRow alloc] initWithFrame:NSZeroRect];

BOOL fileExists = entry.path.length > 0 &&
[[NSFileManager defaultManager] fileExistsAtPath:entry.path];
row.interactive = entry.state == BroDownloadStateComplete && fileExists;
row.highlightGroup = rowHighlightGroup_;
row.target = self;
row.action = @selector(openEntry:);
row.downloadIndex = index;
if (row.interactive) {
row.accessibilityElement = YES;
row.accessibilityRole = NSAccessibilityButtonRole;
row.accessibilityLabel =
[NSString stringWithFormat:@"Open %@", entry.name ?: @"download"];
NSTrackingArea* trackingArea = [[NSTrackingArea alloc]
initWithRect:NSZeroRect
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
NSTrackingInVisibleRect
owner:row
userInfo:nil];
[row addTrackingArea:trackingArea];
}

NSImageView* icon = [[NSImageView alloc]
initWithFrame:NSMakeRect(12.0, 8.0, 28.0, 28.0)];
Expand Down Expand Up @@ -259,7 +318,7 @@ - (NSView*)makeRowForEntry:(BroDownloadEntry*)entry index:(NSUInteger)index {
NSView* track = [[NSView alloc]
initWithFrame:NSMakeRect(textX, 4.0, barWidth, 2.0)];
track.wantsLayer = YES;
track.layer.cornerRadius = 1.0;
track.layer.cornerRadius = BroCapsuleCornerRadius(NSHeight(track.frame));
track.layer.backgroundColor =
[NSColor colorWithWhite:1.0 alpha:0.15].CGColor;
[row addSubview:track];
Expand All @@ -269,7 +328,7 @@ - (NSView*)makeRowForEntry:(BroDownloadEntry*)entry index:(NSUInteger)index {
NSView* fill = [[NSView alloc]
initWithFrame:NSMakeRect(textX, 4.0, barWidth * fraction, 2.0)];
fill.wantsLayer = YES;
fill.layer.cornerRadius = 1.0;
fill.layer.cornerRadius = BroCapsuleCornerRadius(NSHeight(fill.frame));
fill.layer.backgroundColor =
[NSColor colorWithWhite:1.0 alpha:0.85].CGColor;
[row addSubview:fill];
Expand Down Expand Up @@ -297,6 +356,20 @@ - (NSView*)makeRowForEntry:(BroDownloadEntry*)entry index:(NSUInteger)index {
return row;
}

- (void)openEntry:(BroDownloadRow*)sender {
NSUInteger index = sender.downloadIndex;
if (index >= rowEntries_.count) {
return;
}
BroDownloadEntry* entry = rowEntries_[index];
if (entry.state == BroDownloadStateComplete && entry.path.length > 0 &&
[[NSFileManager defaultManager] fileExistsAtPath:entry.path]) {
[[NSWorkspace sharedWorkspace] openURL:
[NSURL fileURLWithPath:entry.path]];
BroHideDownloadsPopover();
}
}

- (NSString*)subtitleForEntry:(BroDownloadEntry*)entry
fileExists:(BOOL)fileExists {
switch (entry.state) {
Expand Down Expand Up @@ -421,7 +494,7 @@ static void BroRemoveDownloadsMonitors(void) {
// BroHideDownloadsPopover is declared extern in bro_mac_internal.h.
void BroHideDownloadsPopover(void) {
BroRemoveDownloadsMonitors();
g_downloads_popover.hidden = YES;
BroOverlayHide(g_downloads_popover);
}

static void BroShowDownloadsPopover(void) {
Expand All @@ -432,6 +505,7 @@ static void BroShowDownloadsPopover(void) {
if (!g_downloads_popover) {
g_downloads_popover =
[[BroDownloadsPopover alloc] initWithFrame:NSZeroRect];
g_downloads_popover.hidden = YES;
// Tracks the top-right corner across window resizes.
g_downloads_popover.autoresizingMask =
NSViewMinXMargin | NSViewMinYMargin;
Expand All @@ -441,7 +515,7 @@ static void BroShowDownloadsPopover(void) {
[container addSubview:g_downloads_popover];
[g_downloads_popover reloadWithMaxHeight:NSHeight(container.bounds) - 12.0];
BroPositionDownloadsPopover();
g_downloads_popover.hidden = NO;
BroOverlayShow(g_downloads_popover);

if (!g_downloads_click_monitor) {
g_downloads_click_monitor = BroInstallOutsideDismissMonitor(
Expand Down Expand Up @@ -483,22 +557,12 @@ static void BroRefreshDownloadsPopover(void) {
BroPositionDownloadsPopover();
}

// Brief dip-and-restore of the button's opacity when a download starts or
// finishes; enough of a nudge without stealing the pointer like an auto-shown
// panel would.
// Nudge when a download starts or finishes; enough without stealing the
// pointer like an auto-shown panel would. The toolbar owns the animation:
// the button hides at rest, so the pulse is a brief reveal (or an opacity
// dip when already revealed by hover).
static void BroPulseDownloadsButton(void) {
BroHoverButton* button = g_toolbar.downloadsButton;
if (!button) {
return;
}
if ([NSWorkspace sharedWorkspace].accessibilityDisplayShouldReduceMotion) {
return;
}
button.alphaValue = 0.25;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext* ctx) {
ctx.duration = 0.5;
button.animator.alphaValue = 1.0;
}];
[g_toolbar pulseDownloadsButton];
}

#pragma mark - Downloads model
Expand Down
31 changes: 31 additions & 0 deletions src/bro_geometry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Shared geometry primitives for Bro's AppKit surfaces.

#ifndef BRO_GEOMETRY_H_
#define BRO_GEOMETRY_H_

#import <Cocoa/Cocoa.h>

// Corner radii follow the concentric-corner rule throughout the app:
//
// outer radius - gap = inner radius
//
// Nested surfaces derive their curve from the boundary around them instead
// of repeating visually similar magic numbers. Standalone surfaces establish
// the outer radius; capsules derive theirs from their own height.
// https://cloudfour.com/thinks/the-math-behind-nesting-rounded-corners/
static inline CGFloat BroNestedCornerRadius(CGFloat outerRadius, CGFloat gap) {
return MAX(0.0, outerRadius - MAX(0.0, gap));
}

static inline CGFloat BroCapsuleCornerRadius(CGFloat height) {
return height / 2.0;
}

static const CGFloat kSurfaceCornerRadius = 8.0;
static const CGFloat kControlCornerGap = 2.0;
static const CGFloat kControlCornerRadius =
BroNestedCornerRadius(kSurfaceCornerRadius, kControlCornerGap);
static const CGFloat kCompactControlCornerRadius =
BroNestedCornerRadius(kControlCornerRadius, kControlCornerGap);

#endif // BRO_GEOMETRY_H_
Loading
Loading