a36ec98b369743472999a8234bafb6a399303ba5
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
36556729f1 |
Let Back close the presets menu instead of the application
The presets menu at the foot of the tool rail is a PopupWindow, and showing a popup takes focus off the develop view until it closes. The menu held nothing focusable, so Android's Back gesture, pressed to dismiss it, found no focus item, went unanswered, and the platform closed the application. Slint closes a popup on Escape by itself but not on Back. The menu now holds a key scope, as the film list does, that closes it on Back or Escape. The next Back leaves develop for the grid. |
||
|
|
31bcc3a462 |
File presets in folders that open and close, as collections do
The presets menu and sheet listed every preset under flat section headings, seventy rows to scroll past. They now list folders, closed until opened, with how many presets each holds; opening one shows what is inside it, folders and presets indented beneath. A category is spelled in the name: "Portraits/Warm skin" is Warm skin in a Portraits folder under Yours. The file format does not change, so an older build lists the whole path as the name; renaming a preset is how it moves, and saving or renaming into a folder opens the way to it. A Lightroom import names what it reads after the folders below the one chosen, and a "/" in a displayed name becomes "∕" so it files nothing. The shipped film sections become Film › Colour, Cinema and Black and white. The tree is built and flattened in Rust (PresetTree), each row carrying its depth, and which folders are open is remembered for the session. A PopupWindow keeps the size it was shown at, so a folder opened in the menu pushed its contents under "Save or manage…"; the menu is shown again after each toggle to take its new height. That is a function on the rail because Slint 1.17 generates Rust that does not compile for a popup's close() reached from inside the popup. The sheet's list takes a preferred height of up to 400px, since a Flickable reports next to nothing and an opened folder showed three rows. The manual describes the folders and naming. Its pictures show the menu with Film › Colour open, and a black-and-white stock applied from Film › Black and white; the scenes aim popup rows from the rail's entry, and pick the menu's "Film" over the develop column's film chooser. |
||
|
|
f52c4cb8b6 |
Open presets from a menu at the foot of the tool rail
Presets were a "Presets…" button in develop's top bar that opened a sheet over the photograph, so applying one was two clicks with the picture covered. They are now an entry pinned to the bottom of the tool rail, apart from the tools because a preset arms nothing, and it opens a menu beside the rail: the same sectioned rows the sheet lists, one click to apply to the open photograph. The menu's last row, "Save or manage…", opens the sheet, which keeps saving, renaming, reverting and importing, since those take a name or a path. The menu is a PopupWindow for the film list's reasons, with the list in a clipped box of its own; the Flickable alone let its last row draw over the manage row. Choosing from it zeroes preset-apply-count before applying, since the grid may have left its selection count there. The manual says where presets are now and gains a picture of the open menu. The scenes reach the sheet through the menu and aim the manage row from the rail's entry, as the film list's rows are aimed, because the automation reports a popup's elements in the popup's coordinates. |
||
|
|
84fade99ec |
Put the developer docs under docs/dev and index the folder for users first
docs/ had 26 developer documents flat beside the manual, and the two audiences are very differently sized: most readers want the manual and the gesture reference, a few want the register, the designs and the measurements. The manual and gestures.md stay at the top; everything for someone changing the code moves to docs/dev/, and the two documents that name their own successors — the v0.1 milestone and the UI-refinement plan — go to docs/dev/archive/ rather than being deleted, since both are still cited. docs/README.md is the index, users first. Every reference follows: code comments, Cargo manifests, the workflows, the pre-commit hook, the bench and traceability tools (which locate the repo root by docs/dev/requirements.md now), packaging, the Docker READMEs, CLAUDE.md, CONTRIBUTING.md and the README. The matrix links one level deeper and is regenerated. Links out of the moved documents into the tree gain a level; a link checker over every Markdown file finds none broken. |
||
|
|
0a5eab0487 |
Wire the develop panels through globals, so a second copy is one line
Every panel in the develop column declared its inputs and its callbacks and had `app.slint` bind each one to a property or a callback on the window root. That is fine while a panel is drawn once. N9 draws them a second time, in the portrait dock, and the wiring is what would have to be copied: `MaskPanel` alone ran to forty lines of forwarding, and a callback added to one copy and not the other compiles, renders, and simply does nothing on the layout nobody was looking at. So the wiring moved to Slint globals. A panel reads the global and calls the global; Rust hooks the global instead of the window; and the instantiation in the column is now the panel's name and a pair of braces — every one of the ten children of the column, with no property that differs by placement left to supply. There is a global per panel family rather than one for all of them, and the reason is an import cycle. Each panel's model struct — `ParamRow`, `MaskRow`, `HistogramView` — is declared in the panel's own file, so a single global holding `[MaskRow]` and `[ParamRow]` would have to live in a file importing `masks.slint` and `adjust.slint` while both imported the global back, which Slint rejects. Breaking that needs six model declarations relocated, which is a change to the data model and not to the plumbing this is about. A global beside the panel it serves also lets each name drop the prefix it was carrying only because the window root is one flat namespace: `root.spot-radius` is `Repair.radius`, and `root.peaking-on` is `Peaking.showing`. `session.slint` is new and holds the two facts every family needs and none of them owns: whether there is an open photograph to edit, and which mode the view is in, with the three readings of the mode derived once instead of at each of the dozen places that tested one. `ViewMode` moves there from `adjust.slint`, where it was only ever a lodger. Nothing on screen changes. What is not here: the tool rail and the status strip still take their properties at the instantiation, because they are drawn once and N9 does not copy them; the preset sheet's own state stays on the window, because the library grid opens the same sheet and a global cannot bind the window's state — which is why `Transfer.open-presets` is handled in `presets.rs`, beside the summary it already had to compute. |
||
|
|
2cd49d1cb7 |
Measure the rail by counting it, so it can scroll instead of clipping
With the adjustment groups in the rail, a short window silently lost them. At a 1500x680 window the rail drew Photo, Compose, Local, Repair, the seam and "All", and then stopped: Optics, Light, Colour, Effects and Detail were not scrolled off, they were gone, with nothing on screen to say so. The develop view's primary navigation, unreachable by any means. The Flickable was put here to prevent exactly that, and it could not, because its viewport asked the layout how tall it wanted to be while the layout was already taking its height from the viewport. Slint settles that cycle by handing back the height it was given, so `max(self.height, preferred-height)` could never exceed `self.height` and there was never anything to scroll. Counting breaks the cycle. Every entry here is a fixed height by construction — a tool is `rail-entry-height`, a group is a touch target — so the content is six plus four fifty-fours plus a gap plus a touch target for each group and "All", which is exact rather than an estimate and depends on nothing that depends on it. Four tools and five groups come to 498, against the 340 a 680-pixel window leaves at 2x, and the difference is now scrollable rather than absent. Found by shrinking the window with the groups forced into the rail. The interaction itself is unverified: synthetic input does not reach a Slint window on this desktop, and the tablet was disconnected, so what is confirmed is the arithmetic and the clipping it explains, not the scrolling it should restore. |
||
|
|
59917c5183 |
Call the tool Compose, since that is what its panel says
Benchmarks / CPU and I/O (per commit) (push) Successful in 14m35s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Failing after 1h20m20s
Build and test / Layer separation (push) Successful in 51s
Traceability / Requirement traces (push) Successful in 2m8s
🐳 Android image / Build and push (push) Successful in 5s
Build and test / android-image (push) Successful in 5s
Build and test / Android (aarch64) (push) Successful in 1h5m51s
The rail entry read "Crop" while the panel it opens is headed COMPOSE and the button leaving it said "Done Cropping". One mode, three names, and the odd one out was named after a single control rather than after the decision — which is what made cropping look like a category of its own in the first place. Straightening, the quarter turns and the flips are already in that panel, and perspective will be. `ViewMode.crop` keeps its name: it identifies a canvas interaction, which is exactly what it still is. Found by looking at the running application rather than by reading, which is also how the two halves of this were noticed to disagree at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6a97fdf6f9 |
Put the adjustment groups in the rail where a finger is driving
Reported from the tablet: the tool rail is very useful there, and the same interface under a mouse and keyboard is not. That is `ui-navigation.md` D-N2's central assumption failing in use, and the interesting part is which half of it failed. D-N2 was right that platform is the wrong axis and width is the wrong axis: a tablet in landscape wants what a desktop wants, and a desktop window dragged narrow wants what a small screen wants. `apply_layout_class` still decides the layout class from the window and nothing here changes that. What D-N2 got wrong is the sentence "touch changes hit regions, not layout" — it identified input as the real difference between the targets and then assumed that difference could never reach the layout. Two controls answer one question — which group of adjustments am I looking at — and neither is better in general. A horizontal strip above the column is one gesture to a target the eye has already found, and it pans when the operation set is rich, so a group can sit off the end with nothing saying so: a pointer user tolerates that, a finger user never discovers it. The same list down the rail is every entry visible at once, each finger-sized, on the edge of the screen the hand is already holding, and it costs no width because the rail is already there. So `ToolRail` grows a second section, and `GroupStrip` stands down when it does. The two are never both on screen, which is why they can share `adjust-tab-picked`: Rust is not told which was pressed and has no reason to want to. Mode and group stay independent axes as N1 requires — one entry lit in each section, and choosing a group while a tool is held still filters without putting the tool down. They stay drawn differently, which N1 also required. The tools fill with `active-dim` and invert their ink; the groups take a bar down the leading edge — the strip's underline turned ninety degrees — so a lit entry says which kind of state it is without the reader having to remember which section it was in. The rule between the sections is the second signal. The rail scrolls now. Its own note argued against a Flickable because "this list is four entries written in this file"; with the groups in it the list comes from the operation set, which is exactly the "something the user's data decides" that note excluded this control from. The axis is input, and it is a preference because the automatic answer is a guess that cannot be made reliable. Neither platform can be asked what the user is holding: an Android tablet in a keyboard case is being driven like a desktop, and a touchscreen laptop is whichever its owner says. `dr_plat::is_touch_first` reports the usual case per platform, and `GroupNavigation` lets it be overridden. Settings names what Automatic resolves to on this device rather than leaving it to be found by pressing. D-N6 records the reversal beside the decision it reverses, including the half that still stands and the question it opens: whether Local is a mode at all, or a scope that would collapse the two sections into one list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
46706fe622 |
Let the tool rail be pressed, not only read
The rail is the develop view's primary navigation and reached the platform as four pieces of static text. The labels got through — a `Text` announces itself — so a screen reader could read "Photo, Crop, Local, Repair" and had no way to learn that any of them could be pressed, or which one was currently held. The one control that decides what a click on the photograph does was a caption. Each entry now declares itself a checkable button carrying the tool's own word, with the held state reported rather than left to the fill. Checkable is unconditional here, unlike `Button`'s: a rail entry is always a held-or-not state, so an unheld one should say "not pressed" rather than pass for an ordinary button. The action repeats the click handler's expression rather than calling it. A `TouchArea`'s `clicked` is raised by the pointer and cannot be raised from a binding, so the alternative is a function wrapping two lines — and the duplicated ternary sits four lines below the original where the two cannot drift out of sight of each other. Worth recording for the next control like this: `accessible-*` on an element inside a `for` does work, despite the accessibility pass skipping repeated elements. `process_repeater_components` runs first and moves the bindings into a real component whose root is not repeated; what the later pass skips is the empty placeholder left behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ef07e6ca3e |
Give the canvas tools a rail of their own, and the column one width
Build and test / Desktop (Linux) (push) Failing after 1h14m38s
Build and test / Layer separation (push) Successful in 48s
🐳 Android image / Build and push (push) Successful in 16m30s
Build and test / android-image (push) Successful in 16m31s
Traceability / Requirement traces (push) Successful in 1m47s
Build and test / Android (aarch64) (push) Successful in 1h0m21s
Crop, Local and Repair were chips at the head of the develop column, sharing a row with the adjustment groups and told apart from them by the shape of their highlight. Three things followed from that, and only the last is cosmetic: the column closes, so the way out of a mode went away with the way in — hence the duplicate "Done Cropping" over the canvas; the chips are generated from the operation set, so the widest thing in the sidebar was a row nobody had chosen the contents of; and a mode and a filter are different kinds of state wearing one control. They are a fixed 60px rail down the left now, generated from a single table in toolrail.slint. A tool is one row of it plus a drawing plus a ViewMode variant; nothing in app.slint is touched to add one. What is left of the strip is the group filters, so it is GroupStrip. The column stops measuring itself. Every panel published a content-width and declared it as min-width, and the column took the largest — which spent the photograph's pixels on whatever happened to be widest, and moved the image sideways when switching tools swapped one set of panels for another. It is panel-width now, one number in style.yaml. That number is 360 and it is measured, not picked: the contents report a minimum of 344 in every mode, and they do not compress below it because a Text that does not elide reports the same minimum as preferred. 320 was tried and sliced Paste down the middle. The Flickable's viewport is floored at the layout's minimum rather than its preferred width for the same reason — content that is never told how much room it has cannot adapt to having less. Removing the eight content-width declarations repairs three comments an earlier edit had spliced sentences into. The raw histogram's note on keeping its hint short is rewritten rather than dropped: an over-long hint no longer widens the column, it pushes the column's minimum past the width it has and clips the panel, which makes that constraint sharper rather than obsolete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |