replace sidebar with separate document browser window
This commit is contained in:
parent
db1a0aaefa
commit
1ccea45a6f
|
|
@ -47,6 +47,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
setupMenuBar()
|
setupMenuBar()
|
||||||
observeDocumentTitle()
|
observeDocumentTitle()
|
||||||
|
|
||||||
|
DocumentBrowserController.shared = DocumentBrowserController(appState: appState)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
self, selector: #selector(settingsDidChange),
|
self, selector: #selector(settingsDidChange),
|
||||||
name: .settingsChanged, object: nil
|
name: .settingsChanged, object: nil
|
||||||
|
|
@ -192,7 +194,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
private func buildViewMenu() -> NSMenuItem {
|
private func buildViewMenu() -> NSMenuItem {
|
||||||
let item = NSMenuItem()
|
let item = NSMenuItem()
|
||||||
let menu = NSMenu(title: "View")
|
let menu = NSMenu(title: "View")
|
||||||
let toggleItem = NSMenuItem(title: "Toggle Sidebar", action: #selector(toggleSidebar), keyEquivalent: "b")
|
let toggleItem = NSMenuItem(title: "Document Browser", action: #selector(toggleBrowser), keyEquivalent: "b")
|
||||||
toggleItem.keyEquivalentModifierMask = .control
|
toggleItem.keyEquivalentModifierMask = .control
|
||||||
toggleItem.target = self
|
toggleItem.target = self
|
||||||
menu.addItem(toggleItem)
|
menu.addItem(toggleItem)
|
||||||
|
|
@ -402,16 +404,24 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
window.contentView?.needsDisplay = true
|
window.contentView?.needsDisplay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func toggleSidebar() {
|
@objc private func toggleBrowser() {
|
||||||
NotificationCenter.default.post(name: .toggleSidebar, object: nil)
|
DocumentBrowserController.shared?.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func zoomIn() {
|
@objc private func zoomIn() {
|
||||||
|
if let browser = DocumentBrowserController.shared, browser.window.isKeyWindow {
|
||||||
|
browser.browserState.scaleUp()
|
||||||
|
return
|
||||||
|
}
|
||||||
ConfigManager.shared.zoomLevel += 1
|
ConfigManager.shared.zoomLevel += 1
|
||||||
NotificationCenter.default.post(name: .settingsChanged, object: nil)
|
NotificationCenter.default.post(name: .settingsChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func zoomOut() {
|
@objc private func zoomOut() {
|
||||||
|
if let browser = DocumentBrowserController.shared, browser.window.isKeyWindow {
|
||||||
|
browser.browserState.scaleDown()
|
||||||
|
return
|
||||||
|
}
|
||||||
let current = ConfigManager.shared.zoomLevel
|
let current = ConfigManager.shared.zoomLevel
|
||||||
if 11 + current > 8 {
|
if 11 + current > 8 {
|
||||||
ConfigManager.shared.zoomLevel -= 1
|
ConfigManager.shared.zoomLevel -= 1
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,21 @@ import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ObservedObject var state: AppState
|
@ObservedObject var state: AppState
|
||||||
@State private var sidebarVisible: Bool = false
|
|
||||||
@State private var themeVersion: Int = 0
|
@State private var themeVersion: Int = 0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let _ = themeVersion
|
let _ = themeVersion
|
||||||
HSplitView {
|
EditorView(state: state)
|
||||||
if sidebarVisible {
|
.frame(minWidth: 400)
|
||||||
SidebarView(state: state)
|
.frame(minWidth: 700, minHeight: 400)
|
||||||
.frame(minWidth: 180, idealWidth: 250, maxWidth: 350)
|
.background(Color(ns: Theme.current.base))
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .settingsChanged)) { _ in
|
||||||
|
themeVersion += 1
|
||||||
}
|
}
|
||||||
EditorView(state: state)
|
|
||||||
.frame(minWidth: 400)
|
|
||||||
}
|
|
||||||
.frame(minWidth: 700, minHeight: 400)
|
|
||||||
.background(Color(ns: Theme.current.base))
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .toggleSidebar)) { _ in
|
|
||||||
withAnimation { sidebarVisible.toggle() }
|
|
||||||
}
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .settingsChanged)) { _ in
|
|
||||||
themeVersion += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
static let toggleSidebar = Notification.Name("toggleSidebar")
|
|
||||||
static let focusEditor = Notification.Name("focusEditor")
|
static let focusEditor = Notification.Name("focusEditor")
|
||||||
static let focusTitle = Notification.Name("focusTitle")
|
static let focusTitle = Notification.Name("focusTitle")
|
||||||
static let formatDocument = Notification.Name("formatDocument")
|
static let formatDocument = Notification.Name("formatDocument")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
import Cocoa
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class DocumentBrowserController {
|
||||||
|
static var shared: DocumentBrowserController?
|
||||||
|
|
||||||
|
let window: NSWindow
|
||||||
|
let browserState: BrowserState
|
||||||
|
private let hostingView: NSHostingView<DocumentBrowserView>
|
||||||
|
|
||||||
|
init(appState: AppState) {
|
||||||
|
browserState = BrowserState(appState: appState)
|
||||||
|
|
||||||
|
let view = DocumentBrowserView(state: browserState)
|
||||||
|
hostingView = NSHostingView(rootView: view)
|
||||||
|
|
||||||
|
window = NSWindow(
|
||||||
|
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
|
||||||
|
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
||||||
|
backing: .buffered,
|
||||||
|
defer: false
|
||||||
|
)
|
||||||
|
window.title = "Documents"
|
||||||
|
window.backgroundColor = Theme.current.base
|
||||||
|
window.contentView = hostingView
|
||||||
|
window.setFrameAutosaveName("SwiftlyBrowser")
|
||||||
|
window.center()
|
||||||
|
window.isReleasedWhenClosed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggle() {
|
||||||
|
if window.isVisible {
|
||||||
|
window.orderOut(nil)
|
||||||
|
} else {
|
||||||
|
browserState.refresh()
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrowserState: ObservableObject {
|
||||||
|
@Published var notes: [NoteInfo] = []
|
||||||
|
@Published var cardScale: CGFloat = 1.0
|
||||||
|
@Published var selectedID: UUID?
|
||||||
|
|
||||||
|
let appState: AppState
|
||||||
|
private let bridge = RustBridge.shared
|
||||||
|
private var cancellable: AnyCancellable?
|
||||||
|
|
||||||
|
init(appState: AppState) {
|
||||||
|
self.appState = appState
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
cancellable = appState.$noteList
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] list in
|
||||||
|
self?.notes = list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refresh() {
|
||||||
|
appState.refreshNoteList()
|
||||||
|
notes = appState.noteList
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewText(for id: UUID) -> String {
|
||||||
|
if bridge.cacheLoad(id) {
|
||||||
|
let text = bridge.getText(id)
|
||||||
|
let lines = text.components(separatedBy: "\n")
|
||||||
|
return lines.prefix(20).joined(separator: "\n")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func openNote(_ id: UUID) {
|
||||||
|
appState.openNote(id)
|
||||||
|
DocumentBrowserController.shared?.window.orderOut(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renameNote(_ id: UUID, to newTitle: String) {
|
||||||
|
guard bridge.cacheLoad(id) else { return }
|
||||||
|
let text = bridge.getText(id)
|
||||||
|
let lines = text.components(separatedBy: "\n")
|
||||||
|
var rest = Array(lines.dropFirst())
|
||||||
|
if rest.isEmpty && text.isEmpty { rest = [] }
|
||||||
|
let newText = ([newTitle] + rest).joined(separator: "\n")
|
||||||
|
bridge.setText(id, text: newText)
|
||||||
|
let _ = bridge.cacheSave(id)
|
||||||
|
if id == appState.currentNoteID {
|
||||||
|
appState.documentText = newText
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func duplicateNote(_ id: UUID) {
|
||||||
|
guard bridge.cacheLoad(id) else { return }
|
||||||
|
let text = bridge.getText(id)
|
||||||
|
let newID = bridge.newDocument()
|
||||||
|
bridge.setText(newID, text: text)
|
||||||
|
let _ = bridge.cacheSave(newID)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func trashNote(_ id: UUID) {
|
||||||
|
let dir = ConfigManager.shared.autoSaveDirectory
|
||||||
|
let dirURL = URL(fileURLWithPath: dir)
|
||||||
|
let note = notes.first { $0.id == id }
|
||||||
|
if let note = note {
|
||||||
|
let filename = note.title.isEmpty ? id.uuidString.lowercased() : note.title
|
||||||
|
let fileURL = dirURL.appendingPathComponent(filename + ".md")
|
||||||
|
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||||
|
try? FileManager.default.trashItem(at: fileURL, resultingItemURL: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appState.deleteNote(id)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func revealInFinder(_ id: UUID) {
|
||||||
|
let dir = ConfigManager.shared.autoSaveDirectory
|
||||||
|
let dirURL = URL(fileURLWithPath: dir)
|
||||||
|
let note = notes.first { $0.id == id }
|
||||||
|
if let note = note {
|
||||||
|
let filename = note.title.isEmpty ? id.uuidString.lowercased() : note.title
|
||||||
|
let fileURL = dirURL.appendingPathComponent(filename + ".md")
|
||||||
|
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||||
|
NSWorkspace.shared.activateFileViewerSelecting([fileURL])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSWorkspace.shared.open(dirURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scaleUp() {
|
||||||
|
cardScale = min(cardScale + 0.1, 3.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scaleDown() {
|
||||||
|
cardScale = max(cardScale - 0.1, 0.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DocumentBrowserView: View {
|
||||||
|
@ObservedObject var state: BrowserState
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
LazyVGrid(
|
||||||
|
columns: [GridItem(.adaptive(minimum: 200 * state.cardScale, maximum: 400 * state.cardScale))],
|
||||||
|
spacing: 16 * state.cardScale
|
||||||
|
) {
|
||||||
|
ForEach(state.notes) { note in
|
||||||
|
DocumentCard(note: note, state: state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(16 * state.cardScale)
|
||||||
|
}
|
||||||
|
.background(Color(ns: Theme.current.base))
|
||||||
|
.frame(minWidth: 400, minHeight: 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DocumentCard: View {
|
||||||
|
let note: NoteInfo
|
||||||
|
@ObservedObject var state: BrowserState
|
||||||
|
@State private var isRenaming = false
|
||||||
|
@State private var renameText = ""
|
||||||
|
|
||||||
|
private var isSelected: Bool {
|
||||||
|
state.selectedID == note.id
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 6 * state.cardScale) {
|
||||||
|
Text(state.previewText(for: note.id))
|
||||||
|
.font(.system(size: 10 * state.cardScale, design: .monospaced))
|
||||||
|
.foregroundColor(Color(ns: Theme.current.subtext0))
|
||||||
|
.lineLimit(nil)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
|
.padding(8 * state.cardScale)
|
||||||
|
.background(Color(ns: Theme.current.mantle))
|
||||||
|
.cornerRadius(4 * state.cardScale)
|
||||||
|
|
||||||
|
if isRenaming {
|
||||||
|
TextField("Title", text: $renameText, onCommit: {
|
||||||
|
state.renameNote(note.id, to: renameText)
|
||||||
|
isRenaming = false
|
||||||
|
})
|
||||||
|
.textFieldStyle(.plain)
|
||||||
|
.font(.system(size: 12 * state.cardScale, weight: .semibold))
|
||||||
|
.foregroundColor(Color(ns: Theme.current.text))
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
} else {
|
||||||
|
Text(note.title)
|
||||||
|
.font(.system(size: 12 * state.cardScale, weight: .semibold))
|
||||||
|
.foregroundColor(Color(ns: Theme.current.text))
|
||||||
|
.lineLimit(2)
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(10 * state.cardScale)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 8 * state.cardScale)
|
||||||
|
.fill(Color(ns: isSelected ? Theme.current.surface1 : Theme.current.surface0))
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8 * state.cardScale)
|
||||||
|
.stroke(isSelected ? Color(ns: Theme.current.blue) : Color.clear, lineWidth: 2)
|
||||||
|
)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture(count: 2) {
|
||||||
|
state.openNote(note.id)
|
||||||
|
}
|
||||||
|
.onTapGesture(count: 1) {
|
||||||
|
state.selectedID = note.id
|
||||||
|
}
|
||||||
|
.contextMenu {
|
||||||
|
Button("Open") { state.openNote(note.id) }
|
||||||
|
Button("Rename") {
|
||||||
|
renameText = note.title
|
||||||
|
isRenaming = true
|
||||||
|
}
|
||||||
|
Button("Duplicate") { state.duplicateNote(note.id) }
|
||||||
|
Divider()
|
||||||
|
Button("Move to Trash") { state.trashNote(note.id) }
|
||||||
|
Divider()
|
||||||
|
Button("Reveal in Finder") { state.revealInFinder(note.id) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,183 +1 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SidebarView: View {
|
|
||||||
@ObservedObject var state: AppState
|
|
||||||
@State private var lastClickedID: UUID?
|
|
||||||
@State private var previewNote: NoteInfo?
|
|
||||||
@FocusState private var sidebarFocused: Bool
|
|
||||||
|
|
||||||
private let dateFormatter: DateFormatter = {
|
|
||||||
let f = DateFormatter()
|
|
||||||
f.dateStyle = .short
|
|
||||||
f.timeStyle = .short
|
|
||||||
return f
|
|
||||||
}()
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
HStack {
|
|
||||||
Text("Notes")
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(Color(ns: Theme.current.text))
|
|
||||||
Spacer()
|
|
||||||
Button(action: { state.newNote() }) {
|
|
||||||
Image(systemName: "plus")
|
|
||||||
.foregroundColor(Color(ns: Theme.current.text))
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.help("New Note")
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
if state.noteList.isEmpty {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
Text("No notes yet")
|
|
||||||
.foregroundColor(Color(ns: Theme.current.overlay1))
|
|
||||||
.font(Font(Theme.sidebarFont as CTFont))
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ScrollView {
|
|
||||||
LazyVStack(spacing: 0) {
|
|
||||||
ForEach(Array(state.noteList.enumerated()), id: \.element.id) { index, note in
|
|
||||||
NoteRow(
|
|
||||||
note: note,
|
|
||||||
isSelected: state.selectedNoteIDs.contains(note.id),
|
|
||||||
isActive: note.id == state.currentNoteID,
|
|
||||||
dateFormatter: dateFormatter
|
|
||||||
)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onTapGesture(count: 1) {
|
|
||||||
handleClick(note: note, index: index)
|
|
||||||
}
|
|
||||||
.contextMenu {
|
|
||||||
if state.selectedNoteIDs.count > 1 && state.selectedNoteIDs.contains(note.id) {
|
|
||||||
Button("Delete \(state.selectedNoteIDs.count) Notes") {
|
|
||||||
state.deleteNotes(state.selectedNoteIDs)
|
|
||||||
lastClickedID = nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button("Delete") { state.deleteNote(note.id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.focusable()
|
|
||||||
.focused($sidebarFocused)
|
|
||||||
.onDeleteCommand {
|
|
||||||
guard !state.selectedNoteIDs.isEmpty else { return }
|
|
||||||
state.deleteNotes(state.selectedNoteIDs)
|
|
||||||
lastClickedID = nil
|
|
||||||
}
|
|
||||||
.onKeyPress(.space) {
|
|
||||||
guard let id = state.selectedNoteIDs.first,
|
|
||||||
state.selectedNoteIDs.count == 1,
|
|
||||||
let note = state.noteList.first(where: { $0.id == id }) else {
|
|
||||||
return .ignored
|
|
||||||
}
|
|
||||||
if previewNote?.id == note.id {
|
|
||||||
previewNote = nil
|
|
||||||
} else {
|
|
||||||
previewNote = note
|
|
||||||
}
|
|
||||||
return .handled
|
|
||||||
}
|
|
||||||
.popover(item: $previewNote, arrowEdge: .trailing) { note in
|
|
||||||
NotePreviewView(note: note, state: state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background(Color(ns: Theme.current.base))
|
|
||||||
.onAppear { state.refreshNoteList() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleClick(note: NoteInfo, index: Int) {
|
|
||||||
sidebarFocused = true
|
|
||||||
let flags = NSEvent.modifierFlags
|
|
||||||
if flags.contains(.command) {
|
|
||||||
state.selectNote(note.id, extend: true)
|
|
||||||
lastClickedID = note.id
|
|
||||||
} else if flags.contains(.shift), let lastID = lastClickedID {
|
|
||||||
if let lastIndex = state.noteList.firstIndex(where: { $0.id == lastID }) {
|
|
||||||
let range = min(lastIndex, index)...max(lastIndex, index)
|
|
||||||
var ids = Set<UUID>()
|
|
||||||
for i in range {
|
|
||||||
ids.insert(state.noteList[i].id)
|
|
||||||
}
|
|
||||||
state.selectedNoteIDs = ids
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.selectNote(note.id)
|
|
||||||
state.openNote(note.id)
|
|
||||||
lastClickedID = note.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NotePreviewView: View {
|
|
||||||
let note: NoteInfo
|
|
||||||
@ObservedObject var state: AppState
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
Text(note.title)
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(Color(ns: Theme.current.text))
|
|
||||||
Divider()
|
|
||||||
ScrollView {
|
|
||||||
Text(previewText)
|
|
||||||
.font(.body)
|
|
||||||
.foregroundColor(Color(ns: Theme.current.subtext0))
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.frame(width: 320, height: 240)
|
|
||||||
.background(Color(ns: Theme.current.base))
|
|
||||||
}
|
|
||||||
|
|
||||||
private var previewText: String {
|
|
||||||
let bridge = RustBridge.shared
|
|
||||||
if bridge.cacheLoad(note.id) {
|
|
||||||
let text = bridge.getText(note.id)
|
|
||||||
return String(text.prefix(2000))
|
|
||||||
}
|
|
||||||
return "(unable to load preview)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NoteRow: View {
|
|
||||||
let note: NoteInfo
|
|
||||||
let isSelected: Bool
|
|
||||||
var isActive: Bool = false
|
|
||||||
let dateFormatter: DateFormatter
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
|
||||||
Text(note.title)
|
|
||||||
.font(Font(isActive
|
|
||||||
? NSFontManager.shared.convert(Theme.sidebarFont, toHaveTrait: .boldFontMask) as CTFont
|
|
||||||
: Theme.sidebarFont as CTFont))
|
|
||||||
.foregroundColor(Color(ns: Theme.current.text))
|
|
||||||
.lineLimit(1)
|
|
||||||
Text(dateFormatter.string(from: note.lastModified))
|
|
||||||
.font(Font(Theme.sidebarDateFont as CTFont))
|
|
||||||
.foregroundColor(Color(ns: Theme.current.subtext0))
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.background(
|
|
||||||
isActive
|
|
||||||
? Color(ns: Theme.current.surface1)
|
|
||||||
: isSelected
|
|
||||||
? Color(ns: Theme.current.surface0)
|
|
||||||
: Color.clear
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue