| 1 | #import <AppKit/AppKit.h> |
| 2 | #import <Foundation/Foundation.h> |
| 3 | |
| 4 | static NSTextField *MakeLabel(NSRect frame, NSString *text, NSFont *font, NSColor *color) { |
| 5 | NSTextField *label = [[NSTextField alloc] initWithFrame:frame]; |
| 6 | [label setStringValue:text ?: @""]; |
| 7 | [label setBezeled:NO]; |
| 8 | [label setBordered:NO]; |
| 9 | [label setDrawsBackground:NO]; |
| 10 | [label setEditable:NO]; |
| 11 | [label setSelectable:NO]; |
| 12 | [label setAlignment:NSTextAlignmentCenter]; |
| 13 | [label setTextColor:color]; |
| 14 | [label setFont:font]; |
| 15 | [label setLineBreakMode:NSLineBreakByTruncatingTail]; |
| 16 | [label setUsesSingleLineMode:YES]; |
| 17 | return label; |
| 18 | } |
| 19 | |
| 20 | int main(int argc, const char *argv[]) { |
| 21 | @autoreleasepool { |
| 22 | NSString *message = argc > 1 ? [NSString stringWithUTF8String:argv[1]] : @"Toast"; |
| 23 | NSString *detail = argc > 2 ? [NSString stringWithUTF8String:argv[2]] : @""; |
| 24 | double durationSeconds = argc > 3 ? strtod(argv[3], NULL) : 1.0; |
| 25 | if (durationSeconds < 0.25) { |
| 26 | durationSeconds = 0.25; |
| 27 | } |
| 28 | if (durationSeconds > 4.0) { |
| 29 | durationSeconds = 4.0; |
| 30 | } |
| 31 | |
| 32 | [NSApplication sharedApplication]; |
| 33 | [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; |
| 34 | |
| 35 | CGFloat width = MIN(420.0, MAX(220.0, (CGFloat)message.length * 9.0 + 72.0)); |
| 36 | CGFloat height = detail.length > 0 ? 72.0 : 52.0; |
| 37 | NSRect screenFrame = NSScreen.mainScreen ? NSScreen.mainScreen.visibleFrame : NSMakeRect(0, 0, 1440, 900); |
| 38 | CGFloat x = NSMidX(screenFrame) - (width / 2.0); |
| 39 | CGFloat y = NSMinY(screenFrame) + 72.0; |
| 40 | |
| 41 | NSPanel *window = [[NSPanel alloc] |
| 42 | initWithContentRect:NSMakeRect(x, y, width, height) |
| 43 | styleMask:NSWindowStyleMaskBorderless | NSWindowStyleMaskNonactivatingPanel |
| 44 | backing:NSBackingStoreBuffered |
| 45 | defer:NO]; |
| 46 | [window setOpaque:NO]; |
| 47 | [window setBackgroundColor:NSColor.clearColor]; |
| 48 | [window setHasShadow:YES]; |
| 49 | [window setIgnoresMouseEvents:YES]; |
| 50 | [window setFloatingPanel:YES]; |
| 51 | [window setHidesOnDeactivate:NO]; |
| 52 | [window setLevel:NSStatusWindowLevel]; |
| 53 | [window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces | |
| 54 | NSWindowCollectionBehaviorFullScreenAuxiliary]; |
| 55 | |
| 56 | NSView *contentView = window.contentView; |
| 57 | contentView.wantsLayer = YES; |
| 58 | contentView.layer.backgroundColor = [[NSColor colorWithCalibratedWhite:0.08 alpha:0.92] CGColor]; |
| 59 | contentView.layer.cornerRadius = 14.0; |
| 60 | contentView.layer.masksToBounds = YES; |
| 61 | contentView.layer.borderWidth = 1.0; |
| 62 | contentView.layer.borderColor = [[NSColor colorWithCalibratedWhite:1.0 alpha:0.12] CGColor]; |
| 63 | |
| 64 | CGFloat titleY = detail.length > 0 ? 34.0 : 15.0; |
| 65 | [contentView addSubview:MakeLabel( |
| 66 | NSMakeRect(18, titleY, width - 36, 20), |
| 67 | message, |
| 68 | [NSFont boldSystemFontOfSize:13.0], |
| 69 | NSColor.whiteColor)]; |
| 70 | |
| 71 | if (detail.length > 0) { |
| 72 | [contentView addSubview:MakeLabel( |
| 73 | NSMakeRect(18, 14, width - 36, 16), |
| 74 | detail, |
| 75 | [NSFont systemFontOfSize:11.0], |
| 76 | [NSColor colorWithCalibratedWhite:1.0 alpha:0.72])]; |
| 77 | } |
| 78 | |
| 79 | [window setAlphaValue:0.0]; |
| 80 | [window orderFrontRegardless]; |
| 81 | |
| 82 | for (NSInteger step = 1; step <= 5; step += 1) { |
| 83 | [window setAlphaValue:(CGFloat)step / 5.0]; |
| 84 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.02]]; |
| 85 | } |
| 86 | |
| 87 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:durationSeconds]]; |
| 88 | |
| 89 | for (NSInteger step = 4; step >= 0; step -= 1) { |
| 90 | [window setAlphaValue:(CGFloat)step / 5.0]; |
| 91 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.02]]; |
| 92 | } |
| 93 | |
| 94 | [window orderOut:nil]; |
| 95 | } |
| 96 | |
| 97 | return 0; |
| 98 | } |