macOS client parity review
This commit is contained in:
@@ -10,6 +10,7 @@ struct MenuEntry: Identifiable {
|
||||
|
||||
struct OverlayMenu {
|
||||
let anchor: CGRect
|
||||
let accent: Color
|
||||
let entries: [MenuEntry]
|
||||
}
|
||||
|
||||
@@ -44,29 +45,42 @@ enum LeftTab {
|
||||
final class Overlays {
|
||||
var alerts: [OverlayAlert] = []
|
||||
var image: NSImage?
|
||||
var menu: OverlayMenu?
|
||||
var menu: OverlayMenu? {
|
||||
didSet {
|
||||
menuHover = false
|
||||
}
|
||||
}
|
||||
var modal: OverlayModal?
|
||||
var switcher: SwitcherModel?
|
||||
var leftTab = LeftTab.lead
|
||||
var leadFocus = 0
|
||||
@ObservationIgnored var pickerFrame: CGRect = .zero
|
||||
@ObservationIgnored var menuHover = false
|
||||
var paneFrame: CGRect = .zero
|
||||
@ObservationIgnored private var monitor: Any?
|
||||
@ObservationIgnored private var mouseMonitor: Any?
|
||||
|
||||
var isOpen: Bool {
|
||||
!alerts.isEmpty || image != nil || menu != nil || modal != nil || switcher != nil
|
||||
}
|
||||
|
||||
var dialogOpen: Bool {
|
||||
!alerts.isEmpty || modal != nil
|
||||
}
|
||||
|
||||
func showAlert(_ text: String) {
|
||||
menu = nil
|
||||
alerts.append(OverlayAlert(content: AnyView(ModalText(text: text))))
|
||||
}
|
||||
|
||||
func showAlert(_ content: some View) {
|
||||
menu = nil
|
||||
alerts.append(OverlayAlert(content: AnyView(content)))
|
||||
}
|
||||
|
||||
func showModal(_ content: some View, _ buttons: [ModalButton], anchor: CGRect? = nil, inPane: Bool = false, canSubmit: @escaping @MainActor () -> Bool = { true }) async -> String {
|
||||
modal?.done("cancel")
|
||||
menu = nil
|
||||
return await withCheckedContinuation { cont in
|
||||
modal = OverlayModal(content: AnyView(content), buttons: buttons, anchor: anchor, inPane: inPane, canSubmit: canSubmit) { value in
|
||||
cont.resume(returning: value)
|
||||
@@ -103,7 +117,7 @@ final class Overlays {
|
||||
|
||||
func closeTop() {
|
||||
if !alerts.isEmpty {
|
||||
alerts.removeFirst()
|
||||
alerts.removeLast()
|
||||
} else if modal != nil {
|
||||
answerModal("cancel")
|
||||
} else if image != nil {
|
||||
@@ -128,13 +142,21 @@ final class Overlays {
|
||||
}
|
||||
return taken ? nil : event
|
||||
}
|
||||
mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown, .otherMouseDown]) { [weak self] event in
|
||||
MainActor.assumeIsolated {
|
||||
if let self, self.menu != nil, !self.menuHover {
|
||||
self.menu = nil
|
||||
}
|
||||
}
|
||||
return event
|
||||
}
|
||||
}
|
||||
|
||||
private func key(_ code: UInt16, _ flags: NSEvent.ModifierFlags, _ chars: String, _ ctrl: @MainActor (String) -> Bool) -> Bool {
|
||||
if !dialogOpen && flags.contains(.control) && !flags.contains(.command) && goKeys.contains(chars) {
|
||||
return ctrl(chars)
|
||||
}
|
||||
if !isOpen {
|
||||
if flags.contains(.control) && !flags.contains(.command) && goKeys.contains(chars) {
|
||||
return ctrl(chars)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if code == 53 {
|
||||
@@ -145,7 +167,7 @@ final class Overlays {
|
||||
let marked = (NSApp.keyWindow?.firstResponder as? NSTextView)?.hasMarkedText() ?? false
|
||||
if !alerts.isEmpty {
|
||||
if enter {
|
||||
alerts.removeFirst()
|
||||
alerts.removeLast()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -180,7 +202,11 @@ final class Overlays {
|
||||
if let monitor {
|
||||
NSEvent.removeMonitor(monitor)
|
||||
}
|
||||
if let mouseMonitor {
|
||||
NSEvent.removeMonitor(mouseMonitor)
|
||||
}
|
||||
monitor = nil
|
||||
mouseMonitor = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,27 +234,6 @@ struct OverlayHost: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
if let menu = overlays.menu {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
overlays.menu = nil
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(menu.entries) { entry in
|
||||
MenuRow(entry: entry) {
|
||||
overlays.menu = nil
|
||||
entry.action()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(4)
|
||||
.background(Palette.raised, in: RoundedRectangle(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).stroke(Palette.border))
|
||||
.shadow(color: .black.opacity(0.18), radius: 14, y: 8)
|
||||
.fixedSize()
|
||||
.offset(x: menu.anchor.minX, y: menu.anchor.maxY + 4)
|
||||
}
|
||||
if let switcher = overlays.switcher {
|
||||
Color.black.opacity(0.35)
|
||||
.contentShape(Rectangle())
|
||||
@@ -252,22 +257,39 @@ struct OverlayHost: View {
|
||||
overlays.image = nil
|
||||
}
|
||||
}
|
||||
if let menu = overlays.menu {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(menu.entries) { entry in
|
||||
MenuRow(entry: entry) {
|
||||
overlays.menu = nil
|
||||
entry.action()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(4)
|
||||
.background(Palette.raised, in: RoundedRectangle(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).stroke(Palette.border))
|
||||
.shadow(color: overlayShadow, radius: 14, y: 8)
|
||||
.environment(\.paneAccent, menu.accent)
|
||||
.onHover { inside in
|
||||
overlays.menuHover = inside
|
||||
}
|
||||
.fixedSize()
|
||||
.offset(x: menu.anchor.minX, y: menu.anchor.maxY + 4)
|
||||
}
|
||||
if let m = overlays.modal {
|
||||
Color.black.opacity(0.35)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
overlays.answerModal("cancel")
|
||||
}
|
||||
ModalPlacement(modal: m, pane: overlays.paneFrame) { value in
|
||||
overlays.answerModal(value)
|
||||
}
|
||||
}
|
||||
if let alert = overlays.alerts.first {
|
||||
if let alert = overlays.alerts.last {
|
||||
Color.black.opacity(0.35)
|
||||
.contentShape(Rectangle())
|
||||
GeometryReader { g in
|
||||
AlertBox(alert: alert, maxText: max(60, g.size.height - 48 - 90)) {
|
||||
overlays.alerts.removeFirst()
|
||||
overlays.alerts.removeLast()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
@@ -399,7 +421,7 @@ private struct ModalBox: View {
|
||||
.frame(maxWidth: 520)
|
||||
.background(Palette.raised, in: RoundedRectangle(cornerRadius: 10))
|
||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Palette.border))
|
||||
.shadow(color: .black.opacity(0.18), radius: 14, y: 8)
|
||||
.shadow(color: overlayShadow, radius: 14, y: 8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +449,7 @@ private struct AlertBox: View {
|
||||
.frame(maxWidth: 520)
|
||||
.background(Palette.raised, in: RoundedRectangle(cornerRadius: 10))
|
||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Palette.border))
|
||||
.shadow(color: .black.opacity(0.18), radius: 14, y: 8)
|
||||
.shadow(color: overlayShadow, radius: 14, y: 8)
|
||||
.padding(24)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user