diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index f2688f67..f6a5ce03 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -65,6 +65,10 @@ A8D5A7D62A91384D004EA5BB /* DirectionSelectorSquareSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D5A7D52A91384D004EA5BB /* DirectionSelectorSquareSegment.swift */; }; A8D5A7D82A913862004EA5BB /* DirectionSelectorCircleSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D5A7D72A913862004EA5BB /* DirectionSelectorCircleSegment.swift */; }; A8D5A7DA2A913B4C004EA5BB /* RadialMenuDirectionSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D5A7D92A913B4C004EA5BB /* RadialMenuDirectionSelectorView.swift */; }; + A8D6D2FF2B6C87F80061B11F /* PaddingConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D6D2FE2B6C87F80061B11F /* PaddingConfigurationView.swift */; }; + A8D6D3012B6C894C0061B11F /* PaddingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D6D3002B6C894C0061B11F /* PaddingModel.swift */; }; + A8D6D3032B6C8D750061B11F /* PaddingPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D6D3022B6C8D750061B11F /* PaddingPreviewView.swift */; }; + A8D6D3052B6C92F20061B11F /* WallpaperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D6D3042B6C92F20061B11F /* WallpaperView.swift */; }; A8DCC97B2980D5F500D41065 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = A8DCC97A2980D5F500D41065 /* Defaults */; }; A8DCC9882981B9E100D41065 /* RadialMenuSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8DCC9872981B9E100D41065 /* RadialMenuSettingsView.swift */; }; A8DCC98A2981F43F00D41065 /* PreviewSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8DCC9892981F43F00D41065 /* PreviewSettingsView.swift */; }; @@ -141,6 +145,10 @@ A8D5A7D52A91384D004EA5BB /* DirectionSelectorSquareSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectionSelectorSquareSegment.swift; sourceTree = ""; }; A8D5A7D72A913862004EA5BB /* DirectionSelectorCircleSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectionSelectorCircleSegment.swift; sourceTree = ""; }; A8D5A7D92A913B4C004EA5BB /* RadialMenuDirectionSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialMenuDirectionSelectorView.swift; sourceTree = ""; }; + A8D6D2FE2B6C87F80061B11F /* PaddingConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingConfigurationView.swift; sourceTree = ""; }; + A8D6D3002B6C894C0061B11F /* PaddingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingModel.swift; sourceTree = ""; }; + A8D6D3022B6C8D750061B11F /* PaddingPreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaddingPreviewView.swift; sourceTree = ""; }; + A8D6D3042B6C92F20061B11F /* WallpaperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperView.swift; sourceTree = ""; }; A8DCC9872981B9E100D41065 /* RadialMenuSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialMenuSettingsView.swift; sourceTree = ""; }; A8DCC9892981F43F00D41065 /* PreviewSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewSettingsView.swift; sourceTree = ""; }; A8E1575E298654960005761C /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; @@ -184,6 +192,8 @@ A80900D32AA3F9F20085C63B /* VisualEffectView.swift */, A85B560D2AAAD62C00386ACE /* EventMonitor.swift */, A86B97AC2AB79E2500099D7F /* ShakeEffect.swift */, + A8D6D3002B6C894C0061B11F /* PaddingModel.swift */, + A8D6D3042B6C92F20061B11F /* WallpaperView.swift */, A8063A722B19891900EAB3D9 /* grid.metal */, ); path = Utilities; @@ -273,8 +283,8 @@ isa = PBXGroup; children = ( A84497C82B393595003D4CF3 /* CustomKeybindView.swift */, - A84497CC2B3A52C7003D4CF3 /* PreviewWindowButton.swift */, A84497CA2B395D8C003D4CF3 /* AnchorPicker.swift */, + A84497CC2B3A52C7003D4CF3 /* PreviewWindowButton.swift */, ); path = "Custom Keybinds"; sourceTree = ""; @@ -324,6 +334,7 @@ children = ( A8063A6D2B19599D00EAB3D9 /* SettingsView.swift */, A882660729809F6F00BCB197 /* GeneralSettingsView.swift */, + A8D6D3062B6C99C10061B11F /* Padding */, A8DCC9872981B9E100D41065 /* RadialMenuSettingsView.swift */, A8DCC9892981F43F00D41065 /* PreviewSettingsView.swift */, A84497CE2B3A57E8003D4CF3 /* Keybindings */, @@ -349,6 +360,15 @@ path = "Custom Window Sizes"; sourceTree = ""; }; + A8D6D3062B6C99C10061B11F /* Padding */ = { + isa = PBXGroup; + children = ( + A8D6D2FE2B6C87F80061B11F /* PaddingConfigurationView.swift */, + A8D6D3022B6C8D750061B11F /* PaddingPreviewView.swift */, + ); + path = Padding; + sourceTree = ""; + }; A8E59C2C297F5E9A0064D4BA = { isa = PBXGroup; children = ( @@ -513,12 +533,14 @@ A8D5A7D62A91384D004EA5BB /* DirectionSelectorSquareSegment.swift in Sources */, A8BE09DB2B113FD700DBB242 /* KeycorderModel.swift in Sources */, A82521EC29E234EB00139654 /* AboutViewController.swift in Sources */, + A8D6D3012B6C894C0061B11F /* PaddingModel.swift in Sources */, A86DAE2C2B3E31F800B968F0 /* CustomCyclingKeybindItemView.swift in Sources */, A82DDBDE2AEC736300D7F974 /* AnimationConfiguration.swift in Sources */, A84497CD2B3A52C7003D4CF3 /* PreviewWindowButton.swift in Sources */, A8789F6729805B190040512E /* RadialMenuView.swift in Sources */, A8330ABD2A3AC0CA00673C8D /* Bundle+Extensions.swift in Sources */, A86B97AD2AB79E2500099D7F /* ShakeEffect.swift in Sources */, + A8D6D3032B6C8D750061B11F /* PaddingPreviewView.swift in Sources */, A82740982AB00FCE00B9BDC5 /* Color+Extensions.swift in Sources */, A869C1A12B38C6E600AD1A84 /* StageManager.swift in Sources */, A8DCC9882981B9E100D41065 /* RadialMenuSettingsView.swift in Sources */, @@ -528,6 +550,7 @@ A80900D52AA3F9F30085C63B /* VisualEffectView.swift in Sources */, A8330AC12A3AC13100673C8D /* Defaults+Extensions.swift in Sources */, A80900D42AA3F9F30085C63B /* BetaIndicator.swift in Sources */, + A8D6D2FF2B6C87F80061B11F /* PaddingConfigurationView.swift in Sources */, A8E1575F298654960005761C /* AboutView.swift in Sources */, A8DCC98A2981F43F00D41065 /* PreviewSettingsView.swift in Sources */, A87376F62AA288EB001890F4 /* Window.swift in Sources */, @@ -551,6 +574,7 @@ A86CB7332A3D22E7006A78F2 /* WindowEngine.swift in Sources */, A8E59C39297F5E9A0064D4BA /* LoopApp.swift in Sources */, A8330ACB2A3AC1C000673C8D /* Angle+Extensions.swift in Sources */, + A8D6D3052B6C92F20061B11F /* WallpaperView.swift in Sources */, A8F0125D2AEDFEC70017307F /* KeybindingsSettingsView.swift in Sources */, A864F4682AA660CD00579738 /* WindowDragManager.swift in Sources */, A8504D2D2A85832F00C2EFDA /* SoftwareUpdater.swift in Sources */, diff --git a/Loop/Extensions/Defaults+Extensions.swift b/Loop/Extensions/Defaults+Extensions.swift index 26068eab..776296ef 100644 --- a/Loop/Extensions/Defaults+Extensions.swift +++ b/Loop/Extensions/Defaults+Extensions.swift @@ -18,7 +18,7 @@ extension Defaults.Keys { static let timesLooped = Key("timesLooped", default: 0) static let windowSnapping = Key("windowSnapping", default: false) // BETA static let animateWindowResizes = Key("animateWindowResizes", default: false) // BETA - static let windowPadding = Key("windowPadding", default: 0) + static let padding = Key("padding", default: .zero) static let restoreWindowFrameOnDrag = Key("restoreWindowFrameOnDrag", default: true) static let resizeWindowUnderCursor = Key("resizeWindowUnderCursor", default: false) static let focusWindowOnResize = Key("focusWindowOnResize", default: true) diff --git a/Loop/Preview Window/PreviewView.swift b/Loop/Preview Window/PreviewView.swift index c6353853..af710c9a 100644 --- a/Loop/Preview Window/PreviewView.swift +++ b/Loop/Preview Window/PreviewView.swift @@ -26,11 +26,13 @@ struct PreviewView: View { @Default(.useGradient) var useGradient @Default(.previewPadding) var previewPadding - @Default(.windowPadding) var windowPadding + @Default(.padding) var padding @Default(.previewCornerRadius) var previewCornerRadius @Default(.previewBorderThickness) var previewBorderThickness @Default(.animationConfiguration) var animationConfiguration + @State var windowEdgesToPad: Edge.Set = [] + var body: some View { GeometryReader { geo in ZStack { @@ -52,7 +54,8 @@ struct PreviewView: View { lineWidth: previewBorderThickness ) } - .padding(windowPadding + previewPadding + previewBorderThickness / 2) + .padding(previewPadding + previewBorderThickness / 2) + .padding(windowEdgesToPad, padding.window / 2) .frame( width: self.currentAction.previewWindowWidth(geo.size.width, window), @@ -64,6 +67,11 @@ struct PreviewView: View { y: self.currentAction.previewWindowYOffset(geo.size.height, window) ) } + .padding(.top, padding.totalTopPadding) + .padding(.bottom, padding.bottom) + .padding(.leading, padding.left) + .padding(.trailing, padding.right) + .opacity(currentAction.direction == .noAction ? 0 : 1) .animation(animationConfiguration.previewWindowAnimation, value: currentAction) .onReceive(.updateUIDirection) { obj in @@ -86,6 +94,15 @@ struct PreviewView: View { if self.previewMode { self.currentAction = .init(.maximize) } + + self.windowEdgesToPad = Edge.Set.all.subtracting( + self.currentAction.getEdgesTouchingScreen() + ) + } + .onChange(of: self.currentAction.getEdgesTouchingScreen()) { _ in + self.windowEdgesToPad = Edge.Set.all.subtracting( + self.currentAction.getEdgesTouchingScreen() + ) } } } diff --git a/Loop/Settings/GeneralSettingsView.swift b/Loop/Settings/GeneralSettingsView.swift index 3588c464..666b4c8c 100644 --- a/Loop/Settings/GeneralSettingsView.swift +++ b/Loop/Settings/GeneralSettingsView.swift @@ -23,7 +23,7 @@ struct GeneralSettingsView: View { @Default(.notificationWhenIconUnlocked) var notificationWhenIconUnlocked @Default(.timesLooped) var timesLooped @Default(.animateWindowResizes) var animateWindowResizes - @Default(.windowPadding) var windowPadding + @Default(.padding) var padding @Default(.windowSnapping) var windowSnapping @Default(.animationConfiguration) var animationConfiguration @Default(.restoreWindowFrameOnDrag) var restoreWindowFrameOnDrag @@ -33,6 +33,8 @@ struct GeneralSettingsView: View { @State var userDisabledLoopNotifications: Bool = false @State var iconFooter: String? + @State var isConfiguringPadding: Bool = false + var body: some View { Form { Section("Behavior") { @@ -77,12 +79,15 @@ struct GeneralSettingsView: View { } } - Slider(value: $windowPadding, - in: 0...50, - step: 5, - minimumValueLabel: Text("0px"), - maximumValueLabel: Text("50px")) { - Text("Window Padding") + HStack { + Text("Padding") + Spacer() + Button("Configure…") { + self.isConfiguringPadding = true + } + } + .sheet(isPresented: self.$isConfiguringPadding) { + PaddingConfigurationView(isSheetShown: $isConfiguringPadding, paddingModel: $padding) } Toggle( diff --git a/Loop/Settings/Keybindings/Custom Cycling Keybinds/CustomCyclingKeybindView.swift b/Loop/Settings/Keybindings/Custom Cycling Keybinds/CustomCyclingKeybindView.swift index 001b0137..88ba3afc 100644 --- a/Loop/Settings/Keybindings/Custom Cycling Keybinds/CustomCyclingKeybindView.swift +++ b/Loop/Settings/Keybindings/Custom Cycling Keybinds/CustomCyclingKeybindView.swift @@ -131,6 +131,7 @@ struct CustomCyclingKeybindView: View { } .frame(width: 450) .fixedSize(horizontal: false, vertical: true) + .background(.background) .onAppear { self.cycleDirections = self.action.cycle ?? [] diff --git a/Loop/Settings/Keybindings/Custom Keybinds/AnchorPicker.swift b/Loop/Settings/Keybindings/Custom Keybinds/AnchorPicker.swift index b7e23064..ee8bceab 100644 --- a/Loop/Settings/Keybindings/Custom Keybinds/AnchorPicker.swift +++ b/Loop/Settings/Keybindings/Custom Keybinds/AnchorPicker.swift @@ -43,17 +43,7 @@ struct AnchorPicker: View { } } .animation(.snappy, value: self.anchor) - .aspectRatio(16/10, contentMode: .fit) .padding(8) - .background { - if let screen = NSScreen.screenWithMouse, - let url = NSWorkspace.shared.desktopImageURL(for: screen), - let image = NSImage(contentsOf: url) { - Image(nsImage: image) - .resizable() - .aspectRatio(contentMode: .fill) - } - } } @ViewBuilder diff --git a/Loop/Settings/Keybindings/Custom Keybinds/CustomKeybindView.swift b/Loop/Settings/Keybindings/Custom Keybinds/CustomKeybindView.swift index 6ce631f3..098553c3 100644 --- a/Loop/Settings/Keybindings/Custom Keybinds/CustomKeybindView.swift +++ b/Loop/Settings/Keybindings/Custom Keybinds/CustomKeybindView.swift @@ -23,9 +23,13 @@ struct CustomKeybindView: View { } Section { - AnchorPicker(anchor: self.$action.anchor) - .ignoresSafeArea() - .padding(-10) + ZStack { + WallpaperView().equatable() + AnchorPicker(anchor: self.$action.anchor) + } + .ignoresSafeArea() + .padding(-10) + .aspectRatio(16/10, contentMode: .fit) } if self.action.anchor == .center || self.action.anchor == .macOSCenter { @@ -133,6 +137,7 @@ struct CustomKeybindView: View { } .frame(width: 400) .fixedSize(horizontal: false, vertical: true) + .background(.background) .onAppear { if self.action.measureSystem == nil { diff --git a/Loop/Settings/Padding/PaddingConfigurationView.swift b/Loop/Settings/Padding/PaddingConfigurationView.swift new file mode 100644 index 00000000..a01711b3 --- /dev/null +++ b/Loop/Settings/Padding/PaddingConfigurationView.swift @@ -0,0 +1,152 @@ +// +// PaddingConfigurationView.swift +// Loop +// +// Created by Kai Azim on 2024-02-01. +// + +import SwiftUI + +struct PaddingConfigurationView: View { + @Binding var isSheetShown: Bool + @Binding var paddingModel: PaddingModel + @State var useSteppers: Bool = false + + var body: some View { + VStack { + Form { + Section("Padding") { + Toggle("Custom Screen Padding", isOn: $paddingModel.configureScreenPadding) + Toggle("Use Custom Values", isOn: $useSteppers) + } + + Section(content: { + ZStack { + WallpaperView().equatable() + PaddingPreviewView($paddingModel) + } + .ignoresSafeArea() + .padding(-10) + .aspectRatio(16/10, contentMode: .fit) + }, footer: { + HStack { + Text("This preview is not to scale.") + .font(.caption) + .foregroundStyle(.secondary) + Spacer() + } + }) + + if paddingModel.configureScreenPadding { + Section { + paddingAdjuster("Window Gaps", value: $paddingModel.window.toDoubleBinding()) + paddingAdjuster( + "External Bar", + value: $paddingModel.externalBar.toDoubleBinding(), + description: "Use this if you are using a custom menubar." + ) + } + + Section("Screen Padding") { + paddingAdjuster("Top", value: $paddingModel.top.toDoubleBinding()) + paddingAdjuster("Bottom", value: $paddingModel.bottom.toDoubleBinding()) + paddingAdjuster("Right", value: $paddingModel.right.toDoubleBinding()) + paddingAdjuster("Left", value: $paddingModel.left.toDoubleBinding()) + } + } else { + paddingAdjuster( + "Padding", + value: Binding( + get: { + paddingModel.window + }, + set: { + paddingModel.window = $0 + paddingModel.top = $0 + paddingModel.bottom = $0 + paddingModel.right = $0 + paddingModel.left = $0 + } + ) + ) + } + } + .formStyle(.grouped) + .scrollDisabled(true) + .onChange(of: paddingModel.configureScreenPadding) { _ in + if !paddingModel.configureScreenPadding { + paddingModel.top = paddingModel.window + paddingModel.bottom = paddingModel.window + paddingModel.right = paddingModel.window + paddingModel.left = paddingModel.window + } + } + + HStack { + Button { + isSheetShown = false + } label: { + Text("Done") + } + .controlSize(.large) + } + .offset(y: -14) + } + .frame(width: 400) + .fixedSize(horizontal: false, vertical: true) + .background(.background) + } + + @ViewBuilder + func paddingAdjuster(_ title: String, value: Binding, description: String? = nil) -> some View { + VStack(alignment: .leading) { + if self.useSteppers { + paddingStepper(title, value: value) + } else { + paddingSlider(title, value: value) + } + + if let description = description { + Text(description) + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + + @ViewBuilder + func paddingSlider(_ title: String, value: Binding) -> some View { + Slider( + value: value, + in: 0...50, + step: 5, + minimumValueLabel: Text("0px"), + maximumValueLabel: Text("50px") + ) { + Text(title) + } + } + + @ViewBuilder + func paddingStepper(_ title: String, value: Binding) -> some View { + HStack { + Stepper( + title, + value: value, + in: 0...100, + format: .number + ) + Text("px") + } + } +} + +extension Binding where Value == CGFloat { + fileprivate func toDoubleBinding() -> Binding { + Binding(get: { + Double(self.wrappedValue) + }, set: { + self.wrappedValue = CGFloat($0) + }) + } +} diff --git a/Loop/Settings/Padding/PaddingPreviewView.swift b/Loop/Settings/Padding/PaddingPreviewView.swift new file mode 100644 index 00000000..19cd2f9f --- /dev/null +++ b/Loop/Settings/Padding/PaddingPreviewView.swift @@ -0,0 +1,44 @@ +// +// PaddingPreviewView.swift +// Loop +// +// Created by Kai Azim on 2024-02-01. +// + +import SwiftUI + +struct PaddingPreviewView: View { + + @Binding var paddingModel: PaddingModel + + init(_ paddingModel: Binding) { + self._paddingModel = paddingModel + } + + var body: some View { + ZStack { + HStack(spacing: paddingModel.window / 2) { + blurredWindow() + + VStack(spacing: paddingModel.window / 2) { + blurredWindow() + blurredWindow() + } + } + .padding(.top, paddingModel.totalTopPadding / 2) + .padding(.bottom, paddingModel.bottom / 2) + .padding(.leading, paddingModel.left / 2) + .padding(.trailing, paddingModel.right / 2) + } + } + + @ViewBuilder + func blurredWindow() -> some View { + VisualEffectView(material: .hudWindow, blendingMode: .withinWindow) + .overlay { + RoundedRectangle(cornerRadius: 5) + .strokeBorder(.white.opacity(0.1), lineWidth: 2) + } + .clipShape(.rect(cornerRadius: 5)) + } +} diff --git a/Loop/Utilities/PaddingModel.swift b/Loop/Utilities/PaddingModel.swift new file mode 100644 index 00000000..b66222e9 --- /dev/null +++ b/Loop/Utilities/PaddingModel.swift @@ -0,0 +1,46 @@ +// +// PaddingModel.swift +// Loop +// +// Created by Kai Azim on 2024-02-01. +// + +import SwiftUI +import Defaults + +struct PaddingModel: Codable, Defaults.Serializable { + var window: CGFloat { + didSet { window = max(window, 0) } + } + var externalBar: CGFloat { + didSet { externalBar = max(externalBar, 0) } + } + var top: CGFloat { + didSet { top = max(top, 0) } + } + var bottom: CGFloat { + didSet { bottom = max(bottom, 0) } + } + var right: CGFloat { + didSet { right = max(right, 0) } + } + var left: CGFloat { + didSet { left = max(left, 0) } + } + + var configureScreenPadding: Bool + + var totalTopPadding: CGFloat { + self.top + externalBar + } + + static var zero = PaddingModel( + window: 0, + externalBar: 0, + top: 0, + bottom: 0, + right: 0, + left: 0, + configureScreenPadding: false + ) +} diff --git a/Loop/Utilities/WallpaperView.swift b/Loop/Utilities/WallpaperView.swift new file mode 100644 index 00000000..e6c3e42a --- /dev/null +++ b/Loop/Utilities/WallpaperView.swift @@ -0,0 +1,30 @@ +// +// WallpaperView.swift +// Loop +// +// Created by Kai Azim on 2024-02-01. +// + +import SwiftUI + +// By making this equatable, we won't refresh the view everytime something else changes +// Make sure to apply .equatable() +struct WallpaperView: View, Equatable { + static func == (lhs: WallpaperView, rhs: WallpaperView) -> Bool { + true + } + + var body: some View { + if let screen = NSScreen.screenWithMouse, + let url = NSWorkspace.shared.desktopImageURL(for: screen), + let image = NSImage(contentsOf: url) { + + GeometryReader { geo in + Image(nsImage: image) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: geo.size.width, height: geo.size.height) + } + } + } +} diff --git a/Loop/Window Management/WindowAction.swift b/Loop/Window Management/WindowAction.swift index c66356f5..fddc6c91 100644 --- a/Loop/Window Management/WindowAction.swift +++ b/Loop/Window Management/WindowAction.swift @@ -58,6 +58,92 @@ struct WindowAction: Codable, Identifiable, Hashable, Equatable, Defaults.Serial } return nil } + + // Returns the window frame within the boundaries of (0, 0) to (1, 1) + // Will be on the screen with mouse if needed. + func getFrameMultiplyValues() -> CGRect { + guard self.direction != .cycle else { + return .zero + } + + let bounds = CGRect(x: 0, y: 0, width: 1, height: 1) + var result = CGRect.zero + + if let frameMultiplyValues = direction.frameMultiplyValues { + result.origin.x = bounds.width * frameMultiplyValues.minX + result.origin.y = bounds.height * frameMultiplyValues.minY + result.size.width = bounds.width * frameMultiplyValues.width + result.size.height = bounds.height * frameMultiplyValues.height + } else { + if direction == .custom, let screenFrame = NSScreen.screenWithMouse?.frame { + switch measureSystem { + case .percentage: + result.size.width = bounds.width * ((width ?? 0) / 100.0) + result.size.height = bounds.height * ((height ?? 0) / 100.0) + case .pixels: + result.size.width += (width ?? 0) / screenFrame.width + result.size.height += (height ?? 0) / screenFrame.height + case .none: + break + } + + switch anchor { + case .topLeft: + break + case .top: + result.origin.x = screenFrame.midX - result.width / 2 + case .topRight: + result.origin.x = screenFrame.maxX - result.width + case .right: + result.origin.x = screenFrame.maxX - result.width + result.origin.y = screenFrame.midY - result.height / 2 + case .bottomRight: + result.origin.x = screenFrame.maxX - result.width + result.origin.y = screenFrame.maxY - result.height + case .bottom: + result.origin.x = screenFrame.midX - result.width / 2 + result.origin.y = screenFrame.maxY - result.height + case .bottomLeft: + result.origin.y = screenFrame.maxY - result.height + case .left: + result.origin.y = screenFrame.midY - result.height / 2 + case .center: + result.origin.x = screenFrame.midX - result.width / 2 + result.origin.y = screenFrame.midY - result.height / 2 + case .macOSCenter: + let yOffset = WindowEngine.getMacOSCenterYOffset(result.height, screenHeight: screenFrame.height) + result.origin.x = screenFrame.midX - result.width / 2 + result.origin.y = (screenFrame.midY - result.height / 2) + yOffset + case .none: + break + } + } + } + return result + } + + func getEdgesTouchingScreen() -> Edge.Set { + guard let frameMultiplyValues = direction.frameMultiplyValues else { + return [] + } + + var result: Edge.Set = [] + + if frameMultiplyValues.minX == 0 { + result.insert(.leading) + } + if frameMultiplyValues.maxX == 1 { + result.insert(.trailing) + } + if frameMultiplyValues.minY == 0 { + result.insert(.top) + } + if frameMultiplyValues.maxY == 1 { + result.insert(.bottom) + } + + return result + } } // MARK: - Import/Export @@ -325,7 +411,7 @@ extension WindowAction { if self.direction == .macOSCenter { let yOffset = WindowEngine.getMacOSCenterYOffset( - previewHeight - (Defaults[.windowPadding] * 2), + previewHeight - (Defaults[.padding].window * 2), screenHeight: parentHeight ) yLocation = (parentHeight / 2) - (previewHeight / 2) + yOffset @@ -350,7 +436,7 @@ extension WindowAction { if self.direction == .center || self.direction == .macOSCenter, let window = window { width = window.frame.width - width += Defaults[.windowPadding] * 2 + width += Defaults[.padding].window * 2 } return width @@ -372,7 +458,7 @@ extension WindowAction { if self.direction == .center || self.direction == .macOSCenter, let window = window { height = window.frame.height - height += Defaults[.windowPadding] * 2 + height += Defaults[.padding].window * 2 } return height diff --git a/Loop/Window Management/WindowEngine.swift b/Loop/Window Management/WindowEngine.swift index f1869c3f..f747e328 100644 --- a/Loop/Window Management/WindowEngine.swift +++ b/Loop/Window Management/WindowEngine.swift @@ -78,11 +78,11 @@ struct WindowEngine { let nsScreenFrame = screenFrame.flipY! if (targetWindowFrame.minX + minSize.width) > nsScreenFrame.maxX { - targetWindowFrame.origin.x = nsScreenFrame.maxX - minSize.width - Defaults[.windowPadding] + targetWindowFrame.origin.x = nsScreenFrame.maxX - minSize.width - Defaults[.padding].right } if (targetWindowFrame.minY + minSize.height) > nsScreenFrame.maxY { - targetWindowFrame.origin.y = nsScreenFrame.maxY - minSize.height - Defaults[.windowPadding] + targetWindowFrame.origin.y = nsScreenFrame.maxY - minSize.height - Defaults[.padding].bottom } window.setFrame(targetWindowFrame, animate: true) { @@ -178,13 +178,6 @@ struct WindowEngine { newWindowFrame.origin = screenFrame.origin switch direction { - case .custom: - guard - let newFrame = WindowEngine.generateCustomWindowFrame(action, screenFrame) - else { - return nil - } - newWindowFrame = newFrame case .center: newWindowFrame = CGRect( x: screenFrame.midX - windowFrame.width / 2, @@ -215,7 +208,7 @@ struct WindowEngine { return nil } default: - guard let frameMultiplyValues = direction.frameMultiplyValues else { return nil} + let frameMultiplyValues = action.getFrameMultiplyValues() newWindowFrame.origin.x += screenFrame.width * frameMultiplyValues.minX newWindowFrame.origin.y += screenFrame.height * frameMultiplyValues.minY newWindowFrame.size.width += screenFrame.width * frameMultiplyValues.width @@ -225,60 +218,6 @@ struct WindowEngine { return newWindowFrame } - private static func generateCustomWindowFrame(_ action: WindowAction, _ screenFrame: CGRect) -> CGRect? { - guard - action.direction == .custom, - let measureSystem = action.measureSystem, - let anchor = action.anchor, - let width = action.width, - let height = action.height - else { - return nil - } - var newWindowFrame: CGRect = .zero - newWindowFrame.origin = screenFrame.origin - - switch measureSystem { - case .percentage: - newWindowFrame.size.width += screenFrame.width * (width / 100.0) - newWindowFrame.size.height += screenFrame.height * (height / 100.0) - case .pixels: - newWindowFrame.size.width += width - newWindowFrame.size.height += height - } - - switch anchor { - case .topLeft: - break - case .top: - newWindowFrame.origin.x = screenFrame.midX - newWindowFrame.width / 2 - case .topRight: - newWindowFrame.origin.x = screenFrame.maxX - newWindowFrame.width - case .right: - newWindowFrame.origin.x = screenFrame.maxX - newWindowFrame.width - newWindowFrame.origin.y = screenFrame.midY - newWindowFrame.height / 2 - case .bottomRight: - newWindowFrame.origin.x = screenFrame.maxX - newWindowFrame.width - newWindowFrame.origin.y = screenFrame.maxY - newWindowFrame.height - case .bottom: - newWindowFrame.origin.x = screenFrame.midX - newWindowFrame.width / 2 - newWindowFrame.origin.y = screenFrame.maxY - newWindowFrame.height - case .bottomLeft: - newWindowFrame.origin.y = screenFrame.maxY - newWindowFrame.height - case .left: - newWindowFrame.origin.y = screenFrame.midY - newWindowFrame.height / 2 - case .center: - newWindowFrame.origin.x = screenFrame.midX - newWindowFrame.width / 2 - newWindowFrame.origin.y = screenFrame.midY - newWindowFrame.height / 2 - case .macOSCenter: - let yOffset = getMacOSCenterYOffset(newWindowFrame.height, screenHeight: screenFrame.height) - newWindowFrame.origin.x = screenFrame.midX - newWindowFrame.width / 2 - newWindowFrame.origin.y = (screenFrame.midY - newWindowFrame.height / 2) + yOffset - } - - return newWindowFrame - } - static func getMacOSCenterYOffset(_ windowHeight: CGFloat, screenHeight: CGFloat) -> CGFloat { let halfScreenHeight = screenHeight / 2 let windowHeightPercent = windowHeight / screenHeight @@ -291,10 +230,15 @@ struct WindowEngine { /// - direction: The direction the window WILL be resized to /// - Returns: CGRect with padding applied private static func applyPadding(_ windowFrame: CGRect, _ screenFrame: CGRect, _ action: WindowAction) -> CGRect { - let padding = Defaults[.windowPadding] - let halfPadding = Defaults[.windowPadding] / 2 + let padding = Defaults[.padding] + let halfPadding = padding.window / 2 + + var paddedScreenFrame = screenFrame + paddedScreenFrame = paddedScreenFrame.padding(.top, padding.totalTopPadding) + paddedScreenFrame = paddedScreenFrame.padding(.bottom, padding.bottom) + paddedScreenFrame = paddedScreenFrame.padding(.leading, padding.left) + paddedScreenFrame = paddedScreenFrame.padding(.trailing, padding.right) - let paddedScreenFrame = screenFrame.padding(.all, padding) var paddedWindowFrame = windowFrame.intersection(paddedScreenFrame) if action.direction == .macOSCenter, @@ -342,11 +286,11 @@ struct WindowEngine { var fixedWindowFrame = windowFrame if fixedWindowFrame.maxX > screenFrame.maxX { - fixedWindowFrame.origin.x = screenFrame.maxX - fixedWindowFrame.width - Defaults[.windowPadding] + fixedWindowFrame.origin.x = screenFrame.maxX - fixedWindowFrame.width - Defaults[.padding].right } if fixedWindowFrame.maxY > screenFrame.maxY { - fixedWindowFrame.origin.y = screenFrame.maxY - fixedWindowFrame.height - Defaults[.windowPadding] + fixedWindowFrame.origin.y = screenFrame.maxY - fixedWindowFrame.height - Defaults[.padding].bottom } window.setPosition(fixedWindowFrame.origin)