Skip to content

Commit

Permalink
Fixed terminal resize and scroll layout issues, improved utility area…
Browse files Browse the repository at this point in the history
… toggle animation (#1845)

When the terminal was resized, it displayed partial lines at the bottom due to a remainder in the line height multiple. This caused two things:

1. Resizing the Utility Area drawer caused a visual stutter in the terminal.
2. Partial lines at the bottom created an unsightly visual artifact.

The terminal height must now be a multiple of a single line’s height to resolve these issues. 

Utility area drawer now has a push/pull animation rather than a reveal.

### Screenshots

Before

https://github.com/user-attachments/assets/f092611e-fe28-4aeb-b4dd-408611f0229e

https://github.com/user-attachments/assets/4c365d3d-e051-4bc4-86ef-7121e4b8a5a5

After

https://github.com/user-attachments/assets/7fb1a8d0-1aac-42f2-8a0a-22c02a7511b0

VS Code handles this in a similar way

https://github.com/user-attachments/assets/67507c80-e39e-45f4-8432-5e132df5116a

Improved utility area animation

https://github.com/user-attachments/assets/36825fd0-caa0-463e-a367-5c17cde7dce2

> [!NOTE]
> I removed the maximize drawer toggle in favor of simply dragging to resize up. This simplifies things greatly.

Co-authored-by: Tom Ludwig <[email protected]>
  • Loading branch information
austincondiff and tom-ludwig authored Aug 16, 2024
1 parent bf99e7d commit 6719995
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/groue/Semaphore",
"state" : {
"revision" : "f1c4a0acabeb591068dea6cffdd39660b86dec28",
"version" : "0.0.8"
"revision" : "2543679282aa6f6c8ecf2138acd613ed20790bc2",
"version" : "0.1.0"
}
},
{
Expand Down Expand Up @@ -240,8 +240,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"state" : {
"revision" : "668a65735751432b640260c56dfa621cec568368",
"version" : "1.2.0"
"revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336",
"version" : "1.3.0"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI
import Cocoa

final class UtilityAreaTerminal: ObservableObject, Identifiable, Equatable {
let id: UUID
Expand Down Expand Up @@ -36,6 +37,12 @@ struct UtilityAreaTerminalView: View {
private var darkAppearance
@AppSettings(\.theme.useThemeBackground)
private var useThemeBackground
@AppSettings(\.textEditing.font)
private var textEditingFont
@AppSettings(\.terminal.font)
private var terminalFont
@AppSettings(\.terminal.useTextEditorFont)
private var useTextEditorFont

@Environment(\.colorScheme)
private var colorScheme
Expand All @@ -52,6 +59,10 @@ struct UtilityAreaTerminalView: View {

@State private var popoverSource: CGRect = .zero

var font: NSFont {
useTextEditorFont == true ? textEditingFont.current : terminalFont.current
}

private func initializeTerminals() {
let id = UUID()

Expand Down Expand Up @@ -101,31 +112,52 @@ struct UtilityAreaTerminalView: View {
utilityAreaViewModel.terminals.move(fromOffsets: source, toOffset: destination)
}

func fontTotalHeight(nsFont: NSFont) -> CGFloat {
let ctFont = nsFont as CTFont
let ascent = CTFontGetAscent(ctFont)
let descent = CTFontGetDescent(ctFont)
let leading = CTFontGetLeading(ctFont)

return ascent + descent + leading
}

var body: some View {
UtilityAreaTabView(model: utilityAreaViewModel.tabViewModel) { tabState in
ZStack {
if utilityAreaViewModel.selectedTerminals.isEmpty {
CEContentUnavailableView("No Selection")
}
ForEach(utilityAreaViewModel.terminals) { terminal in
TerminalEmulatorView(
url: terminal.url!,
shellType: terminal.shell,
onTitleChange: { [weak terminal] newTitle in
guard let id = terminal?.id else { return }
// This can be called whenever, even in a view update so it needs to be dispatched.
DispatchQueue.main.async { [weak utilityAreaViewModel] in
utilityAreaViewModel?.updateTerminal(id, title: newTitle)
} else {
GeometryReader { geometry in
let containerHeight = geometry.size.height
let totalFontHeight = fontTotalHeight(nsFont: font).rounded(.up)
let constrainedHeight = containerHeight - containerHeight.truncatingRemainder(
dividingBy: totalFontHeight
)
ForEach(utilityAreaViewModel.terminals) { terminal in
VStack(spacing: 0) {
Spacer(minLength: 0)
.frame(minHeight: 0)
TerminalEmulatorView(
url: terminal.url!,
shellType: terminal.shell,
onTitleChange: { [weak terminal] newTitle in
guard let id = terminal?.id else { return }
// This can be called whenever, even in a view update
// so it needs to be dispatched.
DispatchQueue.main.async { [weak utilityAreaViewModel] in
utilityAreaViewModel?.updateTerminal(id, title: newTitle)
}
}
)
.frame(height: constrainedHeight - totalFontHeight + 1)
}
.disabled(terminal.id != utilityAreaViewModel.selectedTerminals.first)
.opacity(terminal.id == utilityAreaViewModel.selectedTerminals.first ? 1 : 0)
}
)
.padding(.top, 10)
.padding(.horizontal, 10)
.contentShape(Rectangle())
.disabled(terminal.id != utilityAreaViewModel.selectedTerminals.first)
.opacity(terminal.id == utilityAreaViewModel.selectedTerminals.first ? 1 : 0)
}
}
}
.padding(.horizontal, 10)
.paneToolbar {
PaneToolbarSection {
UtilityAreaTerminalPicker(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ class UtilityAreaViewModel: ObservableObject {
}

func togglePanel() {
withAnimation {
self.isCollapsed.toggle()
}
self.isMaximized = false
self.isCollapsed.toggle()
}

/// Update a terminal's title.
Expand Down
10 changes: 3 additions & 7 deletions CodeEdit/Features/UtilityArea/Views/PaneToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ struct PaneToolbar<Content: View>: View {
.frame(width: 24)
}
.opacity(0)
Divider().opacity(0)
}
content
if model.hasTrailingSidebar
Expand All @@ -35,16 +34,13 @@ struct PaneToolbar<Content: View>: View {
&& model.trailingSidebarIsCollapsed)
|| paneArea == .trailing
) || !model.hasTrailingSidebar {
Divider().opacity(0)
PaneToolbarSection {
if model.hasTrailingSidebar {
if model.hasTrailingSidebar {
PaneToolbarSection {
Spacer()
.frame(width: 24)
}
Spacer()
.frame(width: 24)
.opacity(0)
}
.opacity(0)
}
}
.buttonStyle(.icon(size: 24))
Expand Down
23 changes: 0 additions & 23 deletions CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,5 @@ struct UtilityAreaView: View {
.overlay(Color(nsColor: colorScheme == .dark ? .black : .clear))
}
}
.overlay(alignment: .bottomTrailing) {
HStack(spacing: 5) {
Divider()
HStack(spacing: 0) {
Button {
utilityAreaViewModel.isMaximized.toggle()
} label: {
Image(systemName: "arrowtriangle.up.square")
}
.buttonStyle(.icon(isActive: utilityAreaViewModel.isMaximized, size: 24))
}
}
.colorScheme(
utilityAreaViewModel.selectedTerminals.isEmpty
? colorScheme
: matchAppearance && darkAppearance
? themeModel.selectedDarkTheme?.appearance == .dark ? .dark : .light
: themeModel.selectedTheme?.appearance == .dark ? .dark : .light
)
.padding(.horizontal, 5)
.padding(.vertical, 8)
.frame(maxHeight: 27)
}
}
}
75 changes: 63 additions & 12 deletions CodeEdit/WorkspaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ struct WorkspaceView: View {
@State private var showingAlert = false
@State private var terminalCollapsed = true
@State private var editorCollapsed = false
@State private var editorsHeight: CGFloat = 0
@State private var drawerHeight: CGFloat = 0

private let statusbarHeight: CGFloat = 29

private var keybindings: KeybindingManager = .shared

Expand All @@ -36,28 +40,75 @@ struct WorkspaceView: View {
VStack {
SplitViewReader { proxy in
SplitView(axis: .vertical) {
EditorLayoutView(
layout: editorManager.isFocusingActiveEditor
? editorManager.activeEditor.getEditorLayout() ?? editorManager.editorLayout
: editorManager.editorLayout,
focus: $focusedEditor
)
ZStack {
GeometryReader { geo in
EditorLayoutView(
layout: editorManager.isFocusingActiveEditor
? editorManager.activeEditor.getEditorLayout() ?? editorManager.editorLayout
: editorManager.editorLayout,
focus: $focusedEditor
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onChange(of: geo.size.height) { newHeight in
editorsHeight = newHeight
}
.onAppear {
editorsHeight = geo.size.height
}
}
}
.frame(minHeight: 170 + 29 + 29)
.collapsable()
.collapsed($utilityAreaViewModel.isMaximized)
.frame(minHeight: 170 + 29 + 29)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.holdingPriority(.init(1))
.safeAreaInset(edge: .bottom, spacing: 0) {
StatusBarView(proxy: proxy)
}
UtilityAreaView()
Rectangle()
.collapsable()
.collapsed($utilityAreaViewModel.isCollapsed)
.opacity(0)
.frame(idealHeight: 260)
.frame(minHeight: 100)
.background {
GeometryReader { geo in
Rectangle()
.opacity(0)
.onChange(of: geo.size.height) { newHeight in
drawerHeight = newHeight
}
.onAppear {
drawerHeight = geo.size.height
}
}
}
}
.edgesIgnoringSafeArea(.top)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay(alignment: .top) {
ZStack(alignment: .top) {
UtilityAreaView()
.frame(height: utilityAreaViewModel.isMaximized ? nil : drawerHeight)
.frame(maxHeight: utilityAreaViewModel.isMaximized ? .infinity : nil)
.padding(.top, utilityAreaViewModel.isMaximized ? statusbarHeight + 1 : 0)
.offset(y: utilityAreaViewModel.isMaximized ? 0 : editorsHeight + 1)
VStack(spacing: 0) {
StatusBarView(proxy: proxy)
if utilityAreaViewModel.isMaximized {
PanelDivider()
}
}
.offset(y: utilityAreaViewModel.isMaximized ? 0 : editorsHeight - statusbarHeight)
}
}
.onChange(of: focusedEditor) { newValue in
/// update active tab group only if the new one is not the same with it.
if let newValue, editorManager.activeEditor != newValue {
editorManager.activeEditor = newValue
}
}
.onChange(of: editorManager.activeEditor) { newValue in
if newValue != focusedEditor {
focusedEditor = newValue
}
}
.task {
themeModel.colorScheme = colorScheme

Expand Down

0 comments on commit 6719995

Please sign in to comment.