Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
- Introduced native macOS window effects with a new dynamic library for enhanced UI. - Updated build scripts to compile the macOS effects library and integrate it into the application. - Modified Electron context to apply macOS-specific styles and effects based on the platform. - Enhanced CSS for macOS vibrancy support in the application layout. - Updated configuration files to include new build steps and dependencies for macOS.
403 lines
11 KiB
Plaintext
403 lines
11 KiB
Plaintext
#import <Cocoa/Cocoa.h>
|
|
|
|
static NSString *const kElectrobunVibrancyViewIdentifier =
|
|
@"ElectrobunVibrancyView";
|
|
static NSString *const kElectrobunNativeDragViewIdentifier =
|
|
@"ElectrobunNativeDragView";
|
|
static NSString *const kElectrobunWindowBorderViewIdentifier =
|
|
@"ElectrobunWindowBorderView";
|
|
|
|
static CGFloat gWindowCornerRadius = 15.0;
|
|
|
|
@interface ElectrobunWindowBorderView : NSView
|
|
@property(nonatomic) CGFloat cornerRadius;
|
|
@end
|
|
|
|
@implementation ElectrobunWindowBorderView
|
|
@synthesize cornerRadius = _cornerRadius;
|
|
|
|
- (instancetype)initWithFrame:(NSRect)frameRect {
|
|
self = [super initWithFrame:frameRect];
|
|
if (self) {
|
|
_cornerRadius = gWindowCornerRadius;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isOpaque {
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)acceptsFirstMouse:(NSEvent *)event {
|
|
(void)event;
|
|
return NO;
|
|
}
|
|
|
|
- (NSView *)hitTest:(NSPoint)point {
|
|
(void)point;
|
|
return nil;
|
|
}
|
|
|
|
- (void)setFrame:(NSRect)frame {
|
|
[super setFrame:frame];
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
(void)dirtyRect;
|
|
|
|
NSRect bounds = [self bounds];
|
|
CGFloat radius = MAX(0.0, self.cornerRadius);
|
|
NSBezierPath *borderPath = [NSBezierPath
|
|
bezierPathWithRoundedRect:NSInsetRect(bounds, 0.5, 0.5)
|
|
xRadius:radius
|
|
yRadius:radius];
|
|
|
|
if (@available(macOS 10.14, *)) {
|
|
NSAppearance *appearance = [self effectiveAppearance];
|
|
BOOL isDark =
|
|
[[appearance bestMatchFromAppearancesWithNames:@[
|
|
NSAppearanceNameAqua, NSAppearanceNameDarkAqua
|
|
]] isEqualToString:NSAppearanceNameDarkAqua];
|
|
|
|
if (isDark) {
|
|
[[NSColor colorWithWhite:1.0 alpha:0.18] setStroke];
|
|
} else {
|
|
[[NSColor colorWithWhite:1.0 alpha:0.72] setStroke];
|
|
}
|
|
} else {
|
|
[[NSColor colorWithWhite:1.0 alpha:0.72] setStroke];
|
|
}
|
|
|
|
borderPath.lineWidth = 1.0;
|
|
[borderPath stroke];
|
|
}
|
|
@end
|
|
|
|
@interface ElectrobunNativeDragView : NSView
|
|
@end
|
|
|
|
@implementation ElectrobunNativeDragView
|
|
- (BOOL)isOpaque {
|
|
return NO;
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
(void)dirtyRect;
|
|
}
|
|
|
|
- (void)mouseDown:(NSEvent *)event {
|
|
NSWindow *window = [self window];
|
|
if (window != nil && event != nil) {
|
|
[window performWindowDragWithEvent:event];
|
|
}
|
|
}
|
|
@end
|
|
|
|
static NSVisualEffectView *findVibrancyView(NSView *contentView) {
|
|
for (NSView *subview in [contentView subviews]) {
|
|
if ([subview isKindOfClass:[NSVisualEffectView class]] &&
|
|
[[subview identifier]
|
|
isEqualToString:kElectrobunVibrancyViewIdentifier]) {
|
|
return (NSVisualEffectView *)subview;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static ElectrobunNativeDragView *findNativeDragView(NSView *contentView) {
|
|
for (NSView *subview in [contentView subviews]) {
|
|
if ([subview isKindOfClass:[ElectrobunNativeDragView class]] &&
|
|
[[subview identifier]
|
|
isEqualToString:kElectrobunNativeDragViewIdentifier]) {
|
|
return (ElectrobunNativeDragView *)subview;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static ElectrobunWindowBorderView *findWindowBorderView(NSView *contentView) {
|
|
for (NSView *subview in [contentView subviews]) {
|
|
if ([subview isKindOfClass:[ElectrobunWindowBorderView class]] &&
|
|
[[subview identifier]
|
|
isEqualToString:kElectrobunWindowBorderViewIdentifier]) {
|
|
return (ElectrobunWindowBorderView *)subview;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static void applyWindowCornerRadius(NSWindow *window, CGFloat radius) {
|
|
gWindowCornerRadius = MAX(0.0, radius);
|
|
|
|
NSView *contentView = [window contentView];
|
|
if (contentView == nil) {
|
|
return;
|
|
}
|
|
|
|
contentView.wantsLayer = YES;
|
|
contentView.layer.cornerRadius = gWindowCornerRadius;
|
|
contentView.layer.masksToBounds = YES;
|
|
|
|
NSVisualEffectView *effectView = findVibrancyView(contentView);
|
|
if (effectView != nil) {
|
|
effectView.wantsLayer = YES;
|
|
effectView.layer.cornerRadius = gWindowCornerRadius;
|
|
effectView.layer.masksToBounds = YES;
|
|
}
|
|
|
|
ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView);
|
|
if (borderView != nil) {
|
|
borderView.cornerRadius = gWindowCornerRadius;
|
|
[borderView setNeedsDisplay:YES];
|
|
}
|
|
}
|
|
|
|
static void ensureWindowBorder(NSWindow *window) {
|
|
NSView *contentView = [window contentView];
|
|
if (contentView == nil) {
|
|
return;
|
|
}
|
|
|
|
ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView);
|
|
if (borderView == nil) {
|
|
borderView = [[ElectrobunWindowBorderView alloc]
|
|
initWithFrame:[contentView bounds]];
|
|
[borderView setIdentifier:kElectrobunWindowBorderViewIdentifier];
|
|
[borderView
|
|
setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
|
|
}
|
|
|
|
borderView.cornerRadius = gWindowCornerRadius;
|
|
[borderView setFrame:[contentView bounds]];
|
|
|
|
if ([borderView superview] == nil) {
|
|
[contentView addSubview:borderView
|
|
positioned:NSWindowAbove
|
|
relativeTo:nil];
|
|
} else {
|
|
[borderView removeFromSuperview];
|
|
[contentView addSubview:borderView
|
|
positioned:NSWindowAbove
|
|
relativeTo:nil];
|
|
}
|
|
|
|
[borderView setNeedsDisplay:YES];
|
|
}
|
|
|
|
extern "C" bool enableWindowVibrancy(void *windowPtr, double cornerRadius) {
|
|
if (windowPtr == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
__block BOOL success = NO;
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
if (![window isKindOfClass:[NSWindow class]]) {
|
|
return;
|
|
}
|
|
|
|
applyWindowCornerRadius(window, (CGFloat)cornerRadius);
|
|
|
|
[window setOpaque:NO];
|
|
[window setBackgroundColor:[NSColor clearColor]];
|
|
[window setTitlebarAppearsTransparent:YES];
|
|
[window setHasShadow:YES];
|
|
|
|
NSView *contentView = [window contentView];
|
|
if (contentView == nil) {
|
|
return;
|
|
}
|
|
|
|
NSVisualEffectView *effectView = findVibrancyView(contentView);
|
|
|
|
if (effectView == nil) {
|
|
effectView = [[NSVisualEffectView alloc]
|
|
initWithFrame:[contentView bounds]];
|
|
[effectView setIdentifier:kElectrobunVibrancyViewIdentifier];
|
|
[effectView
|
|
setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
|
|
}
|
|
|
|
if (@available(macOS 10.14, *)) {
|
|
[effectView setMaterial:NSVisualEffectMaterialUnderWindowBackground];
|
|
} else {
|
|
[effectView setMaterial:NSVisualEffectMaterialSidebar];
|
|
}
|
|
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
|
|
[effectView setState:NSVisualEffectStateActive];
|
|
|
|
if ([effectView superview] == nil) {
|
|
NSView *relativeView = [[contentView subviews] firstObject];
|
|
if (relativeView != nil) {
|
|
[contentView addSubview:effectView
|
|
positioned:NSWindowBelow
|
|
relativeTo:relativeView];
|
|
} else {
|
|
[contentView addSubview:effectView];
|
|
}
|
|
}
|
|
|
|
ensureWindowBorder(window);
|
|
applyWindowCornerRadius(window, gWindowCornerRadius);
|
|
|
|
[window invalidateShadow];
|
|
success = YES;
|
|
});
|
|
|
|
return success;
|
|
}
|
|
|
|
extern "C" bool setWindowCornerRadius(void *windowPtr, double cornerRadius) {
|
|
if (windowPtr == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
__block BOOL success = NO;
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
if (![window isKindOfClass:[NSWindow class]]) {
|
|
return;
|
|
}
|
|
|
|
applyWindowCornerRadius(window, (CGFloat)cornerRadius);
|
|
ensureWindowBorder(window);
|
|
[window invalidateShadow];
|
|
success = YES;
|
|
});
|
|
|
|
return success;
|
|
}
|
|
|
|
extern "C" bool ensureWindowShadow(void *windowPtr) {
|
|
if (windowPtr == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
__block BOOL success = NO;
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
if (![window isKindOfClass:[NSWindow class]]) {
|
|
return;
|
|
}
|
|
|
|
[window setHasShadow:YES];
|
|
[window invalidateShadow];
|
|
ensureWindowBorder(window);
|
|
applyWindowCornerRadius(window, gWindowCornerRadius);
|
|
success = YES;
|
|
});
|
|
|
|
return success;
|
|
}
|
|
|
|
extern "C" bool setWindowTrafficLightsPosition(void *windowPtr, double x,
|
|
double yFromTop) {
|
|
if (windowPtr == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
__block BOOL success = NO;
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
if (![window isKindOfClass:[NSWindow class]]) {
|
|
return;
|
|
}
|
|
|
|
NSButton *closeButton =
|
|
[window standardWindowButton:NSWindowCloseButton];
|
|
NSButton *minimizeButton =
|
|
[window standardWindowButton:NSWindowMiniaturizeButton];
|
|
NSButton *zoomButton = [window standardWindowButton:NSWindowZoomButton];
|
|
|
|
if (closeButton == nil || minimizeButton == nil || zoomButton == nil) {
|
|
return;
|
|
}
|
|
|
|
NSView *buttonContainer = [closeButton superview];
|
|
if (buttonContainer == nil) {
|
|
return;
|
|
}
|
|
|
|
CGFloat spacing = NSMinX(minimizeButton.frame) - NSMinX(closeButton.frame);
|
|
if (spacing <= 0) {
|
|
spacing = closeButton.frame.size.width + 6.0;
|
|
}
|
|
|
|
BOOL flipped = [buttonContainer isFlipped];
|
|
CGFloat targetY = yFromTop;
|
|
if (!flipped) {
|
|
targetY = buttonContainer.frame.size.height - yFromTop -
|
|
closeButton.frame.size.height;
|
|
}
|
|
targetY = MAX(0.0, targetY);
|
|
|
|
CGFloat currentX = x;
|
|
NSArray *buttons = @[ closeButton, minimizeButton, zoomButton ];
|
|
for (NSButton *button in buttons) {
|
|
[button setFrameOrigin:NSMakePoint(currentX, targetY)];
|
|
currentX += spacing;
|
|
}
|
|
|
|
[buttonContainer setNeedsLayout:YES];
|
|
[buttonContainer layoutSubtreeIfNeeded];
|
|
[window invalidateShadow];
|
|
success = YES;
|
|
});
|
|
|
|
return success;
|
|
}
|
|
|
|
extern "C" bool setNativeWindowDragRegion(void *windowPtr, double x,
|
|
double height) {
|
|
if (windowPtr == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
__block BOOL success = NO;
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
NSWindow *window = (__bridge NSWindow *)windowPtr;
|
|
if (![window isKindOfClass:[NSWindow class]]) {
|
|
return;
|
|
}
|
|
|
|
NSView *contentView = [window contentView];
|
|
if (contentView == nil) {
|
|
return;
|
|
}
|
|
|
|
CGFloat dragX = MAX(0.0, x);
|
|
CGFloat dragHeight = MAX(0.0, height);
|
|
CGFloat dragWidth = MAX(0.0, contentView.bounds.size.width - dragX);
|
|
if (dragHeight <= 0.0 || dragWidth <= 0.0) {
|
|
return;
|
|
}
|
|
|
|
BOOL flipped = [contentView isFlipped];
|
|
CGFloat dragY = flipped ? 0.0 : contentView.bounds.size.height - dragHeight;
|
|
dragY = MAX(0.0, dragY);
|
|
|
|
ElectrobunNativeDragView *dragView = findNativeDragView(contentView);
|
|
if (dragView == nil) {
|
|
dragView = [[ElectrobunNativeDragView alloc] initWithFrame:NSZeroRect];
|
|
[dragView setIdentifier:kElectrobunNativeDragViewIdentifier];
|
|
}
|
|
|
|
[dragView setFrame:NSMakeRect(dragX, dragY, dragWidth, dragHeight)];
|
|
[dragView setAutoresizingMask:NSViewWidthSizable];
|
|
|
|
if ([dragView superview] == nil) {
|
|
[contentView addSubview:dragView
|
|
positioned:NSWindowAbove
|
|
relativeTo:nil];
|
|
}
|
|
|
|
success = YES;
|
|
});
|
|
|
|
return success;
|
|
}
|