Merge branch 'master' into android-bundled-face-models

This commit is contained in:
2026-08-27 20:22:42 +02:00
64 changed files with 8205 additions and 1907 deletions
+44 -1
View File
@@ -23,6 +23,7 @@ mod activity;
mod collections_ui;
mod derived_sync;
mod develop;
mod display_ui;
mod export;
pub mod faces;
mod gradient;
@@ -39,6 +40,7 @@ mod live_style;
mod masks_ui;
mod net_runtime;
mod presets;
mod remote;
mod segmentation;
mod settings_store;
mod settings_ui;
@@ -1381,8 +1383,20 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
let rows: Rc<slint::VecModel<ParamRow>> = Rc::new(slint::VecModel::default());
window.set_adjust_rows(rows.clone().into());
// Viewport size, tracked so a re-render after a slider move matches it.
//
// In *physical* pixels, which is what the render target wants and what
// FR-DSP-8 means by handling fractional scaling without resampling. The
// one caller that thinks in logical pixels is Slint's resize callback,
// and it converts on the way in.
let viewport = Rc::new(RefCell::new((1024u32, 768u32)));
// TRACES: FR-DSP-8
// What the displays are, and which one the canvas is on. Probed once here
// so that the About page can describe the session's colour path before any
// photograph is opened — the acquisition path is a property of the desktop
// and not of the image.
let display = display_ui::DisplayWatch::probe();
// Re-render the current session into the canvas.
//
// Called on every slider change, so it must do no more than run the
@@ -1397,10 +1411,26 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
let session = session.clone();
let viewport = viewport.clone();
let drawn_history = drawn_history.clone();
let display = display.clone();
Rc::new(move |window: &AppWindow, draft: bool| {
let mut slot = session.borrow_mut();
let Some(s) = slot.as_mut() else { return };
// TRACES: FR-DSP-8 | FR-DSP-6
// **Every canvas render is encoded for the display showing it.**
//
// Set here rather than pushed from the display watch, and rather
// than set once when a photograph is opened, because this is the
// one path every frame takes. A session opened while the window
// sits on the second monitor is then correct on its *first* frame
// — where a push would leave it sRGB until the next poll, which is
// a visible flash of the wrong colour on every image opened.
//
// Free when nothing has changed: the field is compared before it
// is written, and an unchanged output space composes to the same
// structure hash and the same cached pipeline.
s.set_display_space(display.space());
// TRACES: FR-DEV-5
// Whether undo has anywhere to go, pushed from here because every
// edit ends in a redraw and nothing else is on all of their paths:
@@ -2574,13 +2604,22 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
// Track the canvas size so the adjust pass renders at viewport
// resolution rather than sensor resolution (FR-DSP-1).
//
// TRACES: FR-DSP-8
// Slint reports the canvas in *logical* pixels, which is the box the
// compositor will draw into and not the number of device pixels it will
// fill. `display_ui::physical` converts, so that a fractionally scaled
// desktop is presented 1:1 rather than resampled — see that function for
// why a soft canvas is the failure being avoided here.
{
let weak = window.as_weak();
let viewport = viewport.clone();
let redraw = redraw.clone();
let display = display.clone();
window.on_canvas_resized(move |w_px, h_px| {
let Some(w) = weak.upgrade() else { return };
let size = (w_px.max(1) as u32, h_px.max(1) as u32);
let logical = (w_px.max(1) as u32, h_px.max(1) as u32);
let size = display.canvas_resized(&w, logical);
if *viewport.borrow() == size {
return;
}
@@ -2589,6 +2628,10 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
});
}
// TRACES: FR-DSP-8 | FR-DSP-6
// And which display that canvas is on, from now until the window closes.
display_ui::attach(&window, &display, &viewport, redraw.clone());
// FR-UI-1: layout class from window width. Computed here rather than in
// Slint because a property that both derives from and feeds the layout is
// a binding loop.