Make the Identity Manager a peer of the library and develop

It was reachable only from the library header, which made it a side trip
rather than a mode. It is now reachable from develop's header too, beside
the way back, because that is the same kind of move -- leaving this
photograph for somewhere else in the library -- and a screen you can only
reach from one of the other two is not a peer of them.

Leaving returns to whichever screen opened it, and the button says which.
A back button that read "Library" while returning to develop would be
lying about the one thing a back button has to be right about. The
develop session is only hidden, never torn down, so returning to it costs
nothing and keeps the photographer's place.

The header now matches the other two screens rather than using a close
cross: three screens whose headers disagree read as three applications.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 23:14:17 +02:00
co-authored by Claude Opus 5
parent b55812812a
commit 61c4547b9c
5 changed files with 72 additions and 28 deletions
+19
View File
@@ -43,6 +43,13 @@ pub struct IdentityController {
/// Images visited so far in the current sweep, and the total it announced.
progress: std::cell::Cell<(usize, usize)>,
faces_found: std::cell::Cell<usize>,
/// Whether opening this screen left the library rather than develop.
///
/// Recorded so leaving puts the user back where they were. The screen is
/// reachable from both other modes, and returning a photographer to the
/// grid when they came from an open photograph loses their place for no
/// reason.
came_from_library: std::cell::Cell<bool>,
/// Rail portraits, kept between refreshes.
///
/// Every mutating action reloads the whole screen, and cutting a portrait
@@ -272,6 +279,13 @@ pub fn wire<S, M, P>(
let models_present = models.clone();
window.on_identity_open(move || {
let Some(w) = weak.upgrade() else { return };
let from_library = w.get_show_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);
// 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.
@@ -282,8 +296,13 @@ pub fn wire<S, M, P>(
{
let weak = window.as_weak();
let ctl = ctl.clone();
window.on_identity_close(move || {
let Some(w) = weak.upgrade() else { return };
// 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);
});
}
+16
View File
@@ -46,6 +46,7 @@ component StatusBar inherits Rectangle {
in property <bool> can-redo: false;
callback back-to-library();
callback open-people();
callback open-settings();
callback toggle-panel();
callback export-image();
@@ -99,6 +100,18 @@ component StatusBar inherits Rectangle {
clicked => { root.back-to-library(); }
}
// The third screen, reachable from the second. Beside the way back
// because it is the same kind of move — leaving this photograph for
// somewhere else in the library — and because a mode you can only
// reach from one of the other two is not a peer of them, whatever the
// navigation model says.
Button {
text: "Identity";
y: (parent.height - self.height) / 2;
visible: root.can-return-to-library;
clicked => { root.open-people(); }
}
Value { text: root.filename; compact: true; overflow: elide; }
Caption { text: root.position; }
@@ -534,6 +547,7 @@ export component AppWindow inherits Window {
in property <bool> identity-indexing: false;
in property <string> identity-indexing-status;
in property <bool> identity-model-missing: false;
in property <string> identity-back-label: "‹ Library";
in property <string> identity-coverage;
in property <bool> identity-coverage-complete: false;
in property <int> identity-picked: 0;
@@ -1287,6 +1301,7 @@ in property <bool> panel-visible: true;
indexing-status: root.identity-indexing-status;
model-missing: root.identity-model-missing;
picked-count: root.identity-picked;
back-label: root.identity-back-label;
coverage: root.identity-coverage;
coverage-complete: root.identity-coverage-complete;
@@ -1578,6 +1593,7 @@ in property <bool> panel-visible: true;
can-undo: root.can-undo;
can-redo: root.can-redo;
back-to-library() => { root.back-to-library(); }
open-people() => { root.identity-open(); }
open-settings() => { root.settings-open(); }
toggle-panel() => { root.toggle-panel(); }
export-image() => { root.export-image(); }
+21 -12
View File
@@ -166,6 +166,13 @@ export component IdentityScreen inherits Rectangle {
// No model on disk: face indexing cannot run at all (docs/faces.md §2.2).
in property <bool> model-missing: false;
in property <int> picked-count: 0;
/// Where leaving goes back to — "‹ Library" or "‹ Develop".
///
/// The label follows the behaviour rather than the other way round: this
/// screen is reachable from both of the other two, and a button that said
/// "Library" while returning to develop would be lying about the one thing
/// a back button has to be right about.
in property <string> back-label: "‹ Library";
callback person-picked(int);
callback rename(string);
@@ -191,18 +198,20 @@ export component IdentityScreen inherits Rectangle {
padding: Theme.gap;
spacing: Theme.gap-sm;
HorizontalLayout {
Text {
text: "People";
color: Theme.ink;
font-size: Theme.text-lg;
vertical-alignment: center;
}
Rectangle { }
IconButton {
icon: "cross";
clicked => { root.close(); }
}
// "‹ Library" rather than a close cross, matching develop's
// header exactly. This is a screen you *leave for the
// library*, not a dialogue you dismiss, and the two should
// not use different words for the same move.
Button {
text: root.back-label;
clicked => { root.close(); }
}
Text {
text: "Identity Manager";
color: Theme.ink;
font-size: Theme.text-lg;
vertical-alignment: center;
}
if root.unassigned > 0: Text {
+1 -1
View File
@@ -1020,7 +1020,7 @@ component HeaderActions inherits HorizontalLayout {
// A library action rather than a setting, so it sits with the others and
// ahead of Settings, which the comment below keeps last.
Button {
text: "People";
text: "Identity";
y: root.centred ? (root.row-height - self.height) / 2 : 0;
clicked => { root.open-people(); }
}