Filter the grid to a person with their eyes open

An "Eyes open" chip beside the people chips, offered only while someone
is chosen and dropped when the last person goes, so no term narrows the
grid with nothing on the bar to say so. It compiles the rule in
dr_face::eyes into the person's face subquery — Anna, eyes open, whoever
else is blinking beside her — and drops a frame only on a closed eye that
could be read: sunglasses, eyes too small or soft to read, and faces never
read all pass, so an old library shows everything under the chip until
the measuring pass has run. A test drives the same readings through the
SQL and through the rule and requires them to agree.

The People screen badges a face "Eyes closed", "Sunglasses" or "Eyes
unclear" so the reason a frame is or is not in the grid can be read off
the face; the sweep loads the three models when they are beside the pair
and reads eyes on the indexing and measuring passes from the native
render; the coverage line counts unread faces as work to measure so an
already-indexed library keeps its Index button. The term travels with the
place.
This commit is contained in:
2026-09-19 14:04:35 +02:00
parent 6aae4c3eb0
commit 83f4253b6a
16 changed files with 984 additions and 183 deletions
+31
View File
@@ -5671,6 +5671,7 @@ fn push_filter(window: &AppWindow, ctl: &Rc<LibraryController>) {
});
window.set_library_local_only(filter.local_only);
ctl.local_only.set(filter.local_only);
window.set_library_filter_eyes_open(filter.eyes_open);
push_people_chips(window, ctl);
}
@@ -6628,6 +6629,7 @@ pub fn wire<F>(
.borrow_mut()
.people
.retain(|p| *p != id.max(0) as u64);
drop_eyes_open_without_people(&w, &ctl);
push_people_chips(&w, &ctl);
refilter(&w, &ctl);
});
@@ -6656,11 +6658,27 @@ pub fn wire<F>(
f.people.push(person);
}
}
drop_eyes_open_without_people(&w, &ctl);
push_people_chips(&w, &ctl);
refilter(&w, &ctl);
});
}
{
let weak = window.as_weak();
let ctl = ctl.clone();
window.on_library_filter_eyes_open_toggled(move || {
let Some(w) = weak.upgrade() else { return };
let on = {
let mut f = ctl.filter.borrow_mut();
f.eyes_open = !f.eyes_open;
f.eyes_open
};
w.set_library_filter_eyes_open(on);
refilter(&w, &ctl);
});
}
{
let weak = window.as_weak();
let ctl = ctl.clone();
@@ -6988,6 +7006,19 @@ pub fn wire<F>(
/// The label is the People screen's own wording, unnamed groups included, so
/// the same group does not read one way on one screen and another way on the
/// next.
/// Drop the eyes-open term when the last person leaves the filter.
///
/// The chip lives beside the people chips and goes with them; a term that
/// stayed on after its chip had gone would narrow the grid with nothing on
/// the bar to say so, which is the one state a filter bar must never be in.
fn drop_eyes_open_without_people(window: &AppWindow, ctl: &Rc<LibraryController>) {
let mut f = ctl.filter.borrow_mut();
if f.people.is_empty() && f.eyes_open {
f.eyes_open = false;
window.set_library_filter_eyes_open(false);
}
}
fn push_people_chips(window: &AppWindow, ctl: &Rc<LibraryController>) {
let ids = ctl.filter.borrow().people.clone();
if ids.is_empty() {