diff --git a/Makefile b/Makefile index 19940d238..a681aa940 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,7 @@ copy-sparkle-framework: clean-sparkle: rm -rf Frameworks/* > /dev/null 2>&1 || true + rm -rf Sparkle/build > /dev/null 2>&1 || true .PHONY: package archive sign-archive diff --git a/Squirrel.xcodeproj/project.pbxproj b/Squirrel.xcodeproj/project.pbxproj index d618fd5be..c0eaf7c11 100644 --- a/Squirrel.xcodeproj/project.pbxproj +++ b/Squirrel.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ A47C48DF105E8CE8006D528B /* macos_keycode.m in Sources */ = {isa = PBXBuildFile; fileRef = A47C48DE105E8CE8006D528B /* macos_keycode.m */; }; A4B8E1B30F645B870094E08B /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4B8E1B20F645B870094E08B /* Carbon.framework */; }; A4FC48CB0F6530EF0069BE81 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A4FC48C90F6530EF0069BE81 /* Localizable.strings */; }; + D2BDFAEB2706900700924630 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2BDFAEA2706900700924630 /* QuartzCore.framework */; }; E93074B70A5C264700470842 /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E93074B60A5C264700470842 /* InputMethodKit.framework */; }; /* End PBXBuildFile section */ @@ -281,6 +282,7 @@ A47C48DE105E8CE8006D528B /* macos_keycode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = macos_keycode.m; sourceTree = ""; }; A4B8E1B20F645B870094E08B /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; A4FC48CA0F6530EF0069BE81 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + D2BDFAEA2706900700924630 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; E93074B60A5C264700470842 /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = /System/Library/Frameworks/InputMethodKit.framework; sourceTree = ""; }; /* End PBXFileReference section */ @@ -289,6 +291,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D2BDFAEB2706900700924630 /* QuartzCore.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, E93074B70A5C264700470842 /* InputMethodKit.framework in Frameworks */, A4B8E1B30F645B870094E08B /* Carbon.framework in Frameworks */, @@ -385,6 +388,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + D2BDFAEA2706900700924630 /* QuartzCore.framework */, 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, ); diff --git a/SquirrelPanel.m b/SquirrelPanel.m index b25c7330f..1cb07ce04 100644 --- a/SquirrelPanel.m +++ b/SquirrelPanel.m @@ -1,6 +1,7 @@ #import "SquirrelPanel.h" #import "SquirrelConfig.h" +#import static const CGFloat kOffsetHeight = 5; static const CGFloat kDefaultFontSize = 24; @@ -8,6 +9,52 @@ static const NSTimeInterval kShowStatusDuration = 1.2; static NSString *const kDefaultCandidateFormat = @"%c.\u00A0%@"; +@implementation NSBezierPath (BezierPathQuartzUtilities) +// This method works only in OS X v10.2 and later. +- (CGPathRef)quartzPath { + NSInteger i, numElements; + // Need to begin a path here. + CGPathRef immutablePath = NULL; + + // Then draw the path elements. + numElements = [self elementCount]; + if (numElements > 0) { + CGMutablePathRef path = CGPathCreateMutable(); + NSPoint points[3]; + BOOL didClosePath = YES; + for (i = 0; i < numElements; i++) { + switch ([self elementAtIndex:i associatedPoints:points]) { + case NSMoveToBezierPathElement: + CGPathMoveToPoint(path, NULL, points[0].x, points[0].y); + break; + case NSLineToBezierPathElement: + CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y); + didClosePath = NO; + break; + case NSCurveToBezierPathElement: + CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y, + points[1].x, points[1].y, + points[2].x, points[2].y); + didClosePath = NO; + break; + case NSClosePathBezierPathElement: + CGPathCloseSubpath(path); + didClosePath = YES; + break; + } + } + + // Be sure the path is closed or Quartz may not do valid hit detection. + if (!didClosePath) { + CGPathCloseSubpath(path); + } + immutablePath = CGPathCreateCopy(path); + CGPathRelease(path); + } + return immutablePath; +} +@end + @interface SquirrelTheme : NSObject @property(nonatomic, assign) BOOL native; @@ -25,6 +72,7 @@ @interface SquirrelTheme : NSObject @property(nonatomic, readonly) CGFloat linespace; @property(nonatomic, readonly) CGFloat preeditLinespace; @property(nonatomic, readonly) CGFloat alpha; +@property(nonatomic, readonly) BOOL translucency; @property(nonatomic, readonly) BOOL linear; @property(nonatomic, readonly) BOOL vertical; @property(nonatomic, readonly) BOOL inlinePreedit; @@ -58,6 +106,7 @@ - (void)setCornerRadius:(CGFloat)cornerRadius linespace:(CGFloat)linespace preeditLinespace:(CGFloat)preeditLinespace alpha:(CGFloat)alpha + translucency:(BOOL)translucency linear:(BOOL)linear vertical:(BOOL)vertical inlinePreedit:(BOOL)inlinePreedit @@ -124,6 +173,7 @@ - (void)setCornerRadius:(double)cornerRadius linespace:(double)linespace preeditLinespace:(double)preeditLinespace alpha:(CGFloat)alpha + translucency:(BOOL)translucency linear:(BOOL)linear vertical:(BOOL)vertical inlinePreedit:(BOOL)inlinePreedit @@ -134,6 +184,7 @@ - (void)setCornerRadius:(double)cornerRadius _borderWidth = borderWidth; _linespace = linespace; _alpha = alpha; + _translucency = translucency; _preeditLinespace = preeditLinespace; _linear = linear; _vertical = vertical; @@ -177,6 +228,7 @@ @interface SquirrelView : NSView @property(nonatomic, readonly) BOOL isDark; @property(nonatomic, strong, readonly) SquirrelTheme *currentTheme; @property(nonatomic, assign) CGFloat seperatorWidth; +@property(nonatomic, readonly) CAShapeLayer *shape; - (BOOL)isFlipped; - (void)setText:(NSAttributedString *)text; @@ -231,6 +283,7 @@ - (instancetype)initWithFrame:(NSRect)frameRect { if (@available(macOS 10.14, *)) { _darkTheme = [[SquirrelTheme alloc] init]; } + _shape = [[CAShapeLayer alloc] init]; return self; } @@ -629,6 +682,7 @@ - (void)drawRect:(NSRect)dirtyRect { [NSBezierPath setDefaultLineWidth:0]; backgroundPath = drawSmoothLines(rectVertex(backgroundRect), theme.cornerRadius*0.3, theme.cornerRadius*1.4); + _shape.path = backgroundPath.quartzPath; // Nothing should extend beyond backgroundPath borderPath = [backgroundPath copy]; [borderPath addClip]; @@ -700,6 +754,7 @@ - (void)drawRect:(NSRect)dirtyRect { @implementation SquirrelPanel { SquirrelView *_view; + NSVisualEffectView *_back; NSRange _preeditRange; NSRect _screenRect; @@ -857,8 +912,20 @@ - (instancetype)init { self.hasShadow = YES; self.opaque = NO; self.backgroundColor = [NSColor clearColor]; + NSView *contentView = [[NSView alloc] init]; _view = [[SquirrelView alloc] initWithFrame:self.contentView.frame]; - self.contentView = _view; + if (@available(macOS 10.14, *)) { + _back = [[NSVisualEffectView alloc] init]; + _back.blendingMode = NSVisualEffectBlendingModeBehindWindow; + _back.material = NSVisualEffectMaterialFullScreenUI; + _back.state = NSVisualEffectStateActive; + _back.wantsLayer = YES; + _back.layer.mask = _view.shape; + [contentView addSubview:_back]; + } + [contentView addSubview:_view]; + + self.contentView = contentView; [self initializeUIStyleForDarkMode:NO]; if (@available(macOS 10.14, *)) { [self initializeUIStyleForDarkMode:YES]; @@ -966,16 +1033,39 @@ - (void)show { if (NSMinY(windowRect) < NSMinY(_screenRect)) { windowRect.origin.y = NSMinY(_screenRect); } + [self setFrame:windowRect display:YES]; + BOOL translucency = theme.translucency; + [_view setFrame:_view.superview.bounds]; + if (@available(macOS 10.14, *)) { + if (translucency) { + [_back setFrame:_back.superview.bounds]; + _back.appearance = NSApp.effectiveAppearance; + [_back setHidden:NO]; + } else { + [_back setHidden:YES]; + } + } // rotate the view, the core in vertical mode! if (theme.vertical) { _view.boundsRotation = 90.0; [_view setBoundsOrigin:NSMakePoint(0, windowRect.size.width)]; + if (@available(macOS 10.14, *)) { + if (translucency) { + _back.boundsRotation = 90.0; + [_back setBoundsOrigin:NSMakePoint(0, windowRect.size.width)]; + } + } } else { _view.boundsRotation = 0; [_view setBoundsOrigin:NSMakePoint(0, 0)]; + if (@available(macOS 10.14, *)) { + if (translucency) { + _back.boundsRotation = 0; + [_back setBoundsOrigin:NSMakePoint(0, 0)]; + } + } } self.alphaValue = theme.alpha; - [self setFrame:windowRect display:YES]; [self invalidateShadow]; [self orderFront:nil]; // voila ! @@ -1331,6 +1421,7 @@ +(void)updateTheme:(SquirrelTheme *)theme withConfig:(SquirrelConfig *)config fo updateTextOrientation(&vertical, config, @"style"); BOOL inlinePreedit = [config getBool:@"style/inline_preedit"]; BOOL inlineCandidate = [config getBool:@"style/inline_candidate"]; + BOOL translucency = [config getBool:@"style/translucency"]; NSString *candidateFormat = [config getString:@"style/candidate_format"]; NSString *fontName = [config getString:@"style/font_face"]; @@ -1424,6 +1515,11 @@ +(void)updateTheme:(SquirrelTheme *)theme withConfig:(SquirrelConfig *)config fo if (inlineCandidateOverridden) { inlineCandidate = inlineCandidateOverridden.boolValue; } + NSNumber *translucencyOverridden = + [config getOptionalBool:[prefix stringByAppendingString:@"/translucency"]]; + if (translucencyOverridden) { + translucency = translucencyOverridden.boolValue; + } NSString *candidateFormatOverridden = [config getString:[prefix stringByAppendingString:@"/candidate_format"]]; if (candidateFormatOverridden) { @@ -1667,6 +1763,7 @@ +(void)updateTheme:(SquirrelTheme *)theme withConfig:(SquirrelConfig *)config fo linespace:lineSpacing preeditLinespace:spacing alpha:(alpha == 0 ? 1.0 : alpha) + translucency:translucency linear:linear vertical:vertical inlinePreedit:inlinePreedit