diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index 09b99241..58ee5d38 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ BF0C83AB0186CC48FADF66A7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C847B2D68EACFAC1B06D1 /* Localizable.strings */; }; BF0C83E902F75A97E8FD374A /* menubar-2@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8D3333653ACAEF730DD8 /* menubar-2@2x.png */; }; BF0C8406805BDEC5B69E8803 /* screen-recording@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8E25373671B528AD448D /* screen-recording@2x.png */; }; - BF0C8477850829E15FD4DD43 /* close@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8F9CE1699644A22EAF2C /* close@2x.png */; }; BF0C84D7DC2AC65BB1C111EB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8349BA94516E8760BAFA /* Localizable.strings */; }; BF0C852D28E458CDD6FA3E44 /* app.icns in Resources */ = {isa = PBXBuildFile; fileRef = BF0C84E11784B3B0900D0963 /* app.icns */; }; BF0C853B271C4407E87FA183 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8096A88BC18C4902C037 /* InfoPlist.strings */; }; @@ -39,7 +38,6 @@ BF0C894199BB262BDB2D8D37 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8221101CD179DB1121D5 /* InfoPlist.strings */; }; BF0C894325A54CF0B6B53464 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8B8B31D2F421AC258351 /* Localizable.strings */; }; BF0C89C49CFBAAB4646BB8CB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C82A8848224E76B880A83 /* InfoPlist.strings */; }; - BF0C89DF22C7B7D0C27DA2A4 /* WindowControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C884157B87BF0E17EF7F8 /* WindowControlView.swift */; }; BF0C8A739A74695E60F16369 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8062E78677398A4A217B /* InfoPlist.strings */; }; BF0C8A95AAD9DADC95887A2D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C895A9AF79A869EE6B108 /* Localizable.strings */; }; BF0C8AC57CE5E9BBEEE2442A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C885D34CDCE2AFC5A94F7 /* InfoPlist.strings */; }; @@ -47,16 +45,15 @@ BF0C8B2E933E4A0A91D7AF64 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8195F23B30B8F4DD38D1 /* Localizable.strings */; }; BF0C8C1FDE88F7A0429BF466 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8D6F749064299AC47E2F /* InfoPlist.strings */; }; BF0C8C4381FB16A4DFBBB219 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8357200430B9683ECD6F /* InfoPlist.strings */; }; + BF0C8C7E96DB48120462DA00 /* TrafficLightButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8DF400A1AF64EB0CD0B3 /* TrafficLightButton.swift */; }; BF0C8C8E35FF49CAA4BF5FC4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8A970BD1CAD555F31EDA /* Localizable.strings */; }; BF0C8C9CA44285E493918A74 /* menubar-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8E54658B4DAEA0528200 /* menubar-3@2x.png */; }; - BF0C8CB668FFABBCA23880B6 /* minimize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C817D9C335EE4A0536601 /* minimize@2x.png */; }; BF0C8CC5057406014FD612CC /* ATShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8BA452332236D972C60E /* ATShortcut.swift */; }; BF0C8D1C57D74106018D073C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C87F5D4AD51D6950C98B0 /* InfoPlist.strings */; }; BF0C8D610C93D48F572937F8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8FBA6B0F4DEB510C71CB /* Localizable.strings */; }; BF0C8DD7EBC3BA1E86BF4BDB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C841E403C1C6E8C65727E /* InfoPlist.strings */; }; BF0C8EA4654F8059F0AED40E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C82992C31AA542EF9E638 /* InfoPlist.strings */; }; BF0C8F3526BC393290D5BE59 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C81EC1C5F3052A75C7D7B /* InfoPlist.strings */; }; - BF0C8F80281B50D57C95ECCF /* fullscreen@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C83BD867F6A874F9953C9 /* fullscreen@2x.png */; }; BF0C8FA2D756D3258E3A16B6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C81DF55C6B0A279CE0FE1 /* Localizable.strings */; }; D04BA004884A273D4D2D3EF1 /* HelperExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD91161791D42FEC4A60 /* HelperExtensions.swift */; }; D04BA0182F4734A5C8BB63DC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA5461DAEE9B5350C3020 /* Localizable.strings */; }; @@ -169,14 +166,10 @@ BF0C8118F91A00A04AD4DE26 /* bg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = bg; path = InfoPlist.strings; sourceTree = ""; }; BF0C816D6B12CC13F741E61F /* hi */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hi; path = Localizable.strings; sourceTree = ""; }; BF0C816F38751995E4F49BB0 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = InfoPlist.strings; sourceTree = ""; }; - BF0C817D9C335EE4A0536601 /* minimize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "minimize@2x.png"; sourceTree = ""; }; BF0C817E063D1405392D6C5B /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = Localizable.strings; sourceTree = ""; }; - BF0C81B8A30416D774B5B60E /* fullscreen.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = fullscreen.svg; sourceTree = ""; }; - BF0C81C1B55A0519C3272B09 /* close.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = close.svg; sourceTree = ""; }; BF0C828AF7BCC4F47E70B28F /* menubar-2.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = "menubar-2.svg"; sourceTree = ""; }; BF0C82C381FCBCF8109735AC /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = InfoPlist.strings; sourceTree = ""; }; BF0C833D471343CEA3DEFE56 /* icon_512x512@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_512x512@2x.png"; sourceTree = ""; }; - BF0C83BD867F6A874F9953C9 /* fullscreen@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "fullscreen@2x.png"; sourceTree = ""; }; BF0C83FC3C53FFB41A3832FD /* fa */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = fa; path = InfoPlist.strings; sourceTree = ""; }; BF0C843C2EDF4EF3A7A2C64A /* app.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = app.svg; sourceTree = ""; }; BF0C8465A0A9EA2814A22C35 /* SF-Pro-Text-Regular-Full.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "SF-Pro-Text-Regular-Full.otf"; sourceTree = ""; }; @@ -191,7 +184,6 @@ BF0C86E06A44AC151FEA4903 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = InfoPlist.strings; sourceTree = ""; }; BF0C87B913292EE53EDE4220 /* menubar-1.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = "menubar-1.svg"; sourceTree = ""; }; BF0C87C0514B13DACB6E702B /* fa */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = fa; path = Localizable.strings; sourceTree = ""; }; - BF0C884157B87BF0E17EF7F8 /* WindowControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowControlView.swift; sourceTree = ""; }; BF0C888D912E73D88919899A /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = Localizable.strings; sourceTree = ""; }; BF0C889529FF19620EDAC295 /* accessibility@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "accessibility@2x.png"; sourceTree = ""; }; BF0C8898ADB2A1B49312E87B /* cs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = cs; path = InfoPlist.strings; sourceTree = ""; }; @@ -213,6 +205,7 @@ BF0C8D3333653ACAEF730DD8 /* menubar-2@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-2@2x.png"; sourceTree = ""; }; BF0C8D87C20B7308B16498B6 /* et */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = et; path = InfoPlist.strings; sourceTree = ""; }; BF0C8D9E024876AD9A92BE8D /* sr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sr; path = Localizable.strings; sourceTree = ""; }; + BF0C8DF400A1AF64EB0CD0B3 /* TrafficLightButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrafficLightButton.swift; sourceTree = ""; }; BF0C8E25373671B528AD448D /* screen-recording@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "screen-recording@2x.png"; sourceTree = ""; }; BF0C8E54658B4DAEA0528200 /* menubar-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menubar-3@2x.png"; sourceTree = ""; }; BF0C8E5D2BD5F7F93DF77DF5 /* sk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sk; path = InfoPlist.strings; sourceTree = ""; }; @@ -220,8 +213,6 @@ BF0C8EC400A560FDF99F8C35 /* el */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = el; path = Localizable.strings; sourceTree = ""; }; BF0C8EE17F3ABD7A6D44BFFE /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = Localizable.strings; sourceTree = ""; }; BF0C8F0D1F5E9980C2E1A073 /* no */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = no; path = InfoPlist.strings; sourceTree = ""; }; - BF0C8F88F4326C82F16FD738 /* minimize.svg */ = {isa = PBXFileReference; lastKnownFileType = file.svg; path = minimize.svg; sourceTree = ""; }; - BF0C8F9CE1699644A22EAF2C /* close@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "close@2x.png"; sourceTree = ""; }; C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_alt_tab_macos.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D04BA015A45DE7AFDC9794FE /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; D04BA03200F5A8FC0CD03607 /* CGWindowID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGWindowID.swift; sourceTree = ""; }; @@ -565,19 +556,6 @@ path = app.iconset; sourceTree = ""; }; - BF0C8957AB44FB1E745D6BA5 /* window-controls */ = { - isa = PBXGroup; - children = ( - BF0C81C1B55A0519C3272B09 /* close.svg */, - BF0C8F88F4326C82F16FD738 /* minimize.svg */, - BF0C81B8A30416D774B5B60E /* fullscreen.svg */, - BF0C8F9CE1699644A22EAF2C /* close@2x.png */, - BF0C817D9C335EE4A0536601 /* minimize@2x.png */, - BF0C83BD867F6A874F9953C9 /* fullscreen@2x.png */, - ); - path = "window-controls"; - sourceTree = ""; - }; BF0C8A181CD8C6FD282D6AB5 /* ca.lproj */ = { isa = PBXGroup; children = ( @@ -804,6 +782,7 @@ D04BAA41B9A74189B065D856 /* text */, D04BAE333F7170E87C5AC0EF /* CustomRecorderControl.swift */, D04BA8AC77465E1B2BC947CF /* StackView.swift */, + BF0C8DF400A1AF64EB0CD0B3 /* TrafficLightButton.swift */, ); path = "generic-components"; sourceTree = ""; @@ -938,7 +917,6 @@ D04BA1DF8CAB2FAB7FE9244B /* ThumbnailFontIconView.swift */, D04BA258B56193958D60978A /* ThumbnailView.swift */, D04BAA998119CAA8B70A2B67 /* ThumbnailsView.swift */, - BF0C884157B87BF0E17EF7F8 /* WindowControlView.swift */, ); path = "main-window"; sourceTree = ""; @@ -994,7 +972,6 @@ BF0C86C288D4FD8F92D3C7DB /* app */, BF0C80536B40523C5DB23BE2 /* menubar */, BF0C8060939B50725E1C3392 /* permission-window */, - BF0C8957AB44FB1E745D6BA5 /* window-controls */, ); path = icons; sourceTree = ""; @@ -1396,9 +1373,6 @@ BF0C8C9CA44285E493918A74 /* menubar-3@2x.png in Resources */, BF0C829583ED47E458528F3A /* accessibility@2x.png in Resources */, BF0C8406805BDEC5B69E8803 /* screen-recording@2x.png in Resources */, - BF0C8477850829E15FD4DD43 /* close@2x.png in Resources */, - BF0C8CB668FFABBCA23880B6 /* minimize@2x.png in Resources */, - BF0C8F80281B50D57C95ECCF /* fullscreen@2x.png in Resources */, BF0C853B271C4407E87FA183 /* InfoPlist.strings in Resources */, BF0C8658CC748011E57691B7 /* Localizable.strings in Resources */, BF0C88D7E209C788558BD943 /* InfoPlist.strings in Resources */, @@ -1585,9 +1559,9 @@ D04BADA3F2DAB820D6FC6E75 /* AppCenterApplication.m in Sources */, D04BAA16F21AC41A23DDA63D /* ControlsTab.swift in Sources */, D04BADE4DDF7597749FB13BD /* PreferencesWindow.swift in Sources */, - BF0C89DF22C7B7D0C27DA2A4 /* WindowControlView.swift in Sources */, BF0C855CF8BA047E180425F9 /* KeyRepeatTimer.swift in Sources */, BF0C8CC5057406014FD612CC /* ATShortcut.swift in Sources */, + BF0C8C7E96DB48120462DA00 /* TrafficLightButton.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/resources/icons/window-controls/close.svg b/resources/icons/window-controls/close.svg deleted file mode 100644 index 4a75c3c8..00000000 --- a/resources/icons/window-controls/close.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - close - - - - - - - - - - - \ No newline at end of file diff --git a/resources/icons/window-controls/close@2x.png b/resources/icons/window-controls/close@2x.png deleted file mode 100644 index e18e9e92..00000000 Binary files a/resources/icons/window-controls/close@2x.png and /dev/null differ diff --git a/resources/icons/window-controls/fullscreen.svg b/resources/icons/window-controls/fullscreen.svg deleted file mode 100755 index 0016e8c9..00000000 --- a/resources/icons/window-controls/fullscreen.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - fullscreen - - - - - - - - \ No newline at end of file diff --git a/resources/icons/window-controls/fullscreen@2x.png b/resources/icons/window-controls/fullscreen@2x.png deleted file mode 100644 index 2187bde4..00000000 Binary files a/resources/icons/window-controls/fullscreen@2x.png and /dev/null differ diff --git a/resources/icons/window-controls/minimize.svg b/resources/icons/window-controls/minimize.svg deleted file mode 100755 index 3c9f545c..00000000 --- a/resources/icons/window-controls/minimize.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - minimize - - - - - - - - \ No newline at end of file diff --git a/resources/icons/window-controls/minimize@2x.png b/resources/icons/window-controls/minimize@2x.png deleted file mode 100644 index c628cd3e..00000000 Binary files a/resources/icons/window-controls/minimize@2x.png and /dev/null differ diff --git a/src/ui/generic-components/TrafficLightButton.swift b/src/ui/generic-components/TrafficLightButton.swift new file mode 100644 index 00000000..82cedb2c --- /dev/null +++ b/src/ui/generic-components/TrafficLightButton.swift @@ -0,0 +1,188 @@ +import Cocoa +import Foundation + +class TrafficLightButton: NSButton { + var isMouseOver = false + var type: NSWindow.ButtonType! + var window_: Window? + + init(_ type: NSWindow.ButtonType, _ size: CGFloat) { + super.init(frame: .init(origin: .zero, size: .init(width: size, height: size))) + self.type = type + target = self + action = #selector(onClick) + fit(size, size) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + @objc func onClick() { + if (type == .zoomButton) { + window_?.toggleFullscreen() + } else if (type == .miniaturizeButton) { + window_?.minDemin() + } else if (type == .closeButton) { + window_?.close() + } + } + + override func updateTrackingAreas() { + addTrackingArea(NSTrackingArea(rect: bounds, options: [.activeInActiveApp, .mouseEnteredAndExited, .assumeInside, .inVisibleRect], owner: self, userInfo: nil)) + } + + override func mouseEntered(with event: NSEvent) { + isMouseOver = true + setNeedsDisplay() + } + + override func mouseExited(with event: NSEvent) { + isMouseOver = false + setNeedsDisplay() + } + + override func draw(_ dirtyRect: NSRect) { + NSGraphicsContext.current?.saveGraphicsState() + let (backgroundGradient, lineColor, strokeColor) = colors() + var disk = drawDisk(backgroundGradient, strokeColor) + drawSymbol(lineColor) + drawDimming(disk) + NSGraphicsContext.current?.restoreGraphicsState() + } + + private func colors() -> (NSGradient, NSColor, NSColor) { + if NSColor.currentControlTint == .graphiteControlTint { + return ( + NSGradient(starting: NSColor(red: 0.57, green: 0.57, blue: 0.60, alpha: 1), + ending: NSColor(red: 0.56, green: 0.55, blue: 0.57, alpha: 1))!, + type == .zoomButton ? + NSColor(red: 0.16, green: 0.16, blue: 0.17, alpha: 1) + : type == .miniaturizeButton ? + NSColor(red: 0.35, green: 0.35, blue: 0.37, alpha: 1) + : + NSColor(red: 0.19, green: 0.18, blue: 0.20, alpha: 1), + NSColor(red: 0.51, green: 0.51, blue: 0.53, alpha: 1) + ) + } + if type == .zoomButton { + return ( + NSGradient(starting: NSColor(red: 0.153, green: 0.788, blue: 0.247, alpha: 1), + ending: NSColor(red: 0.153, green: 0.816, blue: 0.255, alpha: 1))!, + NSColor(red: 0.004, green: 0.392, blue: 0, alpha: 1), + NSColor(red: 0.180, green: 0.690, blue: 0.235, alpha: 1) + ) + } + if type == .miniaturizeButton { + return ( + NSGradient(starting: NSColor(red: 1, green: 0.741, blue: 0.180, alpha: 1), + ending: NSColor(red: 1, green: 0.773, blue: 0.184, alpha: 1))!, + NSColor(red: 0.600, green: 0.345, blue: 0.004, alpha: 1), + NSColor(red: 0.875, green: 0.616, blue: 0.094, alpha: 1) + ) + } + return ( + NSGradient(starting: NSColor(red: 1, green: 0.373, blue: 0.337, alpha: 1), + ending: NSColor(red: 1, green: 0.388, blue: 0.357, alpha: 1))!, + NSColor(red: 0.302, green: 0, blue: 0, alpha: 1), + NSColor(red: 0.886, green: 0.243, blue: 0.216, alpha: 1) + ) + } + + private func drawDimming(_ disk: NSBezierPath) { + if (isHighlighted) { + NSColor(calibratedRed: 0, green: 0, blue: 0, alpha: 0.5).setFill() + disk.fill() + } else if (isMouseOver) { + NSColor(calibratedRed: 0, green: 0, blue: 0, alpha: 0.25).setFill() + disk.fill() + } + } + + private func drawDisk(_ backgroundGradient: NSGradient, _ strokeColor: NSColor) -> NSBezierPath { + var disk = NSBezierPath() + disk.appendOval(in: NSMakeRect(bounds.origin.x + 0.5, bounds.origin.y + 0.5, bounds.width - 1, bounds.height - 1)) + backgroundGradient.draw(in: disk, relativeCenterPosition: NSMakePoint(0, 0)) + strokeColor.setStroke() + disk.lineWidth = 0.5 + disk.stroke() + return disk + } + + private func drawSymbol(_ lineColor: NSColor) { + if (type == NSWindow.ButtonType.zoomButton) { + var symbol = NSBezierPath() + symbol.move(to: NSMakePoint(bounds.width * 0.25, bounds.height * 0.75)) + symbol.line(to: NSMakePoint(bounds.width * 0.25, bounds.height * 1 / 3)) + symbol.line(to: NSMakePoint(bounds.width * 2 / 3, bounds.height * 0.75)) + symbol.close() + lineColor.setFill() + symbol.fill() + symbol.move(to: NSMakePoint(bounds.width * 0.75, bounds.height * 0.25)) + symbol.line(to: NSMakePoint(bounds.width * 0.75, bounds.height * 2 / 3)) + symbol.line(to: NSMakePoint(bounds.width * 1 / 3, bounds.height * 0.25)) + symbol.close() + lineColor.setFill() + symbol.fill() + // maximize cross + // NSGraphicsContext.current?.shouldAntialias = false + // var symbol = NSBezierPath() + // symbol.move(to: NSMakePoint(bounds.width / 2, bounds.height * 0.20)) + // symbol.line(to: NSMakePoint(bounds.width / 2, bounds.height * 0.80)) + // symbol.move(to: NSMakePoint(bounds.width * 0.80, bounds.height / 2)) + // symbol.line(to: NSMakePoint(bounds.width * 0.20, bounds.height / 2)) + // symbol.lineWidth = 0.75 + } else if (type == NSWindow.ButtonType.miniaturizeButton) { + NSGraphicsContext.current?.shouldAntialias = false + var symbol = NSBezierPath() + symbol.move(to: NSMakePoint(bounds.width * 0.80, bounds.height / 2)) + symbol.line(to: NSMakePoint(bounds.width * 0.20, bounds.height / 2)) + symbol.lineWidth = 0.75 + lineColor.setStroke() + symbol.stroke() + } else if (type == NSWindow.ButtonType.closeButton) { + var symbol = NSBezierPath() + symbol.move(to: NSMakePoint(bounds.width * 0.30, bounds.height * 0.30)) + symbol.line(to: NSMakePoint(bounds.width * 0.70, bounds.height * 0.70)) + symbol.move(to: NSMakePoint(bounds.width * 0.70, bounds.height * 0.30)) + symbol.line(to: NSMakePoint(bounds.width * 0.30, bounds.height * 0.70)) + symbol.lineWidth = 1 + lineColor.setStroke() + symbol.stroke() + } + } +} + + + +// experiment: use actual buttons from OS through standardWindowButton +// issues: +// * zoom button has a popover that can't be removed +// * overall they look/act depending on the parent window +// e.g. need to set `window.collectionBehavior = .fullScreenPrimary` to get fullscreen button + +//class TrafficLightButton: NSView { +// convenience init(_ nameTest: NSWindow.ButtonType) { +// self.init(frame: .zero) +// let button = NSWindow.standardWindowButton(nameTest, for: [.miniaturizable, .closable, .nonactivatingPanel])! +// button.action = nil +// addSubview(button) +// fit(button.frame.size.width, button.frame.size.height) +// } +// +// /// force the hovered state as if the mouse was on the traffic lights area +// /// see https://stackoverflow.com/a/30417372/2249756 +// @objc func _mouseInGroup(_: Any) -> Bool { +// return true +// } +// +// override func updateTrackingAreas() { +// addTrackingArea(NSTrackingArea(rect: .zero, options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect], owner: self, userInfo: nil)) +// } +// +// override func mouseEntered(with event: NSEvent) { +// } +// +// override func mouseExited(with event: NSEvent) { +// } +//} diff --git a/src/ui/main-window/ThumbnailView.swift b/src/ui/main-window/ThumbnailView.swift index 1cf39092..3d9823b1 100644 --- a/src/ui/main-window/ThumbnailView.swift +++ b/src/ui/main-window/ThumbnailView.swift @@ -1,6 +1,8 @@ import Cocoa class ThumbnailView: NSStackView { + static let windowsControlSize = CGFloat(16) + static let windowsControlSpacing = CGFloat(8) var window_: Window? var thumbnail = NSImageView() var appIcon = NSImageView() @@ -10,9 +12,9 @@ class ThumbnailView: NSStackView { var hiddenIcon = ThumbnailFontIconView(.circledSlashSign) var spaceIcon = ThumbnailFontIconView(.circledNumber0) var dockLabelIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledNumber0, 14, NSColor(srgbRed: 1, green: 0.30, blue: 0.25, alpha: 1), nil), NSColor.white) - var closeIcon = WindowControlView("close", 16) - var minimizeIcon = WindowControlView("minimize", 16) - var maximizeIcon = WindowControlView("fullscreen", 16) + var closeIcon = TrafficLightButton(.closeButton, windowsControlSize) + var minimizeIcon = TrafficLightButton(.miniaturizeButton, windowsControlSize) + var maximizeIcon = TrafficLightButton(.zoomButton, windowsControlSize) var hStackView: NSStackView! var mouseUpCallback: (() -> Void)! var mouseMovedCallback: (() -> Void)! @@ -61,13 +63,6 @@ class ThumbnailView: NSStackView { thumbnail.addSubview(closeIcon, positioned: .above, relativeTo: nil) thumbnail.addSubview(minimizeIcon, positioned: .above, relativeTo: nil) thumbnail.addSubview(maximizeIcon, positioned: .above, relativeTo: nil) - [closeIcon, minimizeIcon, maximizeIcon].forEach { - $0.topAnchor.constraint(equalTo: thumbnail.topAnchor, constant: 2).isActive = true - } - closeIcon.leftAnchor.constraint(equalTo: thumbnail.leftAnchor, constant: 3).isActive = true - let windowsControlSpacing = CGFloat(6) - minimizeIcon.leftAnchor.constraint(equalTo: closeIcon.rightAnchor, constant: windowsControlSpacing).isActive = true - maximizeIcon.leftAnchor.constraint(equalTo: minimizeIcon.rightAnchor, constant: windowsControlSpacing).isActive = true [closeIcon, minimizeIcon, maximizeIcon].forEach { $0.isHidden = true } } @@ -80,6 +75,14 @@ class ThumbnailView: NSStackView { isShowingWindowControls = shouldShow [closeIcon, minimizeIcon, maximizeIcon].forEach { $0.isHidden = !shouldShow } } + if isShowingWindowControls { + [closeIcon, minimizeIcon, maximizeIcon].enumerated().forEach { (i, icon: TrafficLightButton) in + icon.setFrameOrigin(.init( + x: 3 + (ThumbnailView.windowsControlSpacing + ThumbnailView.windowsControlSize) * CGFloat(i), + y: thumbnail.frame.height - ThumbnailView.windowsControlSize - 2)) + icon.window_ = window_ + } + } } func highlight(_ highlight: Bool) { @@ -171,7 +174,6 @@ class ThumbnailView: NSStackView { self.mouseUpCallback = { () -> Void in App.app.focusSelectedWindow(element) } self.mouseMovedCallback = { () -> Void in Windows.updateFocusedWindowIndex(index) } showOrHideWindowControls(false) - [closeIcon, minimizeIcon, maximizeIcon].forEach { $0.hovered(false) } // force a display to avoid flickering; see https://github.com/lwouis/alt-tab-macos/issues/197 // quirk: display() should be called last as it resets thumbnail.frame.size somehow if labelChanged { @@ -198,8 +200,8 @@ class ThumbnailView: NSStackView { func getAccessibilityHelp(_ appName: String?, _ dockLabel: Int?) -> String { [appName, dockLabel.map { getAccessibilityTextForBadge($0) }] - .compactMap { $0 } - .joined(separator: " - ") + .compactMap { $0 } + .joined(separator: " - ") } func getAccessibilityTextForBadge(_ dockLabel: Int) -> String { @@ -245,34 +247,11 @@ class ThumbnailView: NSStackView { if Preferences.mouseHoverEnabled && !isHighlighted { mouseMovedCallback() } - hoverWindowControls() - } - - func hoverWindowControls() { - let controls = [closeIcon, minimizeIcon, maximizeIcon] - if let target = thumbnail.hitTest(convert(window!.convertPoint(fromScreen: NSEvent.mouseLocation), from: nil)), - target is NSImageView { - if let control = (controls.first { $0 == target }) { - control.hovered(true) - controls.filter { $0 != control }.forEach { $0.hovered(false) } - } else { - controls.forEach { $0.hovered(false) } - } - } } override func mouseUp(with event: NSEvent) { if event.clickCount >= 1 { - let target = thumbnail.hitTest(convert(event.locationInWindow, from: nil)) - if target == closeIcon { - window_!.close() - } else if target == minimizeIcon { - window_!.minDemin() - } else if target == maximizeIcon { - window_!.toggleFullscreen() - } else { - mouseUpCallback() - } + mouseUpCallback() } } diff --git a/src/ui/main-window/WindowControlView.swift b/src/ui/main-window/WindowControlView.swift deleted file mode 100644 index 5f3af100..00000000 --- a/src/ui/main-window/WindowControlView.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Cocoa - -class WindowControlView: NSImageView { - var originalImage: NSImage! - var hoveredImage: NSImage! - var size: NSSize! - - convenience init(_ imageName: String, _ size_: Int) { - self.init() - translatesAutoresizingMaskIntoConstraints = false - size = NSSize(width: size_, height: size_) - let image = NSImage.initCopy(imageName) - image.size = size - originalImage = image - hoveredImage = image.tinted(.init(white: 0, alpha: 0.25)) - hovered(false) - setAccessibilityLabel(imageName) - setAccessibilityRole(.button) - } - - func hovered(_ isHovered: Bool) { - image = isHovered ? hoveredImage : originalImage - image!.size = size - frame.size = size - } -}