Improve resizing with draggable and hotkeys
This commit is contained in:
@@ -18,6 +18,7 @@ class HotkeyManager {
|
||||
var onNextTab: (() -> Void)?
|
||||
var onPreviousTab: (() -> Void)?
|
||||
var onDetachTab: (() -> Void)?
|
||||
var onApplySizePreset: ((TerminalSizePreset) -> Void)?
|
||||
var onSwitchToTab: ((Int) -> Void)?
|
||||
|
||||
/// Tab-level hotkeys only fire when the notch is open.
|
||||
@@ -50,6 +51,9 @@ class HotkeyManager {
|
||||
private var detachBinding: HotkeyBinding {
|
||||
binding(for: NotchSettings.Keys.hotkeyDetachTab) ?? .cmdD
|
||||
}
|
||||
private var sizePresets: [TerminalSizePreset] {
|
||||
TerminalSizePresetStore.load()
|
||||
}
|
||||
|
||||
private func binding(for key: String) -> HotkeyBinding? {
|
||||
guard let json = UserDefaults.standard.string(forKey: key) else { return nil }
|
||||
@@ -211,6 +215,13 @@ class HotkeyManager {
|
||||
onDetachTab?()
|
||||
return true
|
||||
}
|
||||
for preset in sizePresets {
|
||||
guard let binding = preset.hotkey else { continue }
|
||||
if binding.matches(event) {
|
||||
onApplySizePreset?(preset)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Cmd+1 through Cmd+9
|
||||
if event.modifierFlags.contains(.command) {
|
||||
|
||||
@@ -54,6 +54,9 @@ class ScreenManager: ObservableObject {
|
||||
hk.onDetachTab = { [weak self] in
|
||||
MainActor.assumeIsolated { self?.detachActiveTab() }
|
||||
}
|
||||
hk.onApplySizePreset = { [weak self] preset in
|
||||
MainActor.assumeIsolated { self?.applySizePreset(preset) }
|
||||
}
|
||||
hk.onSwitchToTab = { index in
|
||||
MainActor.assumeIsolated { tm.switchToTab(at: index) }
|
||||
}
|
||||
@@ -130,6 +133,19 @@ class ScreenManager: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func applySizePreset(_ preset: TerminalSizePreset) {
|
||||
guard let (screenUUID, vm) = viewModels.first(where: { $0.value.notchState == .open }) else {
|
||||
UserDefaults.standard.set(preset.width, forKey: NotchSettings.Keys.openWidth)
|
||||
UserDefaults.standard.set(preset.height, forKey: NotchSettings.Keys.openHeight)
|
||||
return
|
||||
}
|
||||
|
||||
withAnimation(vm.openAnimation) {
|
||||
vm.applySizePreset(preset, notifyWindowResize: false)
|
||||
}
|
||||
updateWindowFrame(for: screenUUID, centerHorizontally: true)
|
||||
}
|
||||
|
||||
// MARK: - Window creation
|
||||
|
||||
func rebuildWindows() {
|
||||
@@ -149,21 +165,10 @@ class ScreenManager: ObservableObject {
|
||||
private func createWindow(for screen: NSScreen) {
|
||||
let uuid = screen.displayUUID
|
||||
let vm = NotchViewModel(screenUUID: uuid)
|
||||
|
||||
let shadowPadding: CGFloat = 20
|
||||
let openSize = vm.openNotchSize
|
||||
let windowWidth = max(openSize.width + 40, screen.frame.width * 0.5)
|
||||
let windowHeight = openSize.height + shadowPadding
|
||||
|
||||
let windowRect = NSRect(
|
||||
x: screen.frame.origin.x + (screen.frame.width - windowWidth) / 2,
|
||||
y: screen.frame.origin.y + screen.frame.height - windowHeight,
|
||||
width: windowWidth,
|
||||
height: windowHeight
|
||||
)
|
||||
let initialContentSize = vm.openNotchSize
|
||||
|
||||
let window = NotchWindow(
|
||||
contentRect: windowRect,
|
||||
contentRect: NSRect(origin: .zero, size: CGSize(width: initialContentSize.width + 40, height: initialContentSize.height + 20)),
|
||||
styleMask: [.borderless, .nonactivatingPanel, .utilityWindow],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
@@ -181,19 +186,29 @@ class ScreenManager: ObservableObject {
|
||||
vm.requestClose = { [weak self] in
|
||||
self?.closeNotch(screenUUID: uuid)
|
||||
}
|
||||
vm.requestWindowResize = { [weak self] in
|
||||
self?.updateWindowFrame(for: uuid, centerHorizontally: true)
|
||||
}
|
||||
|
||||
let hostingView = NSHostingView(
|
||||
rootView: ContentView(vm: vm, terminalManager: TerminalManager.shared)
|
||||
.preferredColorScheme(.dark)
|
||||
)
|
||||
hostingView.frame = NSRect(origin: .zero, size: windowRect.size)
|
||||
window.contentView = hostingView
|
||||
let containerView = NSView(frame: NSRect(origin: .zero, size: window.frame.size))
|
||||
containerView.autoresizesSubviews = true
|
||||
containerView.wantsLayer = true
|
||||
containerView.layer?.backgroundColor = NSColor.clear.cgColor
|
||||
|
||||
window.setFrame(windowRect, display: true)
|
||||
window.orderFrontRegardless()
|
||||
hostingView.frame = containerView.bounds
|
||||
hostingView.autoresizingMask = [.width, .height]
|
||||
containerView.addSubview(hostingView)
|
||||
window.contentView = containerView
|
||||
|
||||
windows[uuid] = window
|
||||
viewModels[uuid] = vm
|
||||
|
||||
updateWindowFrame(for: uuid, centerHorizontally: true)
|
||||
window.orderFrontRegardless()
|
||||
}
|
||||
|
||||
// MARK: - Repositioning
|
||||
@@ -205,21 +220,44 @@ class ScreenManager: ObservableObject {
|
||||
|
||||
vm.refreshClosedSize()
|
||||
|
||||
let shadowPadding: CGFloat = 20
|
||||
let openSize = vm.openNotchSize
|
||||
let windowWidth = max(openSize.width + 40, screen.frame.width * 0.5)
|
||||
let windowHeight = openSize.height + shadowPadding
|
||||
|
||||
let newFrame = NSRect(
|
||||
x: screen.frame.origin.x + (screen.frame.width - windowWidth) / 2,
|
||||
y: screen.frame.origin.y + screen.frame.height - windowHeight,
|
||||
width: windowWidth,
|
||||
height: windowHeight
|
||||
)
|
||||
window.setFrame(newFrame, display: true)
|
||||
updateWindowFrame(for: uuid, on: screen, window: window, centerHorizontally: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateWindowFrame(for screenUUID: String, centerHorizontally: Bool = false) {
|
||||
guard let screen = NSScreen.screens.first(where: { $0.displayUUID == screenUUID }),
|
||||
let window = windows[screenUUID] else { return }
|
||||
updateWindowFrame(for: screenUUID, on: screen, window: window, centerHorizontally: centerHorizontally)
|
||||
}
|
||||
|
||||
private func updateWindowFrame(
|
||||
for screenUUID: String,
|
||||
on screen: NSScreen,
|
||||
window: NotchWindow,
|
||||
centerHorizontally: Bool = false
|
||||
) {
|
||||
guard let vm = viewModels[screenUUID] else { return }
|
||||
|
||||
let shadowPadding: CGFloat = 20
|
||||
let openSize = vm.openNotchSize
|
||||
let windowWidth = openSize.width + 40
|
||||
let windowHeight = openSize.height + shadowPadding
|
||||
let centeredX = screen.frame.origin.x + (screen.frame.width - windowWidth) / 2
|
||||
|
||||
let x: CGFloat = centerHorizontally || vm.notchState == .closed
|
||||
? centeredX
|
||||
: min(max(window.frame.minX, screen.frame.minX), screen.frame.maxX - windowWidth)
|
||||
|
||||
let frame = NSRect(
|
||||
x: x,
|
||||
y: screen.frame.origin.y + screen.frame.height - windowHeight,
|
||||
width: windowWidth,
|
||||
height: windowHeight
|
||||
)
|
||||
guard !window.frame.equalTo(frame) else { return }
|
||||
window.setFrame(frame, display: false)
|
||||
}
|
||||
|
||||
// MARK: - Cleanup
|
||||
|
||||
private func cleanupAllWindows() {
|
||||
|
||||
Reference in New Issue
Block a user