Replace the six view booleans with View and Page enums
app.slint carried show-launch, show-library, show-identity, show-settings,
show-import and show-merge as separate booleans, so the root component chose
what to draw with five- and six-term conjunctions and nothing stopped two of
them being true at once. Replaced with two enums: View { develop, library,
identity, launch } for which top-level screen is showing, and Page { none,
settings, import, merge } for which page, if any, is drawn over it.
Two values rather than one, because the two questions are genuinely
different. Settings, Import and Merge are reachable from more than one View
and are drawn outermost without touching it — closing one has to return to
whichever View was already current, and today that works because the
underlying property is left alone while the page sits over it. A single
View with five or more variants would need a second field remembering what
to return to; Page needs nothing to remember, since View was never
overwritten in the first place. Identity, by contrast, genuinely replaces
the window the way Launch and Library do (see the existing "like the launch
screen" comment on its `if`), so it is a View variant, not a Page.
Every `if` chain in app.slint that used to compare four, five or six
booleans now compares active-view and active-page to at most one variant
each. library-visible collapsed from a six-term conjunction to
`active-page == Page.none && active-view == View.library`.
The Rust side follows: every set_show_*/get_show_* call in library_ui.rs,
identity_ui.rs, settings_ui.rs, merge_ui.rs, import_ui.rs, launch_ui.rs and
lib.rs now reads or writes active-view or active-page instead, including
lib.rs's startup match (View.launch vs View.develop, since a Startup that
skips the launch screen used to leave both old booleans false and fall
through the chain to develop) and identity_ui's close handler, which now
writes View.library or View.develop in one call where it used to write
show-library then show-identity separately.
back_one_step needed one deliberate adjustment beyond the mechanical
rename. Identity was never represented in NavState: back had nothing to do
when Identity was opened from the library (show-library stayed true,
unread by IdentityScreen's own condition) and could only reach ToLibrary
when opened from develop, which likewise wrote a property IdentityScreen
never read — so escaping out of Identity was invisible in both cases before
this change. With a single active-view, falling into the general case
would instead overwrite the value IdentityScreen's `if` does read and close
it as an unintended side effect. back_one_step now swallows the gesture
while View.identity is current, reproducing the same "nothing visible
happens" outcome for both origins without threading identity_ui's private
came-from-library state through lib.rs for one screen.
Verified with tools/manual/drive.py against a private Xvfb and the debug
build: launch screen to library, Settings opened and closed, Identity
opened and closed (including Escape doing nothing while it is open),
develop opened from a cell and closed both by the back button and by
Escape. Screenshots under verify/.
This commit is contained in:
+43
-43
File diff suppressed because one or more lines are too long
+11
-11
@@ -25,7 +25,7 @@ Sampling a neutral is the first move of the tonal pass — every colour judgemen
|
||||
|
||||
Anchored on the fingers' midpoint, and on the pointer, so the gesture reads as magnifying the picture rather than sliding it about. Double-tap is the way to an exact 1:1; this is the way to everything in between.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2070`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2109`</sub>
|
||||
|
||||
### Move a magnified photograph about
|
||||
|
||||
@@ -34,7 +34,7 @@ Anchored on the fingers' midpoint, and on the pointer, so the gesture reads as m
|
||||
|
||||
Only once there is something outside the viewport to reach, which is why the cursor becomes a hand exactly then. The view is clamped to the frame: panning past the edge would show undefined area beside the photograph, and that reads as a rendering fault rather than as the end of the picture.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2161`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2200`</sub>
|
||||
|
||||
### Paint a mask by hand
|
||||
|
||||
@@ -43,7 +43,7 @@ Only once there is something outside the viewport to reach, which is why the cur
|
||||
|
||||
A model's mask stops inside a shoulder and leaks into the hair, and no single edge control fixes two errors that go opposite ways. The whole stroke is one step in the history, so taking a mark back costs one press however long it took to make.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2248`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2287`</sub>
|
||||
|
||||
### Take back the last change
|
||||
|
||||
@@ -53,7 +53,7 @@ A model's mask stops inside a shoulder and leaks into the hair, and no single ed
|
||||
|
||||
A whole drag is one step, so undo takes back a decision rather than a frame of a gesture. The list is there because arriving six steps back costs what arriving from one does.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2469`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2508`</sub>
|
||||
|
||||
### Do it again after taking it back
|
||||
|
||||
@@ -61,7 +61,7 @@ A whole drag is one step, so undo takes back a decision rather than a frame of a
|
||||
- **Pointer** — Click it, or press Redo in the History header
|
||||
- **Keyboard** — Ctrl+Shift+Z
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2482`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2521`</sub>
|
||||
|
||||
### Copy the settings from this photograph
|
||||
|
||||
@@ -71,7 +71,7 @@ A whole drag is one step, so undo takes back a decision rather than a frame of a
|
||||
|
||||
The panel is the copy that has to work: a tablet has no modifier key to hold and no menu bar to hang the action from. The shortcut is an accelerator for a control that is on screen either way.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2515`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2554`</sub>
|
||||
|
||||
### Paste the settings onto this photograph
|
||||
|
||||
@@ -81,7 +81,7 @@ The panel is the copy that has to work: a tablet has no modifier key to hold and
|
||||
|
||||
The button names what would be pasted — "3 adjustments", and whether the crop is coming with it — which the shortcut cannot say. Both paste the same scope.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2527`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2566`</sub>
|
||||
|
||||
### Change which group of adjustments is on screen
|
||||
|
||||
@@ -91,7 +91,7 @@ The button names what would be pasted — "3 adjustments", and whether the crop
|
||||
|
||||
The groups are whatever the operation set declares itself to be about, so there are as many as the pipeline has and no key can be assigned to one of them by name. Stepping is the binding that survives a node being added.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2555`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2594`</sub>
|
||||
|
||||
### Look at the photograph at 1:1
|
||||
|
||||
@@ -101,7 +101,7 @@ The groups are whatever the operation set declares itself to be about, so there
|
||||
|
||||
Noise reduction and capture sharpening are judgements about single pixels, and a fitted view averages several of the file's into each one on screen — so the frame looks softer than it is and the correction goes too far. The point and the magnification survive opening the next photograph, which is what makes checking the same eye across forty portraits forty keystrokes rather than forty pans.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2590`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2629`</sub>
|
||||
|
||||
### Move to the next or previous photograph
|
||||
|
||||
@@ -111,7 +111,7 @@ Noise reduction and capture sharpening are judgements about single pixels, and a
|
||||
|
||||
The edit on screen is saved on the way out, so stepping through a folder is as much a departure as going back to the grid and loses nothing.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2642`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2681`</sub>
|
||||
|
||||
### See the photograph before you edited it
|
||||
|
||||
@@ -121,7 +121,7 @@ The edit on screen is saved on the way out, so stepping through a folder is as m
|
||||
|
||||
Held rather than toggled, and no split screen: a split halves the working image on the tablet the column was sized for, and the comparison photographers describe making is a flick back and forth. It takes no history step, so checking whether a frame is overcooked costs nothing to undo afterwards.
|
||||
|
||||
<sub>`ui/dr-ui/ui/app.slint:2766`</sub>
|
||||
<sub>`ui/dr-ui/ui/app.slint:2805`</sub>
|
||||
|
||||
### Put one control back to its default
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use slint::{ComponentHandle, Model as _, ModelRc, VecModel};
|
||||
|
||||
use crate::faces::FaceSweepMessage;
|
||||
use crate::identity::{self, FaceCell, PersonRow};
|
||||
use crate::{AppWindow, IdentityFace, IdentityPerson};
|
||||
use crate::{AppWindow, IdentityFace, IdentityPerson, View};
|
||||
|
||||
/// Which model's faces the screen is looking at.
|
||||
///
|
||||
@@ -740,14 +740,14 @@ fn wire_navigation(
|
||||
let models_present = models.clone();
|
||||
window.on_identity_open(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let from_library = w.get_show_library();
|
||||
let from_library = w.get_active_view() == View::Library;
|
||||
ctl.came_from_library.set(from_library);
|
||||
w.set_identity_back_label(if from_library {
|
||||
"‹ Library".into()
|
||||
} else {
|
||||
"‹ Develop".into()
|
||||
});
|
||||
w.set_show_identity(true);
|
||||
w.set_active_view(View::Identity);
|
||||
// A fact about the filesystem, so it is re-checked on every open
|
||||
// rather than cached: the user may have just put the models there.
|
||||
w.set_identity_model_missing(models_present().is_none());
|
||||
@@ -768,8 +768,11 @@ fn wire_navigation(
|
||||
// Back to whichever screen this was opened from. The develop
|
||||
// session was never torn down — it was only hidden — so returning
|
||||
// to it costs nothing and keeps the photographer's place.
|
||||
w.set_show_library(ctl.came_from_library.get());
|
||||
w.set_show_identity(false);
|
||||
w.set_active_view(if ctl.came_from_library.get() {
|
||||
View::Library
|
||||
} else {
|
||||
View::Develop
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ use dr_types::{FormatFilter, ImportSettings};
|
||||
|
||||
use crate::activity::{Activity, ActivityLog, Kind};
|
||||
use crate::import::{self, Message, Request, Upload};
|
||||
use crate::AppWindow;
|
||||
use crate::{AppWindow, Page};
|
||||
|
||||
/// How often the drain runs while an import is going.
|
||||
///
|
||||
@@ -344,7 +344,7 @@ where
|
||||
*ctl.error.borrow_mut() = String::new();
|
||||
survey(&w, &ctl, &context);
|
||||
render(&w, &ctl);
|
||||
w.set_show_import(true);
|
||||
w.set_active_page(Page::Import);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ where
|
||||
if ctl.running.get() {
|
||||
return;
|
||||
}
|
||||
w.set_show_import(false);
|
||||
w.set_active_page(Page::None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use dr_sync_nextcloud::{auth, NextcloudProvider};
|
||||
use slint::ComponentHandle;
|
||||
|
||||
use crate::launch::{LaunchModel, LaunchState};
|
||||
use crate::AppWindow;
|
||||
use crate::{AppWindow, View};
|
||||
|
||||
/// Shared launch state for the running window.
|
||||
pub struct LaunchController {
|
||||
@@ -257,7 +257,7 @@ fn wire_choose_folder_and_open<F>(
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let session = ctl.model.borrow().session().cloned();
|
||||
if let Some(s) = session {
|
||||
w.set_show_launch(false);
|
||||
w.set_active_view(View::Library);
|
||||
on_open_library(s);
|
||||
}
|
||||
});
|
||||
|
||||
+26
-4
@@ -1618,7 +1618,13 @@ fn construct_screens(
|
||||
{
|
||||
let controller = launch_ui::LaunchController::new();
|
||||
let startup = controller.model.borrow().startup_action(!paths.is_empty());
|
||||
window.set_show_launch(startup == launch::Startup::ShowLaunchScreen);
|
||||
// `OpenLibrary` is overwritten moments later by `library_ui::open`
|
||||
// below, once a stored session exists; `ShowLocalFiles` leaves
|
||||
// `develop` as it found it, since there is no library to switch to.
|
||||
window.set_active_view(match startup {
|
||||
launch::Startup::ShowLaunchScreen => View::Launch,
|
||||
launch::Startup::ShowLocalFiles | launch::Startup::OpenLibrary => View::Develop,
|
||||
});
|
||||
|
||||
let library = library.clone();
|
||||
let collections = collections.clone();
|
||||
@@ -3404,11 +3410,27 @@ fn apply_layout_class(
|
||||
/// the activity — the behaviour every application there has, and the reason
|
||||
/// this answers with a bool rather than swallowing the gesture.
|
||||
fn back_one_step(w: &AppWindow) -> bool {
|
||||
// The Identity screen has no representation in `NavState`: nothing here
|
||||
// records which view it replaced, only `identity_ui`'s own
|
||||
// `came_from_library`, which this function has no reason to reach for
|
||||
// over one screen. Before this refactor, back queued nothing behind
|
||||
// Identity opened from the library (show-library stayed true, unread by
|
||||
// anything) and queued `ToLibrary` behind Identity opened from develop —
|
||||
// but `ToLibrary` only ever set `show-library`, a property
|
||||
// `IdentityScreen`'s own `if` never read, so both cases looked the same
|
||||
// on screen: nothing happened. Swallowing the gesture here keeps that:
|
||||
// falling into the general case below would instead write `active-view`,
|
||||
// which `IdentityScreen`'s `if` *does* read, and close the screen as a
|
||||
// side effect nobody asked for.
|
||||
if w.get_active_view() == View::Identity {
|
||||
return true;
|
||||
}
|
||||
|
||||
let state = NavState {
|
||||
settings: w.get_show_settings(),
|
||||
launch: w.get_show_launch(),
|
||||
settings: w.get_active_page() == Page::Settings,
|
||||
launch: w.get_active_view() == View::Launch,
|
||||
browsing: w.get_launch_browsing(),
|
||||
library: w.get_show_library(),
|
||||
library: w.get_active_view() == View::Library,
|
||||
mode: w.global::<Develop>().get_view_mode(),
|
||||
zoomed: w.get_zoomed(),
|
||||
// Files named on the command line have no grid behind them — the same
|
||||
|
||||
+14
-14
@@ -22,7 +22,7 @@ use dr_types::FormatFilter;
|
||||
use slint::{ComponentHandle, Model as _};
|
||||
|
||||
use crate::library::{self, ScanMessage, ThumbnailMessage};
|
||||
use crate::{AppWindow, GestureRow, KeywordRow, LibraryCell, PersonChip, TimelineBar};
|
||||
use crate::{AppWindow, GestureRow, KeywordRow, LibraryCell, PersonChip, TimelineBar, View};
|
||||
|
||||
/// A screenful before the grid has reported its geometry.
|
||||
///
|
||||
@@ -957,7 +957,7 @@ pub fn open(
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
window.set_library_error(format!("credentials: {e}").into());
|
||||
window.set_show_library(true);
|
||||
window.set_active_view(View::Library);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -977,7 +977,7 @@ pub fn open(
|
||||
// another device may still be applied — see `place_untouched`.
|
||||
ctl.place_untouched.set(true);
|
||||
|
||||
window.set_show_library(true);
|
||||
window.set_active_view(View::Library);
|
||||
window.set_library_open(true);
|
||||
window.set_library_scanning(true);
|
||||
window.set_library_error(slint::SharedString::new());
|
||||
@@ -5391,7 +5391,7 @@ fn write_place(window: &AppWindow, ctl: &Rc<LibraryController>) {
|
||||
fn current_place(window: &AppWindow, ctl: &Rc<LibraryController>) -> Option<dr_types::Place> {
|
||||
use dr_types::{Place, PlaceScope, Screen};
|
||||
|
||||
if window.get_show_launch() || !window.get_library_open() {
|
||||
if window.get_active_view() == View::Launch || !window.get_library_open() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -5401,7 +5401,7 @@ fn current_place(window: &AppWindow, ctl: &Rc<LibraryController>) -> Option<dr_t
|
||||
// tracks; in develop it is the open one, which `report_position` keeps
|
||||
// `index` holding. Both are library ordinals against the same ordering, so
|
||||
// the lookup below is one piece of code rather than two.
|
||||
let in_library = window.get_show_library();
|
||||
let in_library = window.get_active_view() == View::Library;
|
||||
let at = if in_library {
|
||||
ctl.resume_at.get()
|
||||
} else {
|
||||
@@ -5544,7 +5544,7 @@ pub(crate) fn apply_place(
|
||||
if place.screen == Screen::Develop {
|
||||
if let Some((at, Found::Photograph)) = resolved {
|
||||
if let Some(open) = ctl.open_image.borrow().clone() {
|
||||
window.set_show_library(false);
|
||||
window.set_active_view(View::Develop);
|
||||
window.set_library_roll_centre(true);
|
||||
let offset = *ctl.offset.borrow();
|
||||
if let Some(row) = at.checked_sub(offset) {
|
||||
@@ -5836,7 +5836,7 @@ pub fn wire<F>(
|
||||
if let Some(path) = path {
|
||||
// Leave the grid for the develop view. The status bar's
|
||||
// "‹ Library" button comes back here.
|
||||
w.set_show_library(false);
|
||||
w.set_active_view(View::Develop);
|
||||
// Which cell the develop view is now showing, so the photo
|
||||
// roll opens marking it rather than marking nothing.
|
||||
w.set_library_roll_current(i);
|
||||
@@ -5940,7 +5940,7 @@ fn wire_grid_cursor_and_zoom(
|
||||
let row = cursor.checked_sub(offset);
|
||||
let path = row.and_then(|row| ctl.paths.borrow().get(row).cloned());
|
||||
if let Some(path) = path {
|
||||
w.set_show_library(false);
|
||||
w.set_active_view(View::Develop);
|
||||
// As on a click: the roll marks what is open, and centres on it
|
||||
// because this too begins a session.
|
||||
w.set_library_roll_current(row.unwrap_or(0) as i32);
|
||||
@@ -6367,7 +6367,7 @@ fn wire_grid_routes(
|
||||
let weak = window.as_weak();
|
||||
window.on_library_change(move || {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.set_show_launch(true);
|
||||
w.set_active_view(View::Launch);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6401,10 +6401,11 @@ fn wire_grid_routes(
|
||||
let open = w.get_index().max(0) as usize;
|
||||
resume_position(&w, &ctl, &coll_ctl, Some(open));
|
||||
|
||||
w.set_show_library(true);
|
||||
w.set_active_view(View::Library);
|
||||
// TRACES: FR-UI-8
|
||||
// After the flag, not before: `current_place` reads it to say which
|
||||
// view the record is of, and this is the moment it becomes the grid.
|
||||
// After the view is set, not before: `current_place` reads it to
|
||||
// say which view the record is of, and this is the moment it
|
||||
// becomes the grid.
|
||||
write_place(&w, &ctl);
|
||||
});
|
||||
}
|
||||
@@ -6640,8 +6641,7 @@ fn wire_filter_ratings_and_people(window: &AppWindow, ctl: &Rc<LibraryController
|
||||
// Leaving the Identity screen for the grid is the whole point of
|
||||
// the button: the answer to "who is this" is a set of photographs,
|
||||
// and they are shown where photographs are shown.
|
||||
w.set_show_identity(false);
|
||||
w.set_show_library(true);
|
||||
w.set_active_view(View::Library);
|
||||
refilter(&w, &ctl);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::merge::{
|
||||
self, AlignmentReport, Cancel, Decision, FillSettings, MergeDestination, MergeEvent,
|
||||
MergeInput, MergeRequest,
|
||||
};
|
||||
use crate::{AppWindow, MergeFrameRow};
|
||||
use crate::{AppWindow, MergeFrameRow, Page};
|
||||
|
||||
/// How often the page reads the job's channel.
|
||||
const DRAIN_INTERVAL: std::time::Duration = std::time::Duration::from_millis(120);
|
||||
@@ -411,7 +411,7 @@ fn wire_stop_and_leave(window: &AppWindow, ctl: &Rc<MergeController>) {
|
||||
job.activity.finish_quietly();
|
||||
}
|
||||
*ctl.timer.borrow_mut() = None;
|
||||
w.set_show_merge(false);
|
||||
w.set_active_page(Page::None);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -481,7 +481,7 @@ fn start<Fetch>(
|
||||
window.set_merge_fill_note("".into());
|
||||
window.set_merge_preview_filled(false);
|
||||
window.set_merge_done(false);
|
||||
window.set_show_merge(true);
|
||||
window.set_active_page(Page::Merge);
|
||||
|
||||
let timer = slint::Timer::default();
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ use dr_types::{
|
||||
use slint::ComponentHandle;
|
||||
|
||||
use crate::settings_store::SettingsStore;
|
||||
use crate::{Adjustments, AppWindow};
|
||||
use crate::{Adjustments, AppWindow, Page};
|
||||
use dr_sync::Connection;
|
||||
|
||||
/// Shared settings state for the running window.
|
||||
@@ -436,7 +436,7 @@ fn wire_open_close(
|
||||
*ctl.settings.borrow_mut() = ctl.store.load();
|
||||
render(&w, &ctl);
|
||||
on_open(&w);
|
||||
w.set_show_settings(true);
|
||||
w.set_active_page(Page::Settings);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ fn wire_open_close(
|
||||
let weak = window.as_weak();
|
||||
window.on_settings_close(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
w.set_show_settings(false);
|
||||
w.set_active_page(Page::None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+65
-26
@@ -32,6 +32,45 @@ export { ViewMode, GradientHandle, HandleRole, SpotHandle, SpotRole }
|
||||
// panels take their wiring this way and why there is one global per family.
|
||||
export { Develop, Capture, Levels, Peaking, Adjustments, Framing, Transfer, Masking, Repair, Steps }
|
||||
|
||||
/// Which top-level screen is on display. Exactly one at a time — replacing
|
||||
/// the `show-launch` / `show-library` / `show-identity` booleans, with
|
||||
/// develop as "none of the above" wired into every conditional that drew any
|
||||
/// of the other three. Those conditionals compared four and five booleans at
|
||||
/// once to rule out two screens being true together; comparing one value to
|
||||
/// a variant cannot be wrong in that way.
|
||||
///
|
||||
/// `develop` is listed first, and so is the type's default: it is what the
|
||||
/// old booleans meant when every one of them was false, which is the state
|
||||
/// files named on the command line stay in permanently — there is no
|
||||
/// library behind them to switch to — and the state the window briefly
|
||||
/// holds at construction, before `lib.rs::run` chooses.
|
||||
export enum View {
|
||||
develop,
|
||||
library,
|
||||
identity,
|
||||
launch,
|
||||
}
|
||||
|
||||
/// Which page, if any, is drawn over the current `View`. `settings`,
|
||||
/// `import` and `merge` are reachable from more than one `View` — Settings
|
||||
/// from the library and from develop, Import and Merge from the library —
|
||||
/// and are drawn outermost so the view underneath is torn down rather than
|
||||
/// nested inside the one it is about to change. `none` is the ordinary case,
|
||||
/// and the type's default.
|
||||
///
|
||||
/// Kept apart from `View` rather than folded into it as three more variants:
|
||||
/// closing a page has to return to whichever view was already current, and
|
||||
/// today that works because the view's own property is left untouched while
|
||||
/// a page sits over it. Three more `View` variants would need a second field
|
||||
/// remembering what to return to; a separate, single-valued `Page` needs
|
||||
/// nothing to remember.
|
||||
export enum Page {
|
||||
none,
|
||||
settings,
|
||||
import,
|
||||
merge,
|
||||
}
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
title: "DarkRoom";
|
||||
// The one raster asset in the UI, and the exception that the header of
|
||||
@@ -184,11 +223,16 @@ export component AppWindow inherits Window {
|
||||
/// remembered across photographs, which is state no screen holds.
|
||||
callback inspect-toggled(float, float);
|
||||
|
||||
// --- active view (FR-UI-8) ---
|
||||
//
|
||||
// The four top-level screens share one property; see `View`. Rust is the
|
||||
// only writer — nothing in this file assigns to it — so `in` is enough.
|
||||
in property <View> active-view: View.develop;
|
||||
|
||||
// --- launch screen (FR-NC-1, FR-NC-4) ---
|
||||
//
|
||||
// The app opens here when no library is configured, and returns here to
|
||||
// sign out or switch account.
|
||||
in property <bool> show-launch: false;
|
||||
in property <bool> launch-signed-in: false;
|
||||
in property <string> launch-account: "";
|
||||
in property <string> launch-root: "";
|
||||
@@ -228,13 +272,12 @@ export component AppWindow inherits Window {
|
||||
//
|
||||
// Shown after a library is opened, before an image is chosen. Cells are a
|
||||
// window over the catalog, not the whole of it.
|
||||
in property <bool> show-library: false;
|
||||
/// Whether the grid is actually on screen — the condition the `if` below
|
||||
/// is written with, hoisted so Rust can read the same answer.
|
||||
///
|
||||
/// **`show-library` is not that answer.** It says "the library rather than
|
||||
/// develop", and stays true while Settings, Import, People or the launch
|
||||
/// screen replaces the window. The grid subtree is torn down in all four
|
||||
/// **`active-view == View.library` is not enough on its own.** The view
|
||||
/// stays `library` while Settings, Import, Merge or People replaces the
|
||||
/// window as a page over it. The grid subtree is torn down in all four
|
||||
/// cases, and a Flickable being destroyed passes its viewport through zero
|
||||
/// on the way out — which reaches `on_library_scrolled` as a scroll to the
|
||||
/// top of the library and overwrote the position the user was at. That is
|
||||
@@ -243,8 +286,7 @@ export component AppWindow inherits Window {
|
||||
/// So the guard has to be this, and the `if` has to be spelled from it, or
|
||||
/// the two can drift apart again.
|
||||
out property <bool> library-visible:
|
||||
!root.show-import && !root.show-merge && !root.show-settings && !root.show-identity
|
||||
&& !root.show-launch && root.show-library;
|
||||
root.active-page == Page.none && root.active-view == View.library;
|
||||
in-out property <[LibraryCell]> library-cells;
|
||||
in property <int> library-total: 0;
|
||||
// --- background activity (FR-CAT-1, FR-NC-6c) ---
|
||||
@@ -396,8 +438,7 @@ export component AppWindow inherits Window {
|
||||
// A third top-level screen rather than a panel: naming a cluster and
|
||||
// pulling a stranger out of it are tasks with their own rhythm, and they
|
||||
// need the whole window. Gated the same way the other screens are, so
|
||||
// exactly one is ever up.
|
||||
in property <bool> show-identity: false;
|
||||
// exactly one is ever up — see `View`.
|
||||
in property <[IdentityPerson]> identity-people;
|
||||
in property <[IdentityFace]> identity-faces;
|
||||
in property <int> identity-selected: -1;
|
||||
@@ -780,14 +821,14 @@ export component AppWindow inherits Window {
|
||||
|
||||
// --- settings (FR-EXP-1, FR-EXP-3, FR-NC-6a) ---
|
||||
//
|
||||
// A page rather than an overlay, and the outermost of the view conditions
|
||||
// below: it is reachable from the library and from develop, so guarding it
|
||||
// with `!show-library` or `!show-launch` would make which one you came from
|
||||
// decide whether it appears.
|
||||
// A page rather than an overlay, and outermost among the pages drawn over
|
||||
// a view: it is reachable from the library and from develop, so guarding
|
||||
// it with which view was current would make which one you came from
|
||||
// decide whether it appears. See `Page`.
|
||||
//
|
||||
// Every control saves on change (see `settings_ui.rs`), so there is no
|
||||
// dirty state here and nothing to confirm on the way out.
|
||||
in-out property <bool> show-settings: false;
|
||||
in-out property <Page> active-page: Page.none;
|
||||
|
||||
// --- import (FR-CAT-10, FR-CAT-11, FR-NC-7a) -------------------------
|
||||
//
|
||||
@@ -798,7 +839,6 @@ export component AppWindow inherits Window {
|
||||
// Unlike Settings, this page holds a *decision* rather than a set of saved
|
||||
// preferences, which is why it carries `import-can-start` and a running
|
||||
// state: everything here exists to be committed or abandoned.
|
||||
in-out property <bool> show-import: false;
|
||||
/// Whether this platform can import at all — see `dr_plat::imports_supported`.
|
||||
in property <bool> import-supported: false;
|
||||
|
||||
@@ -829,7 +869,6 @@ export component AppWindow inherits Window {
|
||||
// TRACES: FR-MRG-1 | FR-MRG-7
|
||||
// The merge page, on the import page's model: a decision to be
|
||||
// committed or abandoned, with a running state, over the library.
|
||||
in-out property <bool> show-merge: false;
|
||||
in property <string> merge-stage: "";
|
||||
in property <bool> merge-running: false;
|
||||
in property <float> merge-fraction: 0.0;
|
||||
@@ -1266,12 +1305,12 @@ in property <bool> panel-visible: true;
|
||||
}
|
||||
|
||||
// Settings, over everything. First in the file and first in z-order so the
|
||||
// conditions below can be read as "and settings is not open".
|
||||
// conditions below can be read as "and no page is open".
|
||||
// Outermost with the settings page, and before it: both are reachable
|
||||
// from the library and from develop, so guarding either with
|
||||
// `!show-library` would make which view you came from decide whether
|
||||
// from the library and from develop, so guarding either with which
|
||||
// view was current would make which view you came from decide whether
|
||||
// it appears.
|
||||
if root.show-import: ImportPage {
|
||||
if root.active-page == Page.import: ImportPage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -1313,7 +1352,7 @@ in property <bool> panel-visible: true;
|
||||
close() => { root.import-close(); }
|
||||
}
|
||||
|
||||
if !root.show-import && root.show-merge: MergePage {
|
||||
if root.active-page == Page.merge: MergePage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
stage: root.merge-stage;
|
||||
@@ -1346,7 +1385,7 @@ in property <bool> panel-visible: true;
|
||||
close() => { root.merge-close(); }
|
||||
}
|
||||
|
||||
if !root.show-import && !root.show-merge && root.show-settings: SettingsPage {
|
||||
if root.active-page == Page.settings: SettingsPage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -1482,8 +1521,8 @@ in property <bool> panel-visible: true;
|
||||
}
|
||||
|
||||
// Like the launch screen, this replaces the window rather than
|
||||
// overlaying it — see `show-identity`.
|
||||
if !root.show-import && !root.show-merge && !root.show-settings && root.show-identity: IdentityScreen {
|
||||
// overlaying it — see `View`.
|
||||
if root.active-page == Page.none && root.active-view == View.identity: IdentityScreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
people: root.identity-people;
|
||||
@@ -1541,7 +1580,7 @@ in property <bool> panel-visible: true;
|
||||
|
||||
// The launch screen replaces the whole window rather than overlaying it:
|
||||
// there is no library to look at until an account is configured.
|
||||
if !root.show-import && !root.show-merge && !root.show-settings && !root.show-identity && root.show-launch: LaunchScreen {
|
||||
if root.active-page == Page.none && root.active-view == View.launch: LaunchScreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
signed-in: root.launch-signed-in;
|
||||
@@ -1835,7 +1874,7 @@ in property <bool> panel-visible: true;
|
||||
}
|
||||
}
|
||||
|
||||
if !root.show-import && !root.show-merge && !root.show-settings && !root.show-identity && !root.show-launch && !root.show-library: VerticalLayout {
|
||||
if root.active-page == Page.none && root.active-view == View.develop: VerticalLayout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user