diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index 23a20603..763a12a0 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -531,8 +531,8 @@ }, "it" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Accent opacity" + "state" : "translated", + "value" : "Opacità colore dettagli" } }, "ja" : { @@ -1766,8 +1766,8 @@ }, "it" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Background" + "state" : "translated", + "value" : "Sfondo" } }, "ja" : { @@ -5320,8 +5320,8 @@ }, "it" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Enable blur" + "state" : "translated", + "value" : "Abilita sfocatura" } }, "ja" : { @@ -5980,8 +5980,8 @@ }, "it" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Fill Available Space" + "state" : "translated", + "value" : "Riempi spazio disponibile" } }, "ja" : { @@ -22205,7 +22205,7 @@ }, "it" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "Riavvia per completare" } }, diff --git a/Loop/Private APIs/SkyLightToolBelt.swift b/Loop/Private APIs/SkyLightToolBelt.swift index 0615edf1..139bc6b5 100644 --- a/Loop/Private APIs/SkyLightToolBelt.swift +++ b/Loop/Private APIs/SkyLightToolBelt.swift @@ -21,13 +21,17 @@ enum SkyLightToolBelt { return false } - var wid = windowID var psn = ProcessSerialNumber() let status = GetProcessForPID(pid, &psn) - var cgStatus = SLPSSetFrontProcessWithOptions( + guard status == noErr else { + Log.error("Failed to get PSN: \(status)", category: .skyLightToolBelt) + return false + } + + let cgStatus = SLPSSetFrontProcessWithOptions( &psn, - wid, + windowID, kCPSUserGenerated ) diff --git a/Loop/Window Action Indicators/Preview Window/PreviewController.swift b/Loop/Window Action Indicators/Preview Window/PreviewController.swift index 406dd55b..26f4017e 100644 --- a/Loop/Window Action Indicators/Preview Window/PreviewController.swift +++ b/Loop/Window Action Indicators/Preview Window/PreviewController.swift @@ -46,6 +46,7 @@ final class PreviewController { panel.level = NSWindow.Level(NSWindow.Level.screenSaver.rawValue - 1) panel.contentView = NSHostingView(rootView: PreviewView(viewModel: viewModel)) panel.collectionBehavior = .canJoinAllSpaces + panel.hasShadow = false panel.ignoresMouseEvents = true panel.orderFrontRegardless() controller = .init(window: panel) diff --git a/Loop/Window Action Indicators/Radial Menu/RadialMenuView.swift b/Loop/Window Action Indicators/Radial Menu/RadialMenuView.swift index f62cad62..81418821 100644 --- a/Loop/Window Action Indicators/Radial Menu/RadialMenuView.swift +++ b/Loop/Window Action Indicators/Radial Menu/RadialMenuView.swift @@ -12,6 +12,7 @@ import SwiftUI struct RadialMenuView: View { @Environment(\.luminareAnimation) private var luminareAnimation @Environment(\.appearsActive) private var appearsActive + @Environment(\.colorScheme) private var colorScheme @ObservedObject private var accentColorController: AccentColorController = .shared @ObservedObject private var viewModel: RadialMenuViewModel private let radialMenuSize: CGFloat = 100 @@ -53,24 +54,44 @@ struct RadialMenuView: View { /// but for now, we have disabled the materialization Liquid Glass transition. ZStack { if viewModel.isShown { - radialMenuFill() - .mask(directionSelectorMask) - .mask(radialMenuMask) - .glassEffect( - .regular.tint(accentColorController.color1.opacity(0.025)), - in: .rect(cornerRadius: radialMenuCornerRadius) - .inset(by: radialMenuThickness / 2) - .stroke(lineWidth: radialMenuThickness) - ) - .transition(.scale(scale: 1.25).combined(with: .opacity)) - } - } - .overlay { - if viewModel.isShown { - overlayImage() - .transition(.scale(scale: 1.25).combined(with: .opacity)) + ZStack { + radialMenuFill() + .mask(directionSelectorMask) + .glassEffect( + .regular.tint(accentColorController.color1.opacity(0.025)), + in: .rect(cornerRadius: radialMenuCornerRadius) // Using the radial menu thickness here causes a seam in the middle + ) + .mask(radialMenuMask) + + if appearsActive { + let borderColor: Color = colorScheme == .dark ? .white.opacity(0.25).mix(with: accentColorController.color1, by: 0.25) : .white + + // Since the glass is just masked to the radial menu shape, it will be missing its inner border. + // This emulates a liquid glass inner border. + let innerBorderThickness: CGFloat = 0.5 + RoundedRectangle(cornerRadius: radialMenuCornerRadius) + .inset(by: radialMenuThickness - innerBorderThickness) + .strokeBorder(lineWidth: innerBorderThickness) + .foregroundStyle(borderColor) + .mask { + LinearGradient( + colors: [ + .white, + .clear, + .white + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + } + } + + overlayImage() + } + .transition(.scale(scale: 1.25).combined(with: .opacity)) } } + .compositingGroup() .frame(width: radialMenuSize, height: radialMenuSize) .shadow(color: .black.opacity(viewModel.isShadowShown ? 0.2 : 0), radius: 10) .scaleEffect(viewModel.shouldFillRadialMenu ? 0.85 : 1.0) @@ -103,8 +124,8 @@ struct RadialMenuView: View { LinearGradient( gradient: Gradient( colors: [ - shouldAppearActive ? accentColorController.color1 : .systemGray, - shouldAppearActive ? accentColorController.color2 : .systemGray + accentColorController.color1, + accentColorController.color2 ] ), startPoint: .topLeading, diff --git a/Loop/Window Management/Window Manipulation/WindowEngine.swift b/Loop/Window Management/Window Manipulation/WindowEngine.swift index b647334c..c495af0d 100644 --- a/Loop/Window Management/Window Manipulation/WindowEngine.swift +++ b/Loop/Window Management/Window Manipulation/WindowEngine.swift @@ -56,11 +56,18 @@ enum WindowEngine { // Record first frame if needed WindowRecords.recordFirstIfNeeded(for: window) - // Defer recording action or undo + let storeAsFrame = WindowRecords.shouldStoreAsFinalFrame(action) + + /// If this action doesn't require storage as a frame, then record it beforehand. + /// Otherwise, this action will be recorded *after* resizing, such that its final frame is considered if undoing. + if !storeAsFrame { + WindowRecords.record(window, action) + } + defer { if action.direction == .undo { WindowRecords.removeLastAction(for: window) - } else { + } else if storeAsFrame { WindowRecords.record(window, action) } } diff --git a/Loop/Window Management/Window Manipulation/WindowRecords.swift b/Loop/Window Management/Window Manipulation/WindowRecords.swift index 2532587e..bf4b89c9 100644 --- a/Loop/Window Management/Window Manipulation/WindowRecords.swift +++ b/Loop/Window Management/Window Manipulation/WindowRecords.swift @@ -39,6 +39,16 @@ enum WindowRecords { Log.info("Recorded first for: \(window)", category: .windowRecords) } + /// Determines if an action should be recorded using its frame instead of the action applied onto it. + /// - Parameter action: the action to apply onto the window. + /// - Returns: Whether this action should be recorded with its final frame instead of using the action. + static func shouldStoreAsFinalFrame(_ action: WindowAction) -> Bool { + /// Actions that are stored as frames need to be recorded *after* resize. + /// These actions are context-dependent, and cannot simply be called as an action to restore the previous state. + let storeAsFrame = action.direction.willChangeScreen || action.willManipulateExistingWindowFrame + return storeAsFrame + } + /// Record a window's action in the records array /// - Parameters: /// - window: Window to record @@ -52,11 +62,7 @@ enum WindowRecords { return } - /// These actions are context-dependent, and cannot simply be called as an action to restore the previous state. - /// That is why these should be stored as frames instead of actions. - let storeAsFrame = action.direction.willChangeScreen || action.willManipulateExistingWindowFrame - - if storeAsFrame, let screen = ScreenUtility.screenContaining(window) { + if shouldStoreAsFinalFrame(action), let screen = ScreenUtility.screenContaining(window) { let customActionName = "autogenerated_record_\(action.getName())" let windowFrame = window.frame let adjustedBounds = PaddingSettings