Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions App/Browser/BrowserChrome.swift
Original file line number Diff line number Diff line change
Expand Up @@ -346,22 +346,31 @@ struct ToolbarSearchField: View {
closeSearch()
return .handled
}
// Clear belongs to the text: the system field's filled circle, only while there's
// something to clear, and Search stays open with the keyboard.
if !query.isEmpty {
Button("Clear Search", systemImage: "xmark.circle.fill") {
query = ""
takeKeyboard()
}
.labelStyle(.iconOnly)
.buttonStyle(.plain)
.foregroundStyle(.secondary)
.buttonStyle(SearchFieldButtonStyle())
.help("Clear Search")
}
// Close belongs to the capsule: past a hairline, at its trailing end in every
// state, so it never moves and never reads as a second Clear (#294).
Rectangle()
.fill(.separator)
.frame(width: 1, height: 16)
.padding(.horizontal, 2)
.accessibilityHidden(true)
Button("Close Search", systemImage: "xmark", action: closeSearch)
.labelStyle(.iconOnly)
.buttonStyle(.plain)
.buttonStyle(SearchFieldButtonStyle())
.fontWeight(.medium)
.help("Close Search")
}
.padding(.horizontal, Space.m)
// The trailing button's own hit area is the inset there.
.padding(.leading, Space.m)
.padding(.trailing, Space.xs + 2)
// Only the background takes this gesture: a close-button click must not refocus.
.background {
Color.clear.contentShape(.capsule).onTapGesture { takeKeyboard() }
Expand Down Expand Up @@ -394,6 +403,29 @@ struct ToolbarSearchField: View {
}
}

/// Search's Clear and Close: icon-only, as quiet as the field's magnifying glass until the pointer
/// is over one, with a hit area larger than the glyph. No hover animation, as the system field's.
private struct SearchFieldButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
HoverLabel(configuration: configuration)
}

private struct HoverLabel: View {
let configuration: Configuration
@State private var isHovered = false

var body: some View {
configuration.label
.labelStyle(.iconOnly)
.foregroundStyle(isHovered ? AnyShapeStyle(.primary) : AnyShapeStyle(.secondary))
.opacity(configuration.isPressed ? 0.55 : 1)
.frame(width: 18, height: 22)
.contentShape(.rect)
.onHover { isHovered = $0 }
}
}
}

/// Places `SearchFieldFocus`'s anchor beside the toolbar item's text field.
private struct FieldAnchor: NSViewRepresentable {
let focus = SearchFieldFocus()
Expand Down
9 changes: 8 additions & 1 deletion App/DemoScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SwiftUI
/// Stow.app/Contents/MacOS/Stow -StowScene grid -StowSnapshot YES
///
/// Scenes: onboarding, welcome (no history), recents (the welcome window with history),
/// grid, list, fonts, search, search-nested (results inside an inner archive; search-nested-many selects two), activity, pack, tray, open (`-StowOpen <path>`),
/// grid, list, fonts, search, search-open (Search opened by ⌘F, nothing typed yet), search-nested (results inside an inner archive; search-nested-many selects two), activity, pack, tray, open (`-StowOpen <path>`),
/// packing / packed (the pack capsule), packrun (a real pack, see `runPack`).
/// `pack` with `-StowPack <path>` shows the sentence for real files; `-PackAudience mac` and the other
/// `PackPreferences` keys work as launch arguments too; `-StowPackPassword YES` starts with “with a password”;
Expand Down Expand Up @@ -193,6 +193,13 @@ enum DemoScene {
case "search":
app.openArchive(named: SampleArchive.make().name)
app.browser?.query = "harbor"
case "search-open":
app.openArchive(named: SampleArchive.make().name)
// ⌘F once the Browser is on screen: its toolbar answers the request, not the model.
Task {
try? await Task.sleep(for: .milliseconds(600))
app.browser?.focusSearch()
}
case "activity":
app.openDemo(SampleArchive.prototypeBuild())
app.browser?.selection = ["Harbor Prototype.app"]
Expand Down
8 changes: 7 additions & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@ plain path-row and bottom-path studies.
overshoot, stepped frame by frame because the toolbar doesn't follow SwiftUI animations; ⌘F, Esc
and Reduce Motion change it at once (`SearchWidthMotion`). It's the Browser's own field (the
system toolbar field always settles last, at the window's end) with the system field's
contracts: one query, Esc clears it and returns the keyboard to the files in one step. CJK input
contracts: one query, Esc clears it and returns the keyboard to the files in one step. Open, it
carries two controls that never share a glyph (#294): **Clear** is the system field's filled
circle inside the text, shown only with a query, and keeps Search open with the keyboard;
**Close** (a plain ×, medium weight) sits past a hairline at the capsule's trailing end in every
state, so it never moves, and does what Esc does: clears, closes and hands the keyboard back.
Both are as quiet as the magnifying glass until the pointer is over them (no hover animation),
with an 18 × 22 pt hit area. A pointer Close shrinks the field like any close (0.18 s); CJK input
composes in it without filtering on half-typed pinyin, and while an input method is composing,
Esc is the input method's (it cancels the composition; Search stays open). `TitleBarLayout` gives it room
from free space first, then from the name (which truncates in the middle), never from the path.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ running step finishes and is unavailable when another window or a sheet is activ
scripts/snapshot.sh <scene> [light|dark] # → snapshots/
```

Scenes: `editing`, `readonly`, `welcome`, `grid`, `list`, `fonts`, `search`, `activity`, `pack`, `draft-empty`, `draft`, `draft-save` (the new-archive draft and its Save sheet), `tray`, `quicklook`
Scenes: `editing`, `readonly`, `welcome`, `grid`, `list`, `fonts`, `search`, `search-open`, `activity`, `pack`, `draft-empty`, `draft`, `draft-save` (the new-archive draft and its Save sheet), `tray`, `quicklook`
(`-StowPreview encrypted|slow`), `associations`, `integration` (`-StowRepair YES`), `batch`,
`conflict`, `conflict-folder`, `signature-warning`, `replace-app`, `extract-options`, `install-options`,
`packing`, `packed`, `packrun`, `checkup`, `install-preparing`, `install-done`, `open-unreadable`, Details and archive notices (`details-clean`, `details-planned`,
Expand Down
Loading