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>
This commit is contained in:
@@ -26,8 +26,8 @@ use std::rc::Rc;
|
||||
|
||||
use dr_types::settings::budget;
|
||||
use dr_types::{
|
||||
CollisionPolicy, ColourSpace, ExportFormat, ExportTarget, LibrarySettings, OutputSharpening,
|
||||
ScreenSize, Settings, SizingMode,
|
||||
CollisionPolicy, ColourSpace, ExportFormat, ExportTarget, GroupNavigation, LibrarySettings,
|
||||
OutputSharpening, ScreenSize, Settings, SizingMode,
|
||||
};
|
||||
use slint::ComponentHandle;
|
||||
|
||||
@@ -154,6 +154,36 @@ pub fn render(window: &AppWindow, controller: &SettingsController) {
|
||||
window.set_settings_thumbnail_budget(budget::label(s.cache.thumbnail_budget_bytes).into());
|
||||
window.set_settings_thumbnail_unlimited(s.cache.thumbnail_budget_bytes.is_none());
|
||||
window.set_settings_keep_opened(s.cache.keep_opened_originals);
|
||||
|
||||
// --- develop -------------------------------------------------------
|
||||
//
|
||||
// TRACES: FR-UI-1
|
||||
// Where the adjustment groups are chosen from. The labels are written here
|
||||
// rather than derived from the enum, because "Automatic" is a sentence
|
||||
// about behaviour and the other two name controls the photographer can
|
||||
// see; `GroupNavigation`'s variants are identifiers and would read as
|
||||
// jargon in a picker.
|
||||
window.set_settings_group_nav_labels(slint::ModelRc::new(slint::VecModel::from(vec![
|
||||
slint::SharedString::from("Automatic"),
|
||||
slint::SharedString::from("Tool rail"),
|
||||
slint::SharedString::from("Tab strip"),
|
||||
])));
|
||||
window.set_settings_group_nav_selected(match s.develop.group_navigation {
|
||||
GroupNavigation::Auto => 0,
|
||||
GroupNavigation::Rail => 1,
|
||||
GroupNavigation::Tabs => 2,
|
||||
});
|
||||
// Said plainly rather than left to be discovered by pressing it. The
|
||||
// automatic answer depends on which build this is, which is exactly the
|
||||
// sort of thing a photographer cannot see and should not have to infer.
|
||||
window.set_settings_group_nav_auto_says(
|
||||
if dr_plat::is_touch_first() {
|
||||
"the tool rail"
|
||||
} else {
|
||||
"the tab strip"
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
window.set_settings_cache_usage(controller.usage_label.borrow().clone().into());
|
||||
|
||||
// --- library -------------------------------------------------------
|
||||
@@ -458,6 +488,26 @@ pub fn wire<F, G>(
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = controller.clone();
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = controller.clone();
|
||||
window.on_settings_group_nav_picked(move |i| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let choice = match i {
|
||||
1 => GroupNavigation::Rail,
|
||||
2 => GroupNavigation::Tabs,
|
||||
_ => GroupNavigation::Auto,
|
||||
};
|
||||
ctl.edit(|s| s.develop.group_navigation = choice);
|
||||
// Both, and in this order. `render` refreshes the picker;
|
||||
// the second line is what actually moves the groups from one
|
||||
// control to the other, and without it the preference would
|
||||
// save correctly and change nothing until the next launch.
|
||||
render(&w, &ctl);
|
||||
w.set_groups_in_rail(choice.groups_in_rail(dr_plat::is_touch_first()));
|
||||
});
|
||||
}
|
||||
|
||||
window.on_settings_format_picked(move |i| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(f) = ExportFormat::ALL.get(i as usize).copied() {
|
||||
|
||||
Reference in New Issue
Block a user