mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: working on the new terminalmanager
This commit is contained in:
@ -30,6 +30,7 @@
|
|||||||
A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */ = {isa = PBXBuildFile; fileRef = A59630992AEE1C6400D64628 /* Terminal.xib */; };
|
A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */ = {isa = PBXBuildFile; fileRef = A59630992AEE1C6400D64628 /* Terminal.xib */; };
|
||||||
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A596309B2AEE1C9E00D64628 /* TerminalController.swift */; };
|
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A596309B2AEE1C9E00D64628 /* TerminalController.swift */; };
|
||||||
A596309E2AEE1D6C00D64628 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A596309D2AEE1D6C00D64628 /* TerminalView.swift */; };
|
A596309E2AEE1D6C00D64628 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A596309D2AEE1D6C00D64628 /* TerminalView.swift */; };
|
||||||
|
A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A596309F2AEF6AEB00D64628 /* TerminalManager.swift */; };
|
||||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */; };
|
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */; };
|
||||||
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; };
|
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; };
|
||||||
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
|
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
|
||||||
@ -68,6 +69,7 @@
|
|||||||
A59630992AEE1C6400D64628 /* Terminal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Terminal.xib; sourceTree = "<group>"; };
|
A59630992AEE1C6400D64628 /* Terminal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Terminal.xib; sourceTree = "<group>"; };
|
||||||
A596309B2AEE1C9E00D64628 /* TerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalController.swift; sourceTree = "<group>"; };
|
A596309B2AEE1C9E00D64628 /* TerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalController.swift; sourceTree = "<group>"; };
|
||||||
A596309D2AEE1D6C00D64628 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
|
A596309D2AEE1D6C00D64628 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
|
||||||
|
A596309F2AEF6AEB00D64628 /* TerminalManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalManager.swift; sourceTree = "<group>"; };
|
||||||
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = "<group>"; };
|
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = "<group>"; };
|
||||||
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = "<group>"; };
|
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = "<group>"; };
|
||||||
A5A1F8842A489D6800D1E8BC /* terminfo */ = {isa = PBXFileReference; lastKnownFileType = folder; name = terminfo; path = "../zig-out/share/terminfo"; sourceTree = "<group>"; };
|
A5A1F8842A489D6800D1E8BC /* terminfo */ = {isa = PBXFileReference; lastKnownFileType = folder; name = terminfo; path = "../zig-out/share/terminfo"; sourceTree = "<group>"; };
|
||||||
@ -184,6 +186,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A59630992AEE1C6400D64628 /* Terminal.xib */,
|
A59630992AEE1C6400D64628 /* Terminal.xib */,
|
||||||
|
A596309F2AEF6AEB00D64628 /* TerminalManager.swift */,
|
||||||
A596309B2AEE1C9E00D64628 /* TerminalController.swift */,
|
A596309B2AEE1C9E00D64628 /* TerminalController.swift */,
|
||||||
A596309D2AEE1D6C00D64628 /* TerminalView.swift */,
|
A596309D2AEE1D6C00D64628 /* TerminalView.swift */,
|
||||||
);
|
);
|
||||||
@ -319,6 +322,7 @@
|
|||||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
||||||
A59630972AEE163600D64628 /* HostingWindow.swift in Sources */,
|
A59630972AEE163600D64628 /* HostingWindow.swift in Sources */,
|
||||||
|
A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */,
|
||||||
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */,
|
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */,
|
||||||
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
||||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
||||||
|
@ -42,16 +42,16 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
private var dockMenu: NSMenu = NSMenu()
|
private var dockMenu: NSMenu = NSMenu()
|
||||||
|
|
||||||
/// The ghostty global state. Only one per process.
|
/// The ghostty global state. Only one per process.
|
||||||
private var ghostty: Ghostty.AppState = Ghostty.AppState()
|
private let ghostty: Ghostty.AppState = Ghostty.AppState()
|
||||||
|
|
||||||
/// Manages windows and tabs, ensuring they're allocated/deallocated correctly
|
/// Manages our terminal windows.
|
||||||
var windowManager: PrimaryWindowManager!
|
let terminalManager: TerminalManager
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
|
self.terminalManager = TerminalManager(ghostty)
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
ghostty.delegate = self
|
ghostty.delegate = self
|
||||||
windowManager = PrimaryWindowManager(ghostty: self.ghostty)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - NSApplicationDelegate
|
//MARK: - NSApplicationDelegate
|
||||||
@ -73,15 +73,11 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
|
|
||||||
// Let's launch our first window.
|
// Let's launch our first window.
|
||||||
// TODO: we should detect if we restored windows and if so not launch a new window.
|
// TODO: we should detect if we restored windows and if so not launch a new window.
|
||||||
// TODO: remove when TerminalController is done
|
terminalManager.newWindow()
|
||||||
// windowManager.addInitialWindow()
|
|
||||||
|
|
||||||
// Initial config loading
|
// Initial config loading
|
||||||
configDidReload(ghostty)
|
configDidReload(ghostty)
|
||||||
|
|
||||||
let c = TerminalController(ghostty)
|
|
||||||
c.showWindow(self)
|
|
||||||
|
|
||||||
// Register our service provider. This must happen after everything
|
// Register our service provider. This must happen after everything
|
||||||
// else is initialized.
|
// else is initialized.
|
||||||
NSApp.servicesProvider = ServiceProvider()
|
NSApp.servicesProvider = ServiceProvider()
|
||||||
@ -151,7 +147,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
guard !flag else { return true }
|
guard !flag else { return true }
|
||||||
|
|
||||||
// No visible windows, open a new one.
|
// No visible windows, open a new one.
|
||||||
windowManager.newWindow()
|
terminalManager.newWindow()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,15 +171,8 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
var config = Ghostty.SurfaceConfiguration()
|
var config = Ghostty.SurfaceConfiguration()
|
||||||
config.workingDirectory = filename
|
config.workingDirectory = filename
|
||||||
|
|
||||||
// If we don't have a window open through the window manager, we launch
|
// Add a new tab or create a new window
|
||||||
// a new window.
|
terminalManager.newTab(withBaseConfig: config)
|
||||||
guard let mainWindow = windowManager.mainWindow else {
|
|
||||||
windowManager.addNewWindow(withBaseConfig: config)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new tab
|
|
||||||
windowManager.addNewTab(to: mainWindow, withBaseConfig: config)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,7 +247,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
func configDidReload(_ state: Ghostty.AppState) {
|
func configDidReload(_ state: Ghostty.AppState) {
|
||||||
// Config could change keybindings, so update everything that depends on that
|
// Config could change keybindings, so update everything that depends on that
|
||||||
syncMenuShortcuts()
|
syncMenuShortcuts()
|
||||||
windowManager.relabelTabs()
|
//windowManager.relabelTabs()
|
||||||
|
|
||||||
// Config could change window appearance
|
// Config could change window appearance
|
||||||
syncAppearance()
|
syncAppearance()
|
||||||
@ -308,7 +297,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func newWindow(_ sender: Any?) {
|
@IBAction func newWindow(_ sender: Any?) {
|
||||||
windowManager.newWindow()
|
terminalManager.newWindow()
|
||||||
|
|
||||||
// We also activate our app so that it becomes front. This may be
|
// We also activate our app so that it becomes front. This may be
|
||||||
// necessary for the dock menu.
|
// necessary for the dock menu.
|
||||||
@ -316,7 +305,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func newTab(_ sender: Any?) {
|
@IBAction func newTab(_ sender: Any?) {
|
||||||
windowManager.newTab()
|
terminalManager.newTab()
|
||||||
|
|
||||||
// We also activate our app so that it becomes front. This may be
|
// We also activate our app so that it becomes front. This may be
|
||||||
// necessary for the dock menu.
|
// necessary for the dock menu.
|
||||||
|
@ -39,7 +39,7 @@ class ServiceProvider: NSObject {
|
|||||||
private func openTerminal(_ path: String, target: OpenTarget) {
|
private func openTerminal(_ path: String, target: OpenTarget) {
|
||||||
guard let delegateRaw = NSApp.delegate else { return }
|
guard let delegateRaw = NSApp.delegate else { return }
|
||||||
guard let delegate = delegateRaw as? AppDelegate else { return }
|
guard let delegate = delegateRaw as? AppDelegate else { return }
|
||||||
guard let windowManager = delegate.windowManager else { return }
|
let terminalManager = delegate.terminalManager
|
||||||
|
|
||||||
// We only open in directories.
|
// We only open in directories.
|
||||||
var isDirectory = ObjCBool(true)
|
var isDirectory = ObjCBool(true)
|
||||||
@ -50,19 +50,12 @@ class ServiceProvider: NSObject {
|
|||||||
var config = Ghostty.SurfaceConfiguration()
|
var config = Ghostty.SurfaceConfiguration()
|
||||||
config.workingDirectory = path
|
config.workingDirectory = path
|
||||||
|
|
||||||
// If we don't have a window open through the window manager, we launch
|
|
||||||
// a new window even if they requested a tab.
|
|
||||||
guard let mainWindow = windowManager.mainWindow else {
|
|
||||||
windowManager.addNewWindow(withBaseConfig: config)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case .window:
|
case .window:
|
||||||
windowManager.addNewWindow(withBaseConfig: config)
|
terminalManager.newWindow(withBaseConfig: config)
|
||||||
|
|
||||||
case .tab:
|
case .tab:
|
||||||
windowManager.addNewTab(to: mainWindow, withBaseConfig: config)
|
terminalManager.newTab(withBaseConfig: config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import Cocoa
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
class TerminalController: NSWindowController, NSWindowDelegate {
|
class TerminalController: NSWindowController, NSWindowDelegate, TerminalViewDelegate {
|
||||||
override var windowNibName: NSNib.Name? { "Terminal" }
|
override var windowNibName: NSNib.Name? { "Terminal" }
|
||||||
|
|
||||||
/// The app instance that this terminal view will represent.
|
/// The app instance that this terminal view will represent.
|
||||||
@ -12,6 +12,11 @@ class TerminalController: NSWindowController, NSWindowDelegate {
|
|||||||
init(_ ghostty: Ghostty.AppState) {
|
init(_ ghostty: Ghostty.AppState) {
|
||||||
self.ghostty = ghostty
|
self.ghostty = ghostty
|
||||||
super.init(window: nil)
|
super.init(window: nil)
|
||||||
|
|
||||||
|
// Register as observer for window-level manipulations that are best handled
|
||||||
|
// here at the controller layer rather than in the SwiftUI stack.
|
||||||
|
let center = NotificationCenter.default
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -39,7 +44,8 @@ class TerminalController: NSWindowController, NSWindowDelegate {
|
|||||||
|
|
||||||
// Initialize our content view to the SwiftUI root
|
// Initialize our content view to the SwiftUI root
|
||||||
window.contentView = NSHostingView(rootView: TerminalView(
|
window.contentView = NSHostingView(rootView: TerminalView(
|
||||||
ghostty: self.ghostty
|
ghostty: self.ghostty,
|
||||||
|
delegate: self
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,4 +53,15 @@ class TerminalController: NSWindowController, NSWindowDelegate {
|
|||||||
|
|
||||||
func windowWillClose(_ notification: Notification) {
|
func windowWillClose(_ notification: Notification) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - TerminalViewDelegate
|
||||||
|
|
||||||
|
func titleDidChange(to: String) {
|
||||||
|
self.window?.title = to
|
||||||
|
}
|
||||||
|
|
||||||
|
func cellSizeDidChange(to: NSSize) {
|
||||||
|
guard ghostty.windowStepResize else { return }
|
||||||
|
self.window?.contentResizeIncrements = to
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
62
macos/Sources/Features/Terminal/TerminalManager.swift
Normal file
62
macos/Sources/Features/Terminal/TerminalManager.swift
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import Cocoa
|
||||||
|
|
||||||
|
/// Manages a set of terminal windows.
|
||||||
|
class TerminalManager {
|
||||||
|
struct Window {
|
||||||
|
let controller: TerminalController
|
||||||
|
}
|
||||||
|
|
||||||
|
let ghostty: Ghostty.AppState
|
||||||
|
|
||||||
|
/// The set of windows we currently have.
|
||||||
|
private var windows: [Window] = []
|
||||||
|
|
||||||
|
/// Returns the main window of the managed window stack. If there is no window
|
||||||
|
/// then an arbitrary window will be chosen.
|
||||||
|
private var mainWindow: Window? {
|
||||||
|
for window in windows {
|
||||||
|
if (window.controller.window?.isMainWindow ?? false) {
|
||||||
|
return window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no main window, just use the first window.
|
||||||
|
return windows.first
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ ghostty: Ghostty.AppState) {
|
||||||
|
self.ghostty = ghostty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new terminal window.
|
||||||
|
func newWindow(withBaseConfig base: Ghostty.SurfaceConfiguration? = nil) {
|
||||||
|
let c = createWindow(withBaseConfig: base)
|
||||||
|
c.showWindow(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new tab in the current main window. If there are no windows, a window
|
||||||
|
/// is created.
|
||||||
|
func newTab(withBaseConfig base: Ghostty.SurfaceConfiguration? = nil) {
|
||||||
|
// If there is no main window, just create a new window
|
||||||
|
guard let parent = mainWindow?.controller.window else {
|
||||||
|
newWindow(withBaseConfig: base)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new window and add it to the parent
|
||||||
|
let window = createWindow(withBaseConfig: base).window!
|
||||||
|
parent.addTabbedWindow(window, ordered: .above)
|
||||||
|
window.makeKeyAndOrderFront(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a window controller, adds it to our managed list, and returns it.
|
||||||
|
func createWindow(withBaseConfig: Ghostty.SurfaceConfiguration?) -> TerminalController {
|
||||||
|
// Initialize our controller to load the window
|
||||||
|
let c = TerminalController(ghostty)
|
||||||
|
|
||||||
|
// Keep track of every window we manage
|
||||||
|
windows.append(Window(controller: c))
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,33 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
|
protocol TerminalViewDelegate: AnyObject {
|
||||||
|
/// Called when the currently focused surface changed. This can be nil.
|
||||||
|
func focusedSurfaceDidChange(to: Ghostty.SurfaceView?)
|
||||||
|
|
||||||
|
/// The title of the terminal should change.
|
||||||
|
func titleDidChange(to: String)
|
||||||
|
|
||||||
|
/// The cell size changed.
|
||||||
|
func cellSizeDidChange(to: NSSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TerminalViewDelegate {
|
||||||
|
func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {}
|
||||||
|
func titleDidChange(to: String) {}
|
||||||
|
func cellSizeDidChange(to: NSSize) {}
|
||||||
|
}
|
||||||
|
|
||||||
struct TerminalView: View {
|
struct TerminalView: View {
|
||||||
@ObservedObject var ghostty: Ghostty.AppState
|
@ObservedObject var ghostty: Ghostty.AppState
|
||||||
|
|
||||||
|
// An optional delegate to receive information about terminal changes.
|
||||||
|
weak var delegate: TerminalViewDelegate? = nil
|
||||||
|
|
||||||
// This seems like a crutch after switching from SwiftUI to AppKit lifecycle.
|
// This seems like a crutch after switching from SwiftUI to AppKit lifecycle.
|
||||||
@FocusState private var focused: Bool
|
@FocusState private var focused: Bool
|
||||||
|
|
||||||
|
// Various state values sent back up from the currently focused terminals.
|
||||||
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
|
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
|
||||||
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
|
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
|
||||||
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
|
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
|
||||||
@ -38,10 +59,6 @@ struct TerminalView: View {
|
|||||||
case .error:
|
case .error:
|
||||||
ErrorView()
|
ErrorView()
|
||||||
case .ready:
|
case .ready:
|
||||||
let center = NotificationCenter.default
|
|
||||||
let gotoTab = center.publisher(for: Ghostty.Notification.ghosttyGotoTab)
|
|
||||||
let toggleFullscreen = center.publisher(for: Ghostty.Notification.ghosttyToggleFullscreen)
|
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// If we're running in debug mode we show a warning so that users
|
// If we're running in debug mode we show a warning so that users
|
||||||
// know that performance will be degraded.
|
// know that performance will be degraded.
|
||||||
@ -54,6 +71,16 @@ struct TerminalView: View {
|
|||||||
.ghosttyConfig(ghostty.config!)
|
.ghosttyConfig(ghostty.config!)
|
||||||
.focused($focused)
|
.focused($focused)
|
||||||
.onAppear { self.focused = true }
|
.onAppear { self.focused = true }
|
||||||
|
.onChange(of: focusedSurface) { newValue in
|
||||||
|
self.delegate?.focusedSurfaceDidChange(to: newValue)
|
||||||
|
}
|
||||||
|
.onChange(of: title) { newValue in
|
||||||
|
self.delegate?.titleDidChange(to: newValue)
|
||||||
|
}
|
||||||
|
.onChange(of: cellSize) { newValue in
|
||||||
|
guard let size = newValue else { return }
|
||||||
|
self.delegate?.cellSizeDidChange(to: size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user