diff --git a/App/Views/CLAUDE.md b/App/Views/CLAUDE.md index 5628d06..f3a0338 100644 --- a/App/Views/CLAUDE.md +++ b/App/Views/CLAUDE.md @@ -134,6 +134,21 @@ It reads as cosmetic and is not: the outer capsule's padding was taking the width the segments needed, so in portrait the labels truncated to 「キ… コ…」. Look at the labels, not just the outline, when judging a change here. +**And the words do not always fit even then, so the toggle falls back to +icons.** The toolbar's trailing items have a ceiling on their width. On the +11-inch iPad the portrait canvas column still truncated to 「Ca… Co…」 and +「キ… コ…」 after #119, and the landscape one to "Canv…" once a drawing had +run. Pinning the control at its own width kept the words and sent ⟳ and export +into a "•••" overflow instead; in Japanese the toggle went with them, which is +worse. Taking either `ToolbarSpacer` away changed nothing. `CanvasViewToggle` +therefore offers the words and then the icons, through `ViewThatFits`, each +form `fixedSize` so it can never be squeezed into an ellipsis. What that gives, +measured: the words on the 13-inch landscape (both languages), on the Mac (in +Japanese) and the phone's sheet (in English), and English on the 11-inch +landscape; the icons in portrait on +both iPads and for Japanese on the 11-inch landscape. Judge a change here on +the 11-inch, in portrait and in Japanese: that is the narrowest case. + **26's glass is not used on any surface of ours, and that was tested rather than assumed** (#79). All three candidates were built and looked at, and all three came back out. diff --git a/App/Views/ContentView.swift b/App/Views/ContentView.swift index c68677b..1112d84 100644 --- a/App/Views/ContentView.swift +++ b/App/Views/ContentView.swift @@ -451,18 +451,57 @@ struct CanvasToolbar: ToolbarContent { } } -/// The canvas/code segmented toggle, in `CanvasPane`'s toolbar (#23). +/// The canvas/code segmented toggle, in `CanvasPane`'s toolbar (#23): the +/// words when they fit, the icons when they do not. +/// +/// **The toolbar's trailing items have a ceiling on how wide they may be, and +/// the words do not always fit under it.** Measured on the 11-inch iPad +/// simulator: in the portrait canvas column a segmented control that was +/// allowed to shrink came out 「Ca… Co…」 and 「キ… コ…」, and in the landscape +/// one "Canv…" once a drawing had run — the truncation #119 fixed on one width +/// was still there on others. Pinning it at its own width (`fixedSize`) kept the words and sent +/// the ⟳ and export buttons into a "•••" overflow instead, and in Japanese the +/// toggle itself went with them. Neither the flexible spacer before it nor the +/// fixed one after it was what ran out: taking each away changed nothing. +/// +/// So the words are offered first and the icons second, and `ViewThatFits` +/// takes whichever the toolbar has room for. The toolbar is a little stingier +/// than `fixedSize` would suggest: the 11-inch's landscape column fits the +/// English words but not 「キャンバス」, so Japanese gets the icons there. The +/// 13-inch landscape keeps the words in both languages. VoiceOver reads +/// "Canvas" and "Code" either way. struct CanvasViewToggle: View { @Binding var showsCode: Bool + var body: some View { + ViewThatFits(in: .horizontal) { + CanvasViewPicker(showsCode: $showsCode, iconOnly: false) + CanvasViewPicker(showsCode: $showsCode, iconOnly: true) + } + } +} + +/// One form of `CanvasViewToggle`, at its own width and never narrower: a +/// segmented control made to shrink truncates its words. +private struct CanvasViewPicker: View { + @Binding var showsCode: Bool + let iconOnly: Bool + var body: some View { Picker("View", selection: $showsCode) { - Text("Canvas").tag(false) - Text("Code").tag(true) + if iconOnly { + Label("Canvas", systemImage: "scribble.variable").labelStyle(.iconOnly).tag(false) + Label("Code", systemImage: "chevron.left.forwardslash.chevron.right") + .labelStyle(.iconOnly).tag(true) + } + else { + Text("Canvas").tag(false) + Text("Code").tag(true) + } } .pickerStyle(.segmented) .labelsHidden() - .frame(maxWidth: 220) + .fixedSize() } } diff --git a/Tools/film/README.md b/Tools/film/README.md index d9c9a2e..76ceb6a 100644 --- a/Tools/film/README.md +++ b/Tools/film/README.md @@ -168,10 +168,12 @@ Repeat, and the first run put a Start Fill where the repeat should have been. `palette(_:)` scrolls the palette with a finger until the entry is on screen. Coordinates are used anyway because the log needs the point that was pressed. -**The palette entry is the leftmost button with that label.** "Forward" is -also the transport's step button. It is a separate element with the same name, -and a query that walks its matches one by one lost one between counting and -fetching. +**The palette entry is the leftmost button with that label.** The first run +met a second, disabled button labelled "Forward" as the document opened, and a +query that walked its matches one by one lost one between counting and +fetching. Which button that was was never pinned down: the transport's step +button is "Step Forward", not "Forward", whatever an earlier version of this +note said. **A hardware keyboard is attached for the run.** The scripts switch the Simulator's `ConnectHardwareKeyboard` preference on and restart the device to diff --git a/TortoiseBlocksUITests/FilmTestCase.swift b/TortoiseBlocksUITests/FilmTestCase.swift index 7e01630..e56d1cb 100644 --- a/TortoiseBlocksUITests/FilmTestCase.swift +++ b/TortoiseBlocksUITests/FilmTestCase.swift @@ -115,11 +115,13 @@ /// A palette entry by its title, scrolled on screen first if it is not. /// - /// The title is not unique: the transport's step button is "Forward" - /// too. Of every button with that label this is the one furthest left, - /// since the palette is the first column — read from one snapshot, - /// because walking a query's matches one by one raced the UI and lost an - /// element between counting and fetching it. + /// The title need not be unique: the first run met a second, disabled + /// button labelled "Forward" as the document opened, and which one it + /// was was never pinned down (the transport's is "Step Forward"). Of + /// every button with the label this is the one furthest left, since the + /// palette is the first column — read from one snapshot, because walking + /// a query's matches one by one raced the UI and lost an element between + /// counting and fetching it. /// /// **Taps here are coordinates, so nothing scrolls for them.** An /// element's own `tap()` scrolls it into view; a coordinate below the diff --git a/TortoiseBlocksUITests/MacPreviewTests.swift b/TortoiseBlocksUITests/MacPreviewTests.swift index d81a946..1687b77 100644 --- a/TortoiseBlocksUITests/MacPreviewTests.swift +++ b/TortoiseBlocksUITests/MacPreviewTests.swift @@ -104,7 +104,7 @@ // MARK: - The hands /// The leftmost button with this label: the palette is the first - /// column, and "Forward" is the transport's step button too. + /// column, and a label need not be unique (see `FilmTestCase.palette`). @MainActor private func palette(_ title: String) throws -> CGRect { try find(title) { $0.minX < $1.minX }