Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions Overview/Layout/LayoutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ final class LayoutManager: ObservableObject {

// MARK: - Public Methods

func createLayout(name: String) -> Layout? {
func createLayout(name: String, windows: [Window]? = nil) -> Layout? {
guard isLayoutNameUnique(name) else {
logger.warning("Attempted to create layout with non-unique name: \(name)")
return nil
}

let currentWindows = windowServices.windowStorage.collectWindows()
let currentWindows = windows ?? windowServices.windowStorage.collectWindows()
let layout = Layout(name: name, windows: currentWindows)

layouts.append(layout)
Expand All @@ -45,7 +45,7 @@ final class LayoutManager: ObservableObject {
return layout
}

func updateLayout(id: UUID, name: String? = nil) {
func updateLayout(id: UUID, name: String? = nil, windows: [Window]? = nil) {
guard let index = layouts.firstIndex(where: { $0.id == id }) else {
logger.warning("Attempted to update non-existent layout: \(id)")
return
Expand All @@ -59,7 +59,12 @@ final class LayoutManager: ObservableObject {
return
}
layout.update(name: name)
} else {
}

if let windows = windows {
layout.update(windows: windows)
logger.info("Updated layout '\(layout.name)' with \(windows.count) windows")
} else if name == nil {
let currentWindows = windowServices.windowStorage.collectWindows()
layout.update(windows: currentWindows)
logger.info("Updated layout '\(layout.name)' with \(currentWindows.count) windows")
Expand Down
1 change: 1 addition & 0 deletions Overview/Layout/Settings/LayoutSettingsKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ extension Defaults.Keys {
static let storedLayouts = Key<Data?>("storedLayouts", default: nil)
static let launchLayoutUUID = Key<UUID?>("launchLayoutUUID", default: nil)
static let closeWindowsOnApply = Key<Bool>("closeWindowsOnApply", default: true)
static let includeWindowNames = Key<Bool>("includeWindowNames", default: false)
}
10 changes: 7 additions & 3 deletions Overview/Layout/Settings/LayoutSettingsTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct LayoutSettingsTab: View {

// Layout Settings
@Default(.launchLayoutUUID) private var launchLayoutUUID
@Default(.includeWindowNames) private var includeWindowNames

init(windowManager: WindowManager, layoutManager: LayoutManager) {
self.layoutManager = layoutManager
Expand Down Expand Up @@ -151,6 +152,8 @@ struct LayoutSettingsTab: View {

Defaults.Toggle(
"Close all windows when applying layouts", key: .closeWindowsOnApply)
Defaults.Toggle(
"Save window names with layout", key: .includeWindowNames)
}
}
.formStyle(.grouped)
Expand Down Expand Up @@ -179,7 +182,7 @@ struct LayoutSettingsTab: View {
Button("Cancel", role: .cancel) {}
Button("Update") {
if let layout = layoutToModify {
layoutManager.updateLayout(id: layout.id)
windowManager.updateLayout(id: layout.id, includeWindowNames: includeWindowNames)
}
layoutToModify = nil
}
Expand Down Expand Up @@ -271,7 +274,8 @@ struct LayoutSettingsTab: View {
isJSONEditorVisible = true
}
private func createLayout() {
guard !newLayoutName.isEmpty, layoutManager.createLayout(name: newLayoutName) != nil
guard !newLayoutName.isEmpty,
windowManager.saveLayout(name: newLayoutName, includeWindowNames: includeWindowNames) != nil
else {
logger.warning("Attempted to create layout with empty or non-unique name")
return
Expand Down Expand Up @@ -349,7 +353,7 @@ struct LayoutSettingsTab: View {
}

for layout in layoutsFromJSON {
_ = windowManager.saveLayout(name: layout.name)
_ = windowManager.saveLayout(name: layout.name, includeWindowNames: includeWindowNames)

if let newLayout = layoutManager.layouts.last {
layoutManager.updateLayout(id: newLayout.id, name: layout.name)
Expand Down
4 changes: 2 additions & 2 deletions Overview/Window/Services/WindowStorageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ final class WindowStorageService {
}
}

func applyWindows(_ windows: [Window], using handler: (NSRect) -> Void) {
func applyWindows(_ windows: [Window], using handler: (Window) -> Void) {
windows.forEach { window in
handler(window.frame)
handler(window)
}
logger.info("Successfully applied \(windows.count) windows")
}
Expand Down
4 changes: 3 additions & 1 deletion Overview/Window/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ struct Window: Codable, Equatable {
let y: Double
let width: Double
let height: Double
let windowName: String?

var frame: NSRect {
NSRect(x: x, y: y, width: width, height: height)
}

init(frame: NSRect) {
init(frame: NSRect, windowName: String? = nil) {
self.x = frame.origin.x
self.y = frame.origin.y
self.width = frame.width
self.height = frame.height
self.windowName = windowName
}
}
55 changes: 46 additions & 9 deletions Overview/Window/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ final class WindowManager: ObservableObject {
// Private State
private var activeWindows: Set<NSWindow> = []
private var windowDelegates: [NSWindow: WindowDelegate] = [:]
private var captureCoordinators: [NSWindow: CaptureCoordinator] = [:]
private var sessionWindowCounter: Int

init(
Expand All @@ -39,7 +40,7 @@ final class WindowManager: ObservableObject {
logger.debug("Window manager initialized")
}

func createWindow(at frame: NSRect? = nil) throws {
func createWindow(at frame: NSRect? = nil, windowName: String? = nil) throws {
do {
let defaultSize = CGSize(
width: Defaults[.defaultWindowWidth], height: Defaults[.defaultWindowHeight])
Expand All @@ -48,7 +49,7 @@ final class WindowManager: ObservableObject {
windowCount: sessionWindowCounter,
providedFrame: frame)

configureWindow(window)
configureWindow(window, windowName: windowName)

activeWindows.insert(window)
sessionWindowCounter += 1
Expand All @@ -66,6 +67,7 @@ final class WindowManager: ObservableObject {
window.orderOut(self)
activeWindows.remove(window)
windowDelegates.removeValue(forKey: window)
captureCoordinators.removeValue(forKey: window)
logger.debug("Window closed successfully")
}
}
Expand Down Expand Up @@ -102,20 +104,26 @@ final class WindowManager: ObservableObject {
}
}

func saveLayout(name: String) -> Layout? {
let layout = layoutManager.createLayout(name: name)
func saveLayout(name: String, includeWindowNames: Bool = false) -> Layout? {
let windows = collectWindows(includeWindowNames: includeWindowNames)
let layout = layoutManager.createLayout(name: name, windows: windows)
return layout
}

func updateLayout(id: UUID, includeWindowNames: Bool = false) {
let windows = collectWindows(includeWindowNames: includeWindowNames)
layoutManager.updateLayout(id: id, windows: windows)
}

func applyLayout(_ layout: Layout) {
if Defaults[.closeWindowsOnApply] {
closeAllWindows()
}

windowServices.windowStorage.applyWindows(layout.windows) { [weak self] frame in
windowServices.windowStorage.applyWindows(layout.windows) { [weak self] windowState in
guard let self = self else { return }
do {
try createWindow(at: frame)
try createWindow(at: windowState.frame, windowName: windowState.windowName)
} catch {
logger.logError(
error, context: "Failed to create window from layout '\(layout.name)'")
Expand All @@ -134,11 +142,11 @@ final class WindowManager: ObservableObject {
}
}

private func configureWindow(_ window: NSWindow) {
private func configureWindow(_ window: NSWindow, windowName: String?) {
windowServices.windowConfiguration.applyConfiguration(
to: window, hasShadow: Defaults[.windowShadowEnabled])
setupWindowDelegate(for: window)
setupWindowContent(window)
setupWindowContent(window, windowName: windowName)
}

private func setupWindowDelegate(for window: NSWindow) {
Expand All @@ -148,12 +156,14 @@ final class WindowManager: ObservableObject {
logger.debug("Window delegate configured: id=\(sessionWindowCounter)")
}

private func setupWindowContent(_ window: NSWindow) {
private func setupWindowContent(_ window: NSWindow, windowName: String?) {
let captureCoordinator = CaptureCoordinator(
sourceManager: sourceManager,
permissionManager: permissionManager
)

captureCoordinators[window] = captureCoordinator

let contentView = PreviewView(
previewManager: previewManager,
sourceManager: sourceManager,
Expand All @@ -165,6 +175,10 @@ final class WindowManager: ObservableObject {
}
)
window.contentView = NSHostingView(rootView: contentView)

if let title = windowName {
selectSource(named: title, for: captureCoordinator)
}
}

private func closeAllWindows() {
Expand All @@ -174,6 +188,29 @@ final class WindowManager: ObservableObject {
}
}

private func collectWindows(includeWindowNames: Bool) -> [Window] {
activeWindows.compactMap { window in
let title: String? = includeWindowNames ? captureCoordinators[window]?.sourceWindowTitle : nil
return Window(frame: window.frame, windowName: title)
}
}

private func selectSource(named title: String, for coordinator: CaptureCoordinator) {
Task { @MainActor in
do {
let sources = try await sourceManager.getFilteredSources()
if let match = sources.first(where: { $0.title == title }) {
previewManager.startSourcePreview(
captureCoordinator: coordinator,
source: match
)
}
} catch {
logger.logError(error, context: "Failed to select source '\(title)'")
}
}
}

private func handleRestoreCompletion(_ restoredCount: Int) {
if restoredCount == 0 && Defaults[.createOnLaunch] {
logger.info("No windows restored, creating default window")
Expand Down