Wire the develop panels through globals, so a second copy is one line
Every panel in the develop column declared its inputs and its callbacks and had `app.slint` bind each one to a property or a callback on the window root. That is fine while a panel is drawn once. N9 draws them a second time, in the portrait dock, and the wiring is what would have to be copied: `MaskPanel` alone ran to forty lines of forwarding, and a callback added to one copy and not the other compiles, renders, and simply does nothing on the layout nobody was looking at. So the wiring moved to Slint globals. A panel reads the global and calls the global; Rust hooks the global instead of the window; and the instantiation in the column is now the panel's name and a pair of braces — every one of the ten children of the column, with no property that differs by placement left to supply. There is a global per panel family rather than one for all of them, and the reason is an import cycle. Each panel's model struct — `ParamRow`, `MaskRow`, `HistogramView` — is declared in the panel's own file, so a single global holding `[MaskRow]` and `[ParamRow]` would have to live in a file importing `masks.slint` and `adjust.slint` while both imported the global back, which Slint rejects. Breaking that needs six model declarations relocated, which is a change to the data model and not to the plumbing this is about. A global beside the panel it serves also lets each name drop the prefix it was carrying only because the window root is one flat namespace: `root.spot-radius` is `Repair.radius`, and `root.peaking-on` is `Peaking.showing`. `session.slint` is new and holds the two facts every family needs and none of them owns: whether there is an open photograph to edit, and which mode the view is in, with the three readings of the mode derived once instead of at each of the dozen places that tested one. `ViewMode` moves there from `adjust.slint`, where it was only ever a lodger. Nothing on screen changes. What is not here: the tool rail and the status strip still take their properties at the instantiation, because they are drawn once and N9 does not copy them; the preset sheet's own state stays on the window, because the library grid opens the same sheet and a global cannot bind the window's state — which is why `Transfer.open-presets` is handled in `presets.rs`, beside the summary it already had to compute.
This commit is contained in:
+187
-150
@@ -382,23 +382,25 @@ fn is_supported(p: &Path) -> bool {
|
||||
/// rect chosen for the first — or, since local masking became a mode, would
|
||||
/// open the next photograph with a mask stack it does not have.
|
||||
fn reset_view_state(window: &AppWindow) {
|
||||
window.set_view_mode(ViewMode::Photo);
|
||||
window.global::<Develop>().set_view_mode(ViewMode::Photo);
|
||||
window.set_zoom(1.0);
|
||||
window.set_zoomed(false);
|
||||
window.set_crop_x(0.0);
|
||||
window.set_crop_y(0.0);
|
||||
window.set_crop_w(1.0);
|
||||
window.set_crop_h(1.0);
|
||||
window.set_max_straighten(dr_pipeline::framing::MAX_STRAIGHTEN);
|
||||
window.set_straighten(0.0);
|
||||
window.set_flip_h(false);
|
||||
window.set_flip_v(false);
|
||||
window.set_framing_modified(false);
|
||||
let framing = window.global::<Framing>();
|
||||
framing.set_max_straighten(dr_pipeline::framing::MAX_STRAIGHTEN);
|
||||
framing.set_angle(0.0);
|
||||
framing.set_flip_h(false);
|
||||
framing.set_flip_v(false);
|
||||
framing.set_modified(false);
|
||||
// A photograph that failed to decode has no session, so nothing below
|
||||
// will speak for it — and the buttons would otherwise keep offering the
|
||||
// previous image's history.
|
||||
window.set_can_undo(false);
|
||||
window.set_can_redo(false);
|
||||
let steps = window.global::<Steps>();
|
||||
steps.set_can_undo(false);
|
||||
steps.set_can_redo(false);
|
||||
// TRACES: FR-DEV-7
|
||||
// And the comparison goes down with them. Unlike the inspection point
|
||||
// below it, this is not a way of looking at a folder: it is a question
|
||||
@@ -411,17 +413,19 @@ fn reset_view_state(window: &AppWindow) {
|
||||
// And an armed sampler goes down with it: a picker left waiting would make
|
||||
// the first click on the *next* photograph sample it, which is a click
|
||||
// nobody meant to spend.
|
||||
window.set_sampling_op(-1);
|
||||
window.global::<Adjustments>().set_sampling_op(-1);
|
||||
// TRACES: FR-DSP-7
|
||||
// Emptied rather than left standing: the previous photograph's histogram
|
||||
// beside the next one's filename is a confident, precise lie, and the gap
|
||||
// before the new frame settles is exactly long enough to read it.
|
||||
window.set_histogram(histogram::empty());
|
||||
window.global::<Levels>().set_data(histogram::empty());
|
||||
// And the raw reading with it. It belongs to one file more completely than
|
||||
// the display histogram does — nothing about the edit can move it — which
|
||||
// makes leaving it standing over the next photograph's filename worse
|
||||
// rather than better: it would look exactly as current as it is wrong.
|
||||
window.set_raw_histogram(histogram::raw_empty(histogram::RawAbsence::NoImage));
|
||||
window
|
||||
.global::<Levels>()
|
||||
.set_raw_data(histogram::raw_empty(histogram::RawAbsence::NoImage));
|
||||
// TRACES: FR-CULL-3
|
||||
// The marks go down with it, and for the same reason. What is *not* reset
|
||||
// is whether peaking is switched on: that is a way of looking at a folder
|
||||
@@ -521,10 +525,11 @@ fn sync_framing(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSession>>
|
||||
return;
|
||||
};
|
||||
let (angle, flip_h, flip_v, modified, crop) = s;
|
||||
window.set_straighten(angle);
|
||||
window.set_flip_h(flip_h);
|
||||
window.set_flip_v(flip_v);
|
||||
window.set_framing_modified(modified);
|
||||
let framing = window.global::<Framing>();
|
||||
framing.set_angle(angle);
|
||||
framing.set_flip_h(flip_h);
|
||||
framing.set_flip_v(flip_v);
|
||||
framing.set_modified(modified);
|
||||
// The overlay draws from these, and a rotation re-expresses the rect —
|
||||
// so they have to follow a quarter turn even though no handle moved.
|
||||
window.set_crop_x(crop.x);
|
||||
@@ -551,9 +556,10 @@ fn sync_crop_aspect(
|
||||
.iter()
|
||||
.position(|a| *a == chosen)
|
||||
.unwrap_or(0);
|
||||
window.set_crop_aspect(index as i32);
|
||||
window.set_crop_portrait(portrait.get());
|
||||
window.set_crop_aspect_turnable(chosen.has_orientation());
|
||||
let framing = window.global::<Framing>();
|
||||
framing.set_aspect(index as i32);
|
||||
framing.set_portrait(portrait.get());
|
||||
framing.set_aspect_turnable(chosen.has_orientation());
|
||||
}
|
||||
|
||||
/// TRACES: FR-DEV-3
|
||||
@@ -582,7 +588,7 @@ fn apply_crop_aspect(
|
||||
window.set_crop_y(crop.y);
|
||||
window.set_crop_w(crop.width);
|
||||
window.set_crop_h(crop.height);
|
||||
window.set_framing_modified(modified);
|
||||
window.global::<Framing>().set_modified(modified);
|
||||
}
|
||||
|
||||
/// TRACES: FR-EXP-6 | FR-EXP-9 | FR-EXP-7
|
||||
@@ -816,15 +822,16 @@ pub(crate) fn sync_film(window: &AppWindow, session: &Rc<RefCell<Option<DevelopS
|
||||
.and_then(dr_film::default_print)
|
||||
.is_some();
|
||||
|
||||
window.set_film_stocks(slint::ModelRc::new(slint::VecModel::from(
|
||||
let adjustments = window.global::<Adjustments>();
|
||||
adjustments.set_film_stocks(slint::ModelRc::new(slint::VecModel::from(
|
||||
choices
|
||||
.into_iter()
|
||||
.map(|(_, name)| slint::SharedString::from(name))
|
||||
.collect::<Vec<_>>(),
|
||||
)));
|
||||
window.set_film_selected(selected as i32);
|
||||
window.set_film_can_print(can_print);
|
||||
window.set_film_print(chosen.map(|(_, print)| print).unwrap_or(false));
|
||||
adjustments.set_film_selected(selected as i32);
|
||||
adjustments.set_film_can_print(can_print);
|
||||
adjustments.set_film_print(chosen.map(|(_, print)| print).unwrap_or(false));
|
||||
}
|
||||
|
||||
pub(crate) fn sync_rows(
|
||||
@@ -861,8 +868,10 @@ pub(crate) fn sync_rows(
|
||||
),
|
||||
None => (Vec::<slint::SharedString>::new(), -1),
|
||||
};
|
||||
window.set_adjust_tabs(slint::ModelRc::new(slint::VecModel::from(tabs)));
|
||||
window.set_adjust_active_tab(active_tab);
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.set_tabs(slint::ModelRc::new(slint::VecModel::from(tabs)));
|
||||
window.global::<Adjustments>().set_active_tab(active_tab);
|
||||
|
||||
let current = match session.borrow().as_ref() {
|
||||
Some(s) => s.rows(),
|
||||
@@ -920,7 +929,9 @@ pub(crate) fn sync_rows(
|
||||
Some(s) => s.curve_channels().into_iter().map(Into::into).collect(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
window.set_curve_channels(slint::ModelRc::new(slint::VecModel::from(channels)));
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.set_curve_channels(slint::ModelRc::new(slint::VecModel::from(channels)));
|
||||
}
|
||||
|
||||
// Which curve is plotted, on every pass. Picking one that happens to be
|
||||
@@ -928,7 +939,7 @@ pub(crate) fn sync_rows(
|
||||
// moves no point, so this cannot ride on the resample below: the chips
|
||||
// would go on highlighting the curve the user just navigated away from.
|
||||
let channel = session.borrow().as_ref().map_or(0, |s| s.curve_channel());
|
||||
window.set_curve_channel(channel);
|
||||
window.global::<Adjustments>().set_curve_channel(channel);
|
||||
|
||||
if !curve_moved {
|
||||
return;
|
||||
@@ -941,7 +952,9 @@ pub(crate) fn sync_rows(
|
||||
|
||||
// The drawn curve follows the points. Replacing this model wholesale is
|
||||
// safe where replacing `rows` was not: nothing in it is a drag target.
|
||||
window.set_curve_samples(slint::ModelRc::new(slint::VecModel::from(samples)));
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.set_curve_samples(slint::ModelRc::new(slint::VecModel::from(samples)));
|
||||
}
|
||||
|
||||
/// Copy `fresh`'s values into `existing`, keeping the model identity.
|
||||
@@ -1712,7 +1725,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// One model for the lifetime of the window. Rows are mutated in place;
|
||||
// see `sync_rows` for why replacing it breaks dragging.
|
||||
let rows: Rc<slint::VecModel<ParamRow>> = Rc::new(slint::VecModel::default());
|
||||
window.set_adjust_rows(rows.clone().into());
|
||||
window.global::<Adjustments>().set_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
|
||||
@@ -1799,8 +1812,9 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// edit ends in a redraw and nothing else is on all of their paths:
|
||||
// the parameter callbacks sync rows, the framing ones sync the
|
||||
// geometry panel, and a paste arrives through neither.
|
||||
window.set_can_undo(s.can_undo());
|
||||
window.set_can_redo(s.can_redo());
|
||||
let steps = window.global::<Steps>();
|
||||
steps.set_can_undo(s.can_undo());
|
||||
steps.set_can_redo(s.can_redo());
|
||||
|
||||
// TRACES: FR-DEV-5 | FR-DEV-7
|
||||
// The list, on the same path and for the same reason — but only
|
||||
@@ -1811,9 +1825,8 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let revision = s.history_revision();
|
||||
if drawn_history.get() != Some(revision) {
|
||||
drawn_history.set(Some(revision));
|
||||
window
|
||||
.set_history_rows(slint::ModelRc::new(slint::VecModel::from(s.history_rows())));
|
||||
window.set_undo_label(s.undo_label().into());
|
||||
steps.set_rows(slint::ModelRc::new(slint::VecModel::from(s.history_rows())));
|
||||
steps.set_undo_label(s.undo_label().into());
|
||||
}
|
||||
|
||||
// TRACES: FR-DEV-3
|
||||
@@ -1843,7 +1856,9 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
if s.peaking() != chosen_peaking.get() {
|
||||
s.set_peaking(chosen_peaking.get());
|
||||
}
|
||||
window.set_peaking_available(s.peaking_available());
|
||||
window
|
||||
.global::<Peaking>()
|
||||
.set_available(s.peaking_available());
|
||||
|
||||
let (mut w, mut h) = *viewport.borrow();
|
||||
|
||||
@@ -1872,7 +1887,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// Crop mode shows the whole frame, or the area being cropped away
|
||||
// would not be on screen for the handles to drag across. The
|
||||
// overlay draws the rect on top of it.
|
||||
let rendered = if window.get_view_mode() == ViewMode::Crop {
|
||||
let rendered = if window.global::<Develop>().get_view_mode() == ViewMode::Crop {
|
||||
s.render_uncropped(w, h).map(|(image, _, _)| image)
|
||||
} else if window.get_showing_original() {
|
||||
s.render_original(w, h)
|
||||
@@ -1909,7 +1924,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// half-resolution frame flickering past under a finger is
|
||||
// not a reading anyone takes.
|
||||
if !draft {
|
||||
window.set_histogram(
|
||||
window.global::<Levels>().set_data(
|
||||
s.histogram()
|
||||
.as_ref()
|
||||
.map_or_else(histogram::empty, histogram::view),
|
||||
@@ -1939,7 +1954,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
histogram::raw_view,
|
||||
)
|
||||
};
|
||||
window.set_raw_histogram(raw);
|
||||
window.global::<Levels>().set_raw_data(raw);
|
||||
}
|
||||
|
||||
// TRACES: FR-CULL-3 | NFR-P14
|
||||
@@ -1975,7 +1990,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
window.set_load_error(e.into());
|
||||
// No frame, so nothing to describe. The stale plot would
|
||||
// otherwise sit beside the error message looking current.
|
||||
window.set_histogram(histogram::empty());
|
||||
window.global::<Levels>().set_data(histogram::empty());
|
||||
// **The raw reading is left standing, and that is not an
|
||||
// oversight.** It describes the sensor data behind the
|
||||
// session, which a failed *render* says nothing about: the
|
||||
@@ -2124,16 +2139,17 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
match load(gpu.as_ref(), path) {
|
||||
Ok(l) => {
|
||||
window.set_load_error("".into());
|
||||
window.set_camera(describe_camera(&l.meta).into());
|
||||
window.set_exposure(describe_exposure(&l.meta).into());
|
||||
window.set_dimensions(format!("{} × {}", l.width, l.height).into());
|
||||
let capture = window.global::<Capture>();
|
||||
capture.set_camera(describe_camera(&l.meta).into());
|
||||
capture.set_exposure(describe_exposure(&l.meta).into());
|
||||
capture.set_dimensions(format!("{} × {}", l.width, l.height).into());
|
||||
|
||||
// Composed by the session rather than here, because only it
|
||||
// knows whether the lookup found anything — the header can
|
||||
// name a lens the database has never heard of, which is the
|
||||
// ordinary case for third-party and adapted glass and must
|
||||
// read as a fact rather than as a failure.
|
||||
window.set_lens(
|
||||
capture.set_lens(
|
||||
l.session
|
||||
.as_ref()
|
||||
.map(|s| s.lens_summary())
|
||||
@@ -2145,7 +2161,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// this code names no operation (FR-DEV-3a).
|
||||
match l.session {
|
||||
Some(s) => {
|
||||
window.set_adjust_enabled(true);
|
||||
window.global::<Develop>().set_enabled(true);
|
||||
*session.borrow_mut() = Some(s);
|
||||
|
||||
// A local file stores its edit beside itself. Read
|
||||
@@ -2178,7 +2194,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
*session.borrow_mut() = None;
|
||||
*open_image.borrow_mut() = presets::Stored::Nowhere;
|
||||
rows.set_vec(Vec::<ParamRow>::new());
|
||||
window.set_adjust_enabled(false);
|
||||
window.global::<Develop>().set_enabled(false);
|
||||
if let Some(image) = l.fallback {
|
||||
window.set_canvas(image);
|
||||
}
|
||||
@@ -2190,12 +2206,13 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// A failure on one image must not stop browsing (FR-RAW-4).
|
||||
log::warn!("{name}: {e}");
|
||||
*session.borrow_mut() = None;
|
||||
window.set_adjust_enabled(false);
|
||||
window.global::<Develop>().set_enabled(false);
|
||||
window.set_load_error(e.into());
|
||||
window.set_camera("".into());
|
||||
window.set_exposure("".into());
|
||||
window.set_dimensions("".into());
|
||||
window.set_lens("".into());
|
||||
let capture = window.global::<Capture>();
|
||||
capture.set_camera("".into());
|
||||
capture.set_exposure("".into());
|
||||
capture.set_dimensions("".into());
|
||||
capture.set_lens("".into());
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2229,9 +2246,10 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
reset_view_state(&w);
|
||||
w.set_filename(name.clone().into());
|
||||
w.set_load_error("".into());
|
||||
w.set_camera("".into());
|
||||
w.set_exposure("".into());
|
||||
w.set_dimensions("".into());
|
||||
let capture = w.global::<Capture>();
|
||||
capture.set_camera("".into());
|
||||
capture.set_exposure("".into());
|
||||
capture.set_dimensions("".into());
|
||||
// The grid is one image at a time, so next/previous have nothing
|
||||
// to walk. Shown as 1 of 1 rather than left reading 0.
|
||||
w.set_index(0);
|
||||
@@ -2345,12 +2363,13 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
match load_bytes(gpu.as_ref(), &bytes) {
|
||||
Ok(l) => {
|
||||
w.set_load_error("".into());
|
||||
w.set_camera(describe_camera(&l.meta).into());
|
||||
w.set_exposure(describe_exposure(&l.meta).into());
|
||||
w.set_dimensions(format!("{} × {}", l.width, l.height).into());
|
||||
let capture = w.global::<Capture>();
|
||||
capture.set_camera(describe_camera(&l.meta).into());
|
||||
capture.set_exposure(describe_exposure(&l.meta).into());
|
||||
capture.set_dimensions(format!("{} × {}", l.width, l.height).into());
|
||||
match l.session {
|
||||
Some(mut s) => {
|
||||
w.set_adjust_enabled(true);
|
||||
w.global::<Develop>().set_enabled(true);
|
||||
// TRACES: FR-CULL-10
|
||||
// Who is in this photograph, so a
|
||||
// segmentation run can say "Anna" where
|
||||
@@ -2409,7 +2428,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
None => {
|
||||
*session.borrow_mut() = None;
|
||||
rows.set_vec(Vec::<ParamRow>::new());
|
||||
w.set_adjust_enabled(false);
|
||||
w.global::<Develop>().set_enabled(false);
|
||||
if let Some(image) = l.fallback {
|
||||
w.set_canvas(image);
|
||||
}
|
||||
@@ -2420,7 +2439,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
Err(e) => {
|
||||
log::warn!("{name}: {e}");
|
||||
*session.borrow_mut() = None;
|
||||
w.set_adjust_enabled(false);
|
||||
w.global::<Develop>().set_enabled(false);
|
||||
w.set_load_error(e.into());
|
||||
}
|
||||
}
|
||||
@@ -2490,7 +2509,9 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// Rendered from the same settings controller and at the same moment,
|
||||
// so the rail and the strip cannot both be on screen after a change to
|
||||
// the preference that chooses between them.
|
||||
window.set_groups_in_rail(groups_in_rail(&settings));
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.set_groups_in_rail(groups_in_rail(&settings));
|
||||
presets::render(&window, &clipboard, &settings);
|
||||
}
|
||||
|
||||
@@ -2640,35 +2661,39 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_param_changed(move |op, param, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_param(op, param, value);
|
||||
// TRACES: FR-DEV-3f
|
||||
// The film's own exposures ride *inside* the baked tables
|
||||
// rather than arriving as uniforms, because the print balance
|
||||
// is solved against them — an enlarger's filtration depends on
|
||||
// how the negative was exposed. So moving one has to rebuild
|
||||
// the lookup, which no other slider in the panel does.
|
||||
s.rebake_film_if_affected(op);
|
||||
}
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.on_param_changed(move |op, param, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_param(op, param, value);
|
||||
// TRACES: FR-DEV-3f
|
||||
// The film's own exposures ride *inside* the baked tables
|
||||
// rather than arriving as uniforms, because the print balance
|
||||
// is solved against them — an enlarger's filtration depends on
|
||||
// how the negative was exposed. So moving one has to rebuild
|
||||
// the lookup, which no other slider in the panel does.
|
||||
s.rebake_film_if_affected(op);
|
||||
}
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_param_reset(move |op, param| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.reset_param(op, param);
|
||||
}
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.on_param_reset(move |op, param| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.reset_param(op, param);
|
||||
}
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
// TRACES: FR-DEV-3
|
||||
// The local-adjustment panel. Wired as a block rather than inline because
|
||||
@@ -2680,7 +2705,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_adjust_tab_picked(move |index| {
|
||||
window.global::<Adjustments>().on_tab_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_active_tab(index);
|
||||
@@ -2707,7 +2732,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_reset_all(move || {
|
||||
window.global::<Adjustments>().on_reset_all(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.reset_all();
|
||||
@@ -2724,7 +2749,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_curve_reset(move |op| {
|
||||
window.global::<Adjustments>().on_curve_reset(move |op| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.reset_curve(op);
|
||||
@@ -2743,13 +2768,15 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_curve_channel_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_curve_channel(index);
|
||||
}
|
||||
sync_rows(&w, &rows, &session);
|
||||
});
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.on_curve_channel_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_curve_channel(index);
|
||||
}
|
||||
sync_rows(&w, &rows, &session);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- the film stock (FR-DEV-3f) -------------------------------------
|
||||
@@ -2762,7 +2789,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_film_picked(move |index| {
|
||||
window.global::<Adjustments>().on_film_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
// Index zero is "None" — see `DevelopSession::film_choices`, which
|
||||
// puts it first so that the neutral choice needs no sentinel.
|
||||
@@ -2790,16 +2817,18 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_film_print_toggled(move |print| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
if let Some((stock, _)) = s.film().map(|(a, b)| (a.to_string(), b)) {
|
||||
s.pick_film(Some(&stock), print);
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.on_film_print_toggled(move |print| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
if let Some((stock, _)) = s.film().map(|(a, b)| (a.to_string(), b)) {
|
||||
s.pick_film(Some(&stock), print);
|
||||
}
|
||||
}
|
||||
}
|
||||
sync_film(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
sync_film(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- undo and redo (FR-DEV-5) ---------------------------------------
|
||||
@@ -2816,7 +2845,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_undo(move || {
|
||||
window.global::<Steps>().on_undo(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let stepped = session.borrow_mut().as_mut().is_some_and(|s| s.undo());
|
||||
if stepped {
|
||||
@@ -2830,7 +2859,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_redo(move || {
|
||||
window.global::<Steps>().on_redo(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let stepped = session.borrow_mut().as_mut().is_some_and(|s| s.redo());
|
||||
if stepped {
|
||||
@@ -2909,7 +2938,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_history_picked(move |index| {
|
||||
window.global::<Steps>().on_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let stepped = session
|
||||
.borrow_mut()
|
||||
@@ -2939,7 +2968,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
// the pipeline needs and the whole of what another application could read.
|
||||
let crop_aspect = Rc::new(Cell::new(develop::CropAspect::default()));
|
||||
let crop_portrait = Rc::new(Cell::new(false));
|
||||
window.set_crop_aspects(
|
||||
window.global::<Framing>().set_aspects(
|
||||
develop::CropAspect::CHOICES
|
||||
.iter()
|
||||
.map(|a| slint::SharedString::from(a.label()))
|
||||
@@ -3073,7 +3102,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
w.set_view_mode(mode);
|
||||
w.global::<Develop>().set_view_mode(mode);
|
||||
masks_ui::sync(&w, &session);
|
||||
if let Some(s) = session.borrow().as_ref() {
|
||||
spots_ui::sync_handles(&w, s);
|
||||
@@ -3126,7 +3155,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
w.set_crop_y(c.y);
|
||||
w.set_crop_w(c.width);
|
||||
w.set_crop_h(c.height);
|
||||
w.set_framing_modified(s.framing_edits_image());
|
||||
w.global::<Framing>().set_modified(s.framing_edits_image());
|
||||
}
|
||||
redraw(&w);
|
||||
});
|
||||
@@ -3145,7 +3174,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let redraw = redraw.clone();
|
||||
let crop_aspect = crop_aspect.clone();
|
||||
let crop_portrait = crop_portrait.clone();
|
||||
window.on_crop_aspect_picked(move |index| {
|
||||
window.global::<Framing>().on_aspect_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let Some(&aspect) = develop::CropAspect::CHOICES.get(index.max(0) as usize) else {
|
||||
return;
|
||||
@@ -3167,7 +3196,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let redraw = redraw.clone();
|
||||
let crop_aspect = crop_aspect.clone();
|
||||
let crop_portrait = crop_portrait.clone();
|
||||
window.on_crop_portrait_toggled(move || {
|
||||
window.global::<Framing>().on_portrait_toggled(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if !crop_aspect.get().has_orientation() {
|
||||
return;
|
||||
@@ -3191,7 +3220,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let redraw = redraw.clone();
|
||||
let crop_aspect = crop_aspect.clone();
|
||||
let crop_portrait = crop_portrait.clone();
|
||||
window.on_rotate_quarters(move |turns| {
|
||||
window.global::<Framing>().on_rotate(move |turns| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.rotate_quarters(turns);
|
||||
@@ -3214,7 +3243,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_flip_h_toggled(move || {
|
||||
window.global::<Framing>().on_flip_h_toggled(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.toggle_flip_h();
|
||||
@@ -3227,7 +3256,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_flip_v_toggled(move || {
|
||||
window.global::<Framing>().on_flip_v_toggled(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.toggle_flip_v();
|
||||
@@ -3240,7 +3269,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_straighten_changed(move |degrees| {
|
||||
window.global::<Framing>().on_angle_changed(move |degrees| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_angle(degrees);
|
||||
@@ -3270,18 +3299,20 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_straighten_committed(move |_degrees| {
|
||||
// The angle itself is already applied: `SliderTrack` emits its
|
||||
// last `changed` before it commits, so `on_straighten_changed` has
|
||||
// run with this very value. What is left is the correction that
|
||||
// has to happen exactly once.
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.auto_crop_to_angle();
|
||||
}
|
||||
sync_framing(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Framing>()
|
||||
.on_angle_committed(move |_degrees| {
|
||||
// The angle itself is already applied: `SliderTrack` emits its
|
||||
// last `changed` before it commits, so `on_straighten_changed` has
|
||||
// run with this very value. What is left is the correction that
|
||||
// has to happen exactly once.
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.auto_crop_to_angle();
|
||||
}
|
||||
sync_framing(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
// The geometry section's reset: crop, angle, rotation and flips back
|
||||
@@ -3290,7 +3321,7 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_framing_reset(move || {
|
||||
window.global::<Framing>().on_reset(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.reset_framing();
|
||||
@@ -3383,18 +3414,18 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let chosen = chosen_peaking.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_peaking_toggled(move |on| {
|
||||
window.global::<Peaking>().on_toggled(move |on| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
// Built from the chips as they currently stand rather than from a
|
||||
// remembered value: they are what the photographer can see, and an
|
||||
// overlay that came back in a configuration the panel is not
|
||||
// showing would be the panel lying about itself.
|
||||
let next = on.then(|| dr_gpu::FocusPeaking {
|
||||
sensitivity: peaking::sensitivity(w.get_peaking_sensitivity()),
|
||||
colour: peaking::colour(w.get_peaking_colour()),
|
||||
sensitivity: peaking::sensitivity(w.global::<Peaking>().get_sensitivity()),
|
||||
colour: peaking::colour(w.global::<Peaking>().get_colour()),
|
||||
});
|
||||
chosen.set(next);
|
||||
w.set_peaking_on(next.is_some());
|
||||
w.global::<Peaking>().set_showing(next.is_some());
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
@@ -3402,27 +3433,29 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
let weak = window.as_weak();
|
||||
let chosen = chosen_peaking.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_peaking_sensitivity_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
w.set_peaking_sensitivity(index);
|
||||
// Only reachable while peaking is on — the chips are not drawn
|
||||
// otherwise — but written as a conditional rather than an
|
||||
// `expect`, because a panel is free to change its mind about that
|
||||
// and nothing here should fall over when it does.
|
||||
if let Some(mut current) = chosen.get() {
|
||||
current.sensitivity = peaking::sensitivity(index);
|
||||
chosen.set(Some(current));
|
||||
redraw(&w);
|
||||
}
|
||||
});
|
||||
window
|
||||
.global::<Peaking>()
|
||||
.on_sensitivity_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
w.global::<Peaking>().set_sensitivity(index);
|
||||
// Only reachable while peaking is on — the chips are not drawn
|
||||
// otherwise — but written as a conditional rather than an
|
||||
// `expect`, because a panel is free to change its mind about that
|
||||
// and nothing here should fall over when it does.
|
||||
if let Some(mut current) = chosen.get() {
|
||||
current.sensitivity = peaking::sensitivity(index);
|
||||
chosen.set(Some(current));
|
||||
redraw(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let chosen = chosen_peaking.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_peaking_colour_picked(move |index| {
|
||||
window.global::<Peaking>().on_colour_picked(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
w.set_peaking_colour(index);
|
||||
w.global::<Peaking>().set_colour(index);
|
||||
if let Some(mut current) = chosen.get() {
|
||||
current.colour = peaking::colour(index);
|
||||
chosen.set(Some(current));
|
||||
@@ -3432,8 +3465,12 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||
}
|
||||
// The chips open on whatever the vocabulary calls its default, so the
|
||||
// panel and the pass agree before anything has been pressed.
|
||||
window.set_peaking_sensitivity(peaking::sensitivity_index(Default::default()));
|
||||
window.set_peaking_colour(peaking::colour_index(Default::default()));
|
||||
window
|
||||
.global::<Peaking>()
|
||||
.set_sensitivity(peaking::sensitivity_index(Default::default()));
|
||||
window
|
||||
.global::<Peaking>()
|
||||
.set_colour(peaking::colour_index(Default::default()));
|
||||
|
||||
// TRACES: FR-DSP-8 | FR-DSP-6
|
||||
// And which display that canvas is on, from now until the window closes.
|
||||
@@ -3831,7 +3868,7 @@ fn back_one_step(w: &AppWindow) -> bool {
|
||||
launch: w.get_show_launch(),
|
||||
browsing: w.get_launch_browsing(),
|
||||
library: w.get_show_library(),
|
||||
mode: w.get_view_mode(),
|
||||
mode: w.global::<Develop>().get_view_mode(),
|
||||
zoomed: w.get_zoomed(),
|
||||
// Files named on the command line have no grid behind them — the same
|
||||
// condition the status strip uses to decide whether to offer the way
|
||||
|
||||
Reference in New Issue
Block a user