Skip to content

Commit 2f94292

Browse files
Show information for non-text files in the status bar (#1713)
* properly name `utilityAreaViewModel` across the project * add article docs for `CodeEdit/UtilityAreaView` * update article docs for `CodeEdit/StatusBarView` * properly name items in `CodeEdit/Features/UtilityArea/Toolbar` * remove UtilityAreaViewModel properties from status bar implementation * remove unused properties in `UtilityAreaViewModel` * delete `CursorLocation` model * rename `StatusBarCursorLocationLabel` to `StatusBarCursorPositionLabel` * add `StatusBarViewModel` * show non-text file info in the status bar * fix unpredictable image dimensions in status bar * add UpdateStatusBarInfo view modifier to clean up implementation * move the `NonTextFileView.onDisappear` modifier to inside the view * change `StatusBarViewModel.dimensions` from tuple to struct * refactor `StatusBarFileInfoView` * fix SwiftLint violation * minor doc change in `UpdateStatusBarInfo` * remove unused editor variable in `UpdateStatusBarInfo` * refactor - stylistic changes - docs change - properly name `UtilityAreaSplitTerminalButton` - remove unused `isBreakpointEnabled` in `StatusBarBreakpointButton` * Update CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarFileInfoView.swift * properly resolve merge conflict I used the GitHub UI, it didn't come out how I expected --------- Co-authored-by: Austin Condiff <austin.condiff@gmail.com>
1 parent 60e4b4f commit 2f94292

28 files changed

+370
-192
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 60 additions & 32 deletions
Large diffs are not rendered by default.

CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
8989
WorkspaceView()
9090
.environmentObject(workspace)
9191
.environmentObject(workspace.editorManager)
92+
.environmentObject(workspace.statusBarViewModel)
9293
.environmentObject(workspace.utilityAreaModel)
9394
}
9495
}

CodeEdit/Features/Documents/WorkspaceDocument.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
3030
}
3131
}
3232

33+
var statusBarViewModel = StatusBarViewModel()
3334
var utilityAreaModel = UtilityAreaViewModel()
3435
var searchState: SearchState?
3536
var quickOpenViewModel: QuickOpenViewModel?

CodeEdit/Features/Editor/Views/EditorAreaFileView.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import SwiftUI
1313
struct EditorAreaFileView: View {
1414

1515
@EnvironmentObject private var editorManager: EditorManager
16-
1716
@EnvironmentObject private var editor: Editor
1817

1918
@Environment(\.edgeInsets)

CodeEdit/Features/Editor/Views/ImageFileView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct ImageFileView: View {
4141
maxWidth: min(pixelWidth, proxy.size.width, nsImage.size.width),
4242
maxHeight: min(pixelHeight, proxy.size.height, nsImage.size.height)
4343
)
44+
4445
}
4546
.frame(width: proxy.size.width, height: proxy.size.height)
4647
}

CodeEdit/Features/Editor/Views/NonTextFileView.swift

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,38 @@ struct NonTextFileView: View {
1717
/// The file document you wish to open.
1818
let fileDocument: CodeFileDocument
1919

20+
@EnvironmentObject private var editorManager: EditorManager
21+
@EnvironmentObject private var statusBarViewModel: StatusBarViewModel
22+
2023
var body: some View {
2124

22-
if let fileURL = fileDocument.fileURL {
25+
Group {
26+
if let fileURL = fileDocument.fileURL {
2327

24-
switch fileDocument.utType {
25-
case .some(.image):
26-
ImageFileView(fileURL)
28+
switch fileDocument.utType {
29+
case .some(.image):
30+
ImageFileView(fileURL)
31+
.modifier(UpdateStatusBarInfo(withURL: fileURL))
2732

28-
case .some(.pdf):
29-
PDFFileView(fileURL)
33+
case .some(.pdf):
34+
PDFFileView(fileURL)
35+
.modifier(UpdateStatusBarInfo(withURL: fileURL))
3036

31-
default:
32-
AnyFileView(fileURL)
33-
}
37+
default:
38+
AnyFileView(fileURL)
39+
.modifier(UpdateStatusBarInfo(withURL: fileURL))
40+
}
3441

35-
} else {
36-
ZStack {
37-
Text("Cannot retrieve URL to the file you opened.")
42+
} else {
43+
ZStack {
44+
Text("Cannot retrieve URL to the file you opened.")
45+
}
3846
}
3947
}
48+
.onDisappear {
49+
statusBarViewModel.dimensions = nil
50+
statusBarViewModel.fileSize = nil
51+
}
4052

4153
}
4254
}

CodeEdit/Features/StatusBar/Models/CursorLocation.swift

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// ImageDimensions.swift
3+
// CodeEdit
4+
//
5+
// Created by Paul Ebose on 2024/5/13.
6+
//
7+
8+
import Foundation
9+
10+
/// Helper struct used to store the (width x height) of the currently opened image.
11+
struct ImageDimensions {
12+
var width: Int
13+
var height: Int
14+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// StatusBarViewModel.swift
3+
// CodeEdit
4+
//
5+
// Created by Paul Ebose on 2024/5/12.
6+
//
7+
8+
import SwiftUI
9+
10+
final class StatusBarViewModel: ObservableObject {
11+
12+
/// The file size of the currently opened file.
13+
@Published var fileSize: Int?
14+
15+
/// The dimensions (width x height) of the currently opened image.
16+
@Published var dimensions: ImageDimensions?
17+
18+
/// Indicates whether the breakpoint is enabled or not.
19+
@Published var isBreakpointEnabled = true
20+
21+
/// The font style of items shown in the status bar.
22+
private(set) var statusBarFont = Font.system(size: 11, weight: .medium)
23+
24+
/// The color of the text shown in the status bar.
25+
private(set) var foregroundStyle = Color.secondary
26+
27+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// UpdateStatusBarInfo.swift
3+
// CodeEdit
4+
//
5+
// Created by Paul Ebose on 2024/5/12.
6+
//
7+
8+
import SwiftUI
9+
10+
/// Updates ``StatusBarFileInfoView``'s `fileSize` and `dimensions`.
11+
/// ```swift
12+
/// FileView
13+
/// .modifier(UpdateStatusBarInfo(withURL))
14+
/// ```
15+
struct UpdateStatusBarInfo: ViewModifier {
16+
17+
/// The URL of the file to compute information from.
18+
let withURL: URL
19+
20+
@EnvironmentObject private var editorManager: EditorManager
21+
@EnvironmentObject private var statusBarViewModel: StatusBarViewModel
22+
23+
/// This is returned by ``UpdateStatusBarInfo`` `.computeStatusBarInfo`.
24+
private struct ComputedStatusBarInfo {
25+
let fileSize: Int
26+
let dimensions: ImageDimensions?
27+
}
28+
29+
/// Compute information that can be used to update properties in ``StatusBarFileInfoView``.
30+
/// - Parameter url: URL of the file to compute information from.
31+
/// - Returns: The file size and its image dimensions (if any).
32+
private func computeStatusBarInfo(url: URL) -> ComputedStatusBarInfo? {
33+
guard let resourceValues = try? url.resourceValues(forKeys: [.contentTypeKey, .fileSizeKey]),
34+
let contentType = resourceValues.contentType,
35+
let fileSize = resourceValues.fileSize
36+
else {
37+
return nil
38+
}
39+
40+
if contentType.conforms(to: .image), let imageReps = NSImage(contentsOf: url)?.representations.first {
41+
let dimensions = ImageDimensions(width: imageReps.pixelsWide, height: imageReps.pixelsHigh)
42+
return ComputedStatusBarInfo(fileSize: fileSize, dimensions: dimensions)
43+
} else { // non-image file
44+
return ComputedStatusBarInfo(fileSize: fileSize, dimensions: nil)
45+
}
46+
}
47+
48+
func body(content: Content) -> some View {
49+
content
50+
.onAppear {
51+
let statusBarInfo = computeStatusBarInfo(url: withURL)
52+
statusBarViewModel.fileSize = statusBarInfo?.fileSize
53+
statusBarViewModel.dimensions = statusBarInfo?.dimensions
54+
}
55+
.onChange(of: editorManager.activeEditor.selectedTab) { newTab in
56+
guard let newTab else { return }
57+
let statusBarInfo = computeStatusBarInfo(url: newTab.file.url)
58+
statusBarViewModel.fileSize = statusBarInfo?.fileSize
59+
statusBarViewModel.dimensions = statusBarInfo?.dimensions
60+
}
61+
}
62+
63+
}

CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarBreakpointButton.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// StatusBarBreakpointButton.swift
3-
// CodeEditModules/StatusBar
3+
// CodeEdit
44
//
55
// Created by Stef Kors on 14/04/2022.
66
//
@@ -9,13 +9,13 @@ import SwiftUI
99
import CodeEditSymbols
1010

1111
struct StatusBarBreakpointButton: View {
12-
@EnvironmentObject private var model: UtilityAreaViewModel
12+
@EnvironmentObject private var statusBarViewModel: StatusBarViewModel
1313

1414
var body: some View {
1515
Button {
16-
model.isBreakpointEnabled.toggle()
16+
statusBarViewModel.isBreakpointEnabled.toggle()
1717
} label: {
18-
if model.isBreakpointEnabled {
18+
if statusBarViewModel.isBreakpointEnabled {
1919
Image.breakpointFill
2020
.foregroundColor(.accentColor)
2121
} else {

CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorLocationLabel.swift renamed to CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorPositionLabel.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
//
2-
// StatusBarCursorLocationLabel.swift
3-
// CodeEditModules/StatusBar
2+
// StatusBarCursorPositionLabel.swift
3+
// CodeEdit
44
//
55
// Created by Lukas Pistrol on 22.03.22.
66
//
77

88
import SwiftUI
99
import CodeEditSourceEditor
1010

11-
struct StatusBarCursorLocationLabel: View {
11+
struct StatusBarCursorPositionLabel: View {
1212
@Environment(\.controlActiveState)
1313
private var controlActive
1414
@Environment(\.modifierKeys)
1515
private var modifierKeys
1616

17-
@EnvironmentObject private var model: UtilityAreaViewModel
17+
@EnvironmentObject private var statusBarViewModel: StatusBarViewModel
18+
@EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel
1819
@EnvironmentObject private var editorManager: EditorManager
1920

2021
@State private var tab: EditorInstance?
@@ -87,7 +88,7 @@ struct StatusBarCursorLocationLabel: View {
8788
EmptyView()
8889
}
8990
}
90-
.font(model.toolbarFont)
91+
.font(statusBarViewModel.statusBarFont)
9192
.foregroundColor(foregroundColor)
9293
.fixedSize()
9394
.lineLimit(1)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// StatusBarFileInfoView.swift
3+
// CodeEdit
4+
//
5+
// Created by Paul Ebose on 2024/5/12.
6+
//
7+
8+
import SwiftUI
9+
10+
/// Shows media information about the currently opened file.
11+
///
12+
/// This currently shows the file size and image dimensions, if available.
13+
struct StatusBarFileInfoView: View {
14+
15+
@EnvironmentObject private var statusBarViewModel: StatusBarViewModel
16+
17+
private let dimensionsNumberStyle = IntegerFormatStyle<Int>(locale: Locale(identifier: "en_US")).grouping(.never)
18+
19+
var body: some View {
20+
21+
HStack(spacing: 15) {
22+
23+
if let dimensions = statusBarViewModel.dimensions {
24+
let width = dimensionsNumberStyle.format(dimensions.width)
25+
let height = dimensionsNumberStyle.format(dimensions.height)
26+
27+
Text("\(width) × \(height)")
28+
}
29+
30+
if let fileSize = statusBarViewModel.fileSize {
31+
Text(fileSize.formatted(.byteCount(style: .memory)))
32+
}
33+
34+
}
35+
.font(statusBarViewModel.statusBarFont)
36+
.foregroundStyle(statusBarViewModel.foregroundStyle)
37+
}
38+
39+
}

CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarToggleUtilityAreaButton.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// StatusBarToggleUtilityAreaButton.swift
3-
// CodeEditModules/StatusBar
3+
// CodeEdit
44
//
55
// Created by Lukas Pistrol on 22.03.22.
66
//
@@ -11,25 +11,25 @@ internal struct StatusBarToggleUtilityAreaButton: View {
1111
@Environment(\.controlActiveState)
1212
var controlActiveState
1313

14-
@EnvironmentObject private var model: UtilityAreaViewModel
14+
@EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel
1515

1616
internal var body: some View {
1717
Button {
18-
model.togglePanel()
18+
utilityAreaViewModel.togglePanel()
1919
} label: {
2020
Image(systemName: "square.bottomthird.inset.filled")
2121
}
2222
.buttonStyle(.icon)
2323
.keyboardShortcut("Y", modifiers: [.command, .shift])
24-
.help(model.isCollapsed ? "Show the Utility area" : "Hide the Utility area")
24+
.help(utilityAreaViewModel.isCollapsed ? "Show the Utility area" : "Hide the Utility area")
2525
.onHover { isHovering($0) }
2626
.onChange(of: controlActiveState) { newValue in
2727
if newValue == .key {
2828
CommandManager.shared.addCommand(
2929
name: "Toggle Utility Area",
3030
title: "Toggle Utility Area",
3131
id: "open.drawer",
32-
command: CommandClosureWrapper.init(closure: model.togglePanel)
32+
command: CommandClosureWrapper.init(closure: utilityAreaViewModel.togglePanel)
3333
)
3434
}
3535
}
@@ -38,7 +38,7 @@ internal struct StatusBarToggleUtilityAreaButton: View {
3838
name: "Toggle Utility Area",
3939
title: "Toggle Utility Area",
4040
id: "open.drawer",
41-
command: CommandClosureWrapper.init(closure: model.togglePanel)
41+
command: CommandClosureWrapper.init(closure: utilityAreaViewModel.togglePanel)
4242
)
4343
}
4444
}

CodeEdit/Features/StatusBar/Views/StatusBarView.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// StatusBarView.swift
3-
// CodeEditModules/StatusBar
3+
// CodeEdit
44
//
55
// Created by Lukas Pistrol on 19.03.22.
66
//
@@ -11,7 +11,9 @@ import SwiftUI
1111
///
1212
/// A View that lives on the bottom of the window and offers information
1313
/// about compilation errors/warnings, git, cursor position in text,
14-
/// indentation width (in spaces), text encoding and linebreak
14+
/// indentation width (in spaces), text encoding and linebreak.
15+
///
16+
/// Also information about the file size and dimensions, if available.
1517
///
1618
/// Additionally it offers a togglable/resizable drawer which can
1719
/// host a terminal or additional debug information
@@ -20,8 +22,6 @@ struct StatusBarView: View {
2022
@Environment(\.controlActiveState)
2123
private var controlActive
2224

23-
@EnvironmentObject private var model: UtilityAreaViewModel
24-
2525
static let height = 28.0
2626

2727
@Environment(\.colorScheme)
@@ -37,8 +37,9 @@ struct StatusBarView: View {
3737
// StatusBarBreakpointButton()
3838
// StatusBarDivider()
3939
Spacer()
40+
StatusBarFileInfoView()
4041
HStack(alignment: .center, spacing: 10) {
41-
StatusBarCursorLocationLabel()
42+
StatusBarCursorPositionLabel()
4243
}
4344
StatusBarDivider()
4445
StatusBarToggleUtilityAreaButton()

0 commit comments

Comments
 (0)