Improve macOS app lifecycle and split handle
This commit is contained in:
@@ -7,6 +7,7 @@ struct ContentView: View {
|
|||||||
@State private var settings = SettingsStore.load()
|
@State private var settings = SettingsStore.load()
|
||||||
@State private var selectedClipID: UUID?
|
@State private var selectedClipID: UUID?
|
||||||
@State private var variantPickerClip: GrowthLapseProjectClip?
|
@State private var variantPickerClip: GrowthLapseProjectClip?
|
||||||
|
@State private var reviewPanelHeight: CGFloat = 340
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HSplitView {
|
HSplitView {
|
||||||
@@ -231,12 +232,14 @@ struct ContentView: View {
|
|||||||
.progressViewStyle(.linear)
|
.progressViewStyle(.linear)
|
||||||
|
|
||||||
if let project = processor.project {
|
if let project = processor.project {
|
||||||
VSplitView {
|
ResizableVerticalStack(
|
||||||
|
topHeight: $reviewPanelHeight,
|
||||||
|
minTopHeight: 260,
|
||||||
|
minBottomHeight: 160
|
||||||
|
) {
|
||||||
projectReviewPanel(project)
|
projectReviewPanel(project)
|
||||||
.frame(minHeight: 260)
|
} bottom: {
|
||||||
|
|
||||||
logView
|
logView
|
||||||
.frame(minHeight: 160)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -635,6 +638,92 @@ private struct ClipPreviewView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct ResizableVerticalStack<Top: View, Bottom: View>: View {
|
||||||
|
@Binding var topHeight: CGFloat
|
||||||
|
let minTopHeight: CGFloat
|
||||||
|
let minBottomHeight: CGFloat
|
||||||
|
@ViewBuilder let top: () -> Top
|
||||||
|
@ViewBuilder let bottom: () -> Bottom
|
||||||
|
|
||||||
|
@State private var dragStartHeight: CGFloat?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { proxy in
|
||||||
|
let dividerHeight: CGFloat = 14
|
||||||
|
let availableHeight = max(0, proxy.size.height - dividerHeight)
|
||||||
|
let maxTopHeight = max(minTopHeight, availableHeight - minBottomHeight)
|
||||||
|
let clampedTopHeight = min(max(topHeight, minTopHeight), maxTopHeight)
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
top()
|
||||||
|
.frame(height: clampedTopHeight)
|
||||||
|
.clipped()
|
||||||
|
|
||||||
|
SplitHandle()
|
||||||
|
.frame(height: dividerHeight)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.gesture(
|
||||||
|
DragGesture(minimumDistance: 0)
|
||||||
|
.onChanged { value in
|
||||||
|
if dragStartHeight == nil {
|
||||||
|
dragStartHeight = clampedTopHeight
|
||||||
|
}
|
||||||
|
let proposed = (dragStartHeight ?? clampedTopHeight) + value.translation.height
|
||||||
|
topHeight = min(max(proposed, minTopHeight), maxTopHeight)
|
||||||
|
}
|
||||||
|
.onEnded { _ in
|
||||||
|
dragStartHeight = nil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.help("Ziehen, um Clip-Nachbearbeitung und Log größer oder kleiner zu machen.")
|
||||||
|
|
||||||
|
bottom()
|
||||||
|
.frame(height: max(minBottomHeight, availableHeight - clampedTopHeight))
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
topHeight = clampedTopHeight
|
||||||
|
}
|
||||||
|
.onChange(of: proxy.size.height) { _ in
|
||||||
|
topHeight = clampedTopHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SplitHandle: View {
|
||||||
|
@State private var isHovering = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color(nsColor: .windowBackgroundColor))
|
||||||
|
|
||||||
|
RoundedRectangle(cornerRadius: 3)
|
||||||
|
.fill(isHovering ? Color.accentColor.opacity(0.75) : Color(nsColor: .separatorColor))
|
||||||
|
.frame(width: 84, height: 5)
|
||||||
|
}
|
||||||
|
.overlay(alignment: .top) {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color(nsColor: .separatorColor).opacity(0.45))
|
||||||
|
.frame(height: 1)
|
||||||
|
}
|
||||||
|
.overlay(alignment: .bottom) {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color(nsColor: .separatorColor).opacity(0.45))
|
||||||
|
.frame(height: 1)
|
||||||
|
}
|
||||||
|
.onHover { hovering in
|
||||||
|
isHovering = hovering
|
||||||
|
if hovering {
|
||||||
|
NSCursor.resizeUpDown.push()
|
||||||
|
} else {
|
||||||
|
NSCursor.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private struct SegmentTimelineView: View {
|
private struct SegmentTimelineView: View {
|
||||||
let clip: GrowthLapseProjectClip
|
let clip: GrowthLapseProjectClip
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
|
import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct GrowthLapseApp: App {
|
struct GrowthLapseApp: App {
|
||||||
|
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
.frame(minWidth: 980, minHeight: 680)
|
.frame(minWidth: 980, minHeight: 680)
|
||||||
}
|
}
|
||||||
.windowStyle(.titleBar)
|
.windowStyle(.titleBar)
|
||||||
|
.commands {
|
||||||
|
CommandGroup(replacing: .appTermination) {
|
||||||
|
Button("GrowthLapse beenden") {
|
||||||
|
NSApp.terminate(nil)
|
||||||
|
}
|
||||||
|
.keyboardShortcut("q", modifiers: .command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
NSApp.setActivationPolicy(.regular)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user