Skip to content

Commit a3dadc7

Browse files
committed
Rewrote and moved hide interface logic
- The logic for hide interface has been moved to a new file, CodeEditWindowsController+Panels. - The function for toggling first panel and last panel has also been moved to said file. - The logic for hide interface is now much more simplified, dynamic and easier to maintain.
1 parent 0ac2bae commit a3dadc7

3 files changed

Lines changed: 177 additions & 155 deletions

File tree

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//
2+
// CodeEditWindowController+Panels.swift
3+
// CodeEdit
4+
//
5+
// Created by Simon Kudsk on 11/05/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
extension CodeEditWindowController {
11+
@objc
12+
func objcToggleFirstPanel() {
13+
toggleFirstPanel(shouldAnimate: true)
14+
}
15+
16+
/// Toggles the navigator pane, optionally without animation.
17+
func toggleFirstPanel(shouldAnimate: Bool = true) {
18+
guard let firstSplitView = splitViewController?.splitViewItems.first else { return }
19+
20+
if shouldAnimate {
21+
// Standard animated toggle
22+
firstSplitView.animator().isCollapsed.toggle()
23+
} else {
24+
// Instant toggle (no animation)
25+
firstSplitView.isCollapsed.toggle()
26+
}
27+
28+
splitViewController?.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed)
29+
}
30+
31+
@objc
32+
func objcToggleLastPanel() {
33+
toggleLastPanel(shouldAnimate: true)
34+
}
35+
36+
func toggleLastPanel(shouldAnimate: Bool = true) {
37+
guard let lastSplitView = splitViewController?.splitViewItems.last else {
38+
return
39+
}
40+
41+
if shouldAnimate {
42+
// Standard animated toggle
43+
NSAnimationContext.runAnimationGroup { _ in
44+
lastSplitView.animator().isCollapsed.toggle()
45+
}
46+
} else {
47+
// Instant toggle (no animation)
48+
lastSplitView.isCollapsed.toggle()
49+
}
50+
51+
splitViewController?.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed)
52+
}
53+
54+
// PanelDescriptor, used for an array of panels, for use with "Hide interface".
55+
private struct PanelDescriptor {
56+
/// Returns the current `isCollapsed` value for the panel.
57+
let isCollapsed: () -> Bool
58+
/// Returns the last stored previous state (or `nil` if none).
59+
let getPrevCollapsed: () -> Bool?
60+
/// Stores a new previous state (`nil` to clear).
61+
let setPrevCollapsed: (Bool?) -> Void
62+
/// Performs the actual toggle action for the panel.
63+
let toggle: () -> Void
64+
}
65+
66+
// The panels which "Hide interface" should interact with.
67+
private var panels: [PanelDescriptor] {
68+
[
69+
PanelDescriptor(
70+
isCollapsed: { self.navigatorCollapsed },
71+
getPrevCollapsed: { self.prevNavigatorCollapsed },
72+
setPrevCollapsed: { self.prevNavigatorCollapsed = $0 },
73+
toggle: { self.toggleFirstPanel(shouldAnimate: false) }
74+
),
75+
PanelDescriptor(
76+
isCollapsed: { self.inspectorCollapsed },
77+
getPrevCollapsed: { self.prevInspectorCollapsed },
78+
setPrevCollapsed: { self.prevInspectorCollapsed = $0 },
79+
toggle: { self.toggleLastPanel(shouldAnimate: false) }
80+
),
81+
PanelDescriptor(
82+
isCollapsed: { self.workspace?.utilityAreaModel?.isCollapsed ?? true },
83+
getPrevCollapsed: { self.prevUtilityAreaCollapsed },
84+
setPrevCollapsed: { self.prevUtilityAreaCollapsed = $0 },
85+
toggle: { CommandManager.shared.executeCommand("open.drawer.no.animation") }
86+
),
87+
PanelDescriptor(
88+
isCollapsed: { self.toolbarCollapsed },
89+
getPrevCollapsed: { self.prevToolbarCollapsed },
90+
setPrevCollapsed: { self.prevToolbarCollapsed = $0 },
91+
toggle: { self.toggleToolbar() }
92+
)
93+
]
94+
}
95+
96+
/// Returns `true` if at least one panel that was visible is still collapsed, meaning the interface is still hidden
97+
func isInterfaceStillHidden() -> Bool {
98+
// Some panels do not yet have a remembered state
99+
if panels.contains(where: { $0.getPrevCollapsed() == nil }) {
100+
// Hidden only if all panels are collapsed
101+
return panels.allSatisfy { $0.isCollapsed() }
102+
}
103+
104+
// All panels have a remembered state. Check if any that were visible are still collapsed
105+
let stillHidden = panels.contains { descriptor in
106+
guard let prev = descriptor.getPrevCollapsed() else { return false }
107+
return !prev && descriptor.isCollapsed()
108+
}
109+
110+
// If the interface has been restored, reset the remembered states
111+
if !stillHidden {
112+
resetStoredInterfaceCollapseState()
113+
}
114+
115+
return stillHidden
116+
}
117+
118+
/// Function for toggling the interface elements on or off
119+
///
120+
/// - Parameter shouldHide: Pass `true` to hide all interface panels (and remember their current states),
121+
/// or `false` to restore them to how they were before hiding.
122+
func toggleInterface(shouldHide: Bool) {
123+
// Store the current layout before hiding
124+
if shouldHide {
125+
storeInterfaceCollapseState()
126+
}
127+
128+
// Iterate over all panels and update their state as needed
129+
for panel in panels {
130+
let targetState = determineDesiredCollapseState(
131+
shouldHide: shouldHide,
132+
currentlyCollapsed: panel.isCollapsed(),
133+
previouslyCollapsed: panel.getPrevCollapsed(),
134+
)
135+
if panel.isCollapsed() != targetState {
136+
panel.toggle()
137+
}
138+
}
139+
}
140+
141+
/// Calculates the collapse state an interface element should have after a hide / show toggle.
142+
/// - Parameters:
143+
/// - shouldHide: `true` when we’re hiding the whole interface.
144+
/// - currentlyCollapsed: The panels current state
145+
/// - previouslyCollapsed: The state we saved the last time we hid the UI, if any.
146+
/// - Returns: `true` for visible element, `false` for collapsed element
147+
func determineDesiredCollapseState(shouldHide: Bool, currentlyCollapsed: Bool, previouslyCollapsed: Bool?) -> Bool {
148+
// If ShouldHide, everything should close
149+
if shouldHide {
150+
return true
151+
}
152+
153+
// If not hiding, and not currently collapsed, the panel should remain as such.
154+
if !currentlyCollapsed {
155+
return false
156+
}
157+
158+
// If the panel is currently collapsed and we are "showing" or "restoring":
159+
// Option 1: Restore to its previously remembered state if available.
160+
// Option 2: If no previously remembered state, default to making it visible (not collapsed).
161+
return previouslyCollapsed ?? false
162+
}
163+
164+
/// Function for storing the current interface visibility states
165+
func storeInterfaceCollapseState() {
166+
for panel in panels {
167+
panel.setPrevCollapsed(panel.isCollapsed())
168+
}
169+
}
170+
171+
/// Function for resetting the stored interface visibility states
172+
func resetStoredInterfaceCollapseState() {
173+
for panel in panels {
174+
panel.setPrevCollapsed(nil)
175+
}
176+
}
177+
}

CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift

Lines changed: 0 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -209,116 +209,4 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
209209
workspace = nil
210210
return true
211211
}
212-
213-
var utilityAreaCollapsed: Bool {
214-
workspace?.utilityAreaModel?.isCollapsed ?? true
215-
}
216-
217-
/// Returns `true` if at least one panel that was visible is still collapsed, meaning the interface is still hidden
218-
func isInterfaceStillHidden() -> Bool {
219-
// If the interface is already un-hidden, we can short-circuit.
220-
guard let prevNav = prevNavigatorCollapsed,
221-
let prevInsp = prevInspectorCollapsed,
222-
let prevUtil = prevUtilityAreaCollapsed,
223-
let prevTool = prevToolbarCollapsed
224-
else {
225-
return navigatorCollapsed && navigatorCollapsed && toolbarCollapsed && utilityAreaCollapsed
226-
}
227-
228-
let stillHidden = (!prevNav && navigatorCollapsed) ||
229-
(!prevInsp && inspectorCollapsed) ||
230-
(!prevUtil && utilityAreaCollapsed) ||
231-
(!prevTool && toolbarCollapsed)
232-
233-
if !stillHidden { resetStoredInterfaceCollapseState() }
234-
235-
// True when any panel that was previously visible is collapsed
236-
return stillHidden
237-
}
238-
239-
/// Function for toggling the interface elements on or off
240-
///
241-
/// - Parameter shouldHide: Pass `true` to hide all interface panels (and remember their current states),
242-
/// or `false` to restore them to how they were before hiding.
243-
func toggleInterface(shouldHide: Bool) {
244-
// When hiding, store how the interface looks now
245-
if shouldHide {
246-
storeInterfaceCollapseState()
247-
}
248-
249-
// Determine the desired collapsed/visible state for every interface element
250-
let navigatorTargetState = determineDesiredCollapseState(
251-
shouldHide: shouldHide,
252-
currentlyCollapsed: navigatorCollapsed,
253-
previouslyCollapsed: prevNavigatorCollapsed,
254-
)
255-
let inspectorTargetState = determineDesiredCollapseState(
256-
shouldHide: shouldHide,
257-
currentlyCollapsed: inspectorCollapsed,
258-
previouslyCollapsed: prevInspectorCollapsed,
259-
)
260-
let utilityAreaTargetState = determineDesiredCollapseState(
261-
shouldHide: shouldHide,
262-
currentlyCollapsed: utilityAreaCollapsed,
263-
previouslyCollapsed: prevUtilityAreaCollapsed,
264-
)
265-
let toolbarTargetState = determineDesiredCollapseState(
266-
shouldHide: shouldHide,
267-
currentlyCollapsed: toolbarCollapsed,
268-
previouslyCollapsed: prevToolbarCollapsed,
269-
)
270-
271-
// Toggle only the parts that need to change
272-
if navigatorCollapsed != navigatorTargetState {
273-
toggleFirstPanel(shouldAnimate: false)
274-
}
275-
if inspectorCollapsed != inspectorTargetState {
276-
toggleLastPanel(shouldAnimate: false)
277-
}
278-
if workspace?.utilityAreaModel?.isCollapsed != utilityAreaTargetState {
279-
CommandManager.shared.executeCommand("open.drawer.no.animation")
280-
}
281-
if toolbarCollapsed != toolbarTargetState {
282-
toggleToolbar()
283-
}
284-
}
285-
286-
/// Calculates the collapse state an interface element should have after a hide / show toggle.
287-
/// - Parameters:
288-
/// - shouldHide: `true` when we’re hiding the whole interface.
289-
/// - currentlyCollapsed: The panels current state
290-
/// - previouslyCollapsed: The state we saved the last time we hid the UI, if any.
291-
/// - Returns: `true` for visible element, `false` for collapsed element
292-
func determineDesiredCollapseState(shouldHide: Bool, currentlyCollapsed: Bool, previouslyCollapsed: Bool?) -> Bool {
293-
// If ShouldHide, everything should close
294-
if shouldHide { return true }
295-
296-
// If currently collapsed, and there is no previous state, show it.
297-
if previouslyCollapsed == nil && currentlyCollapsed { return false }
298-
299-
// If currently visible and !shouldHide, it should not collapse.
300-
if !currentlyCollapsed && !shouldHide { return false }
301-
302-
// If we have a previous state, return that one.
303-
if let remembered = previouslyCollapsed { return remembered }
304-
305-
// If there is no stored state, return the current state.
306-
return currentlyCollapsed
307-
}
308-
309-
/// Function for storing the current interface visibility states
310-
func storeInterfaceCollapseState() {
311-
prevNavigatorCollapsed = navigatorCollapsed
312-
prevInspectorCollapsed = inspectorCollapsed
313-
prevUtilityAreaCollapsed = workspace?.utilityAreaModel?.isCollapsed
314-
prevToolbarCollapsed = toolbarCollapsed
315-
}
316-
317-
/// Function for resetting the stored interface visibility states
318-
func resetStoredInterfaceCollapseState() {
319-
prevNavigatorCollapsed = nil
320-
prevInspectorCollapsed = nil
321-
prevUtilityAreaCollapsed = nil
322-
prevToolbarCollapsed = nil
323-
}
324212
}

CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,49 +9,6 @@ import SwiftUI
99
import Combine
1010

1111
extension CodeEditWindowController {
12-
@objc
13-
func objcToggleFirstPanel() {
14-
toggleFirstPanel(shouldAnimate: true)
15-
}
16-
17-
/// Toggles the navigator pane, optionally without animation.
18-
func toggleFirstPanel(shouldAnimate: Bool = true) {
19-
guard let firstSplitView = splitViewController?.splitViewItems.first else { return }
20-
21-
if shouldAnimate {
22-
// Standard animated toggle
23-
firstSplitView.animator().isCollapsed.toggle()
24-
} else {
25-
// Instant toggle (no animation)
26-
firstSplitView.isCollapsed.toggle()
27-
}
28-
29-
splitViewController?.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed)
30-
}
31-
32-
@objc
33-
func objcToggleLastPanel() {
34-
toggleLastPanel(shouldAnimate: true)
35-
}
36-
37-
func toggleLastPanel(shouldAnimate: Bool = true) {
38-
guard let lastSplitView = splitViewController?.splitViewItems.last else {
39-
return
40-
}
41-
42-
if shouldAnimate {
43-
// Standard animated toggle
44-
NSAnimationContext.runAnimationGroup { _ in
45-
lastSplitView.animator().isCollapsed.toggle()
46-
}
47-
} else {
48-
// Instant toggle (no animation)
49-
lastSplitView.isCollapsed.toggle()
50-
}
51-
52-
splitViewController?.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed)
53-
}
54-
5512
/// These are example items that added as commands to command palette
5613
func registerCommands() {
5714
CommandManager.shared.addCommand(

0 commit comments

Comments
 (0)