From 46f87ccebd83cc58b0478e6f93fb1c9eabc2f74f Mon Sep 17 00:00:00 2001 From: Tomoki Kobayashi Date: Sun, 27 Sep 2026 08:35:56 +0900 Subject: [PATCH] Keep the canvas toggle's words whole, or show icons instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the 11-inch iPad the canvas/code toggle still truncated after #119: - portrait: "Ca… Co…" and 「キ… コ…」 in the canvas column; - landscape: "Canv…" once a drawing had run. The toolbar caps how wide its trailing items may be. A segmented control allowed to shrink truncates. Pinning it at its own width instead pushed ⟳ and export into a "•••" overflow, and in Japanese took the toggle with them. Removing either toolbar spacer made no difference. The toggle now offers the words, then icons (a scribble and ), through ViewThatFits. Each form is fixedSize, so neither can be squeezed into an ellipsis. Measured on the simulators and this Mac: - words on the 13-inch landscape in both languages, on the Mac and in the phone's sheet, and in English on the 11-inch landscape; - icons in portrait on both iPads, and for Japanese on the 11-inch landscape. VoiceOver still reads "Canvas" and "Code". Also corrects a claim the film tools made: the transport's step button is "Step Forward", not "Forward". The second "Forward" the first teaser run met was never identified, and the comments and the README now say so. --- App/Views/CLAUDE.md | 15 +++++++ App/Views/ContentView.swift | 47 +++++++++++++++++++-- Tools/film/README.md | 10 +++-- TortoiseBlocksUITests/FilmTestCase.swift | 12 +++--- TortoiseBlocksUITests/MacPreviewTests.swift | 2 +- 5 files changed, 72 insertions(+), 14 deletions(-) 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 }