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
|
||||
|
||||
+174
-143
@@ -31,7 +31,8 @@ use slint::{ComponentHandle as _, Model as _, ModelRc, VecModel};
|
||||
use crate::develop::{Abandon, DevelopSession, RefinedInstance, Segmented, SessionId};
|
||||
use crate::segmentation;
|
||||
use crate::{
|
||||
sync_rows, AppWindow, CategoryRow, GradientHandle, MaskRow, ParamRow, PartRow, SubjectRow,
|
||||
sync_rows, Adjustments, AppWindow, CategoryRow, GradientHandle, MaskRow, Masking, ParamRow,
|
||||
PartRow, Steps, SubjectRow,
|
||||
};
|
||||
|
||||
/// What the adjust panel's heading says when the controls are global.
|
||||
@@ -166,12 +167,15 @@ fn category_label(name: &str) -> String {
|
||||
/// Push every mask-related property from the session into the window.
|
||||
pub(crate) fn sync(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSession>>>) {
|
||||
let slot = session.borrow();
|
||||
let masking = window.global::<Masking>();
|
||||
let Some(s) = slot.as_ref() else {
|
||||
window.set_mask_rows(ModelRc::new(VecModel::<MaskRow>::default()));
|
||||
window.set_subject_rows(ModelRc::new(VecModel::<SubjectRow>::default()));
|
||||
window.set_category_rows(ModelRc::new(VecModel::<CategoryRow>::default()));
|
||||
window.set_segmented(false);
|
||||
window.set_adjust_scope(GLOBAL_SCOPE.into());
|
||||
masking.set_masks(ModelRc::new(VecModel::<MaskRow>::default()));
|
||||
masking.set_subjects(ModelRc::new(VecModel::<SubjectRow>::default()));
|
||||
masking.set_categories(ModelRc::new(VecModel::<CategoryRow>::default()));
|
||||
masking.set_segmented(false);
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.set_scope(GLOBAL_SCOPE.into());
|
||||
clear_handles(window);
|
||||
window.set_overlay_on(false);
|
||||
return;
|
||||
@@ -213,7 +217,7 @@ pub(crate) fn sync(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSessio
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
window.set_mask_rows(ModelRc::new(VecModel::from(rows)));
|
||||
masking.set_masks(ModelRc::new(VecModel::from(rows)));
|
||||
|
||||
let subjects: Vec<SubjectRow> = s
|
||||
.detected_subjects()
|
||||
@@ -225,7 +229,7 @@ pub(crate) fn sync(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSessio
|
||||
score,
|
||||
})
|
||||
.collect();
|
||||
window.set_subject_rows(ModelRc::new(VecModel::from(subjects)));
|
||||
masking.set_subjects(ModelRc::new(VecModel::from(subjects)));
|
||||
|
||||
// Already largest-first from the precompute, and already filtered to the
|
||||
// ones with enough coverage to be worth a control.
|
||||
@@ -238,7 +242,7 @@ pub(crate) fn sync(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSessio
|
||||
coverage: c.coverage,
|
||||
})
|
||||
.collect();
|
||||
window.set_category_rows(ModelRc::new(VecModel::from(categories)));
|
||||
masking.set_categories(ModelRc::new(VecModel::from(categories)));
|
||||
|
||||
// TRACES: FR-DEV-19a
|
||||
// Only the selected layer's parts, and only when exactly one is selected:
|
||||
@@ -258,15 +262,17 @@ pub(crate) fn sync(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSessio
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
window.set_mask_parts(ModelRc::new(VecModel::from(parts)));
|
||||
masking.set_parts(ModelRc::new(VecModel::from(parts)));
|
||||
|
||||
let (radius, hardness, flow) = s.brush();
|
||||
window.set_brush_radius(radius);
|
||||
window.set_brush_hardness(hardness);
|
||||
window.set_brush_flow(flow);
|
||||
masking.set_brush_radius(radius);
|
||||
masking.set_brush_hardness(hardness);
|
||||
masking.set_brush_flow(flow);
|
||||
|
||||
window.set_segmented(s.has_segmentation());
|
||||
window.set_adjust_scope(scope_label(s).into());
|
||||
masking.set_segmented(s.has_segmentation());
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.set_scope(scope_label(s).into());
|
||||
sync_handles(window, s);
|
||||
|
||||
// The overlay is regenerated only when there is one to draw. It is a
|
||||
@@ -382,7 +388,7 @@ pub(crate) fn wire(
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
let running = running.clone();
|
||||
window.on_segment_image(move |fine| {
|
||||
window.global::<Masking>().on_segment(move |fine| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
// Both the photograph and the device come out of the session, so
|
||||
// no session is nothing to look at and nothing to look with.
|
||||
@@ -399,7 +405,7 @@ pub(crate) fn wire(
|
||||
// Set before the thread rather than by it, so there is no moment
|
||||
// in which the press has been taken and nothing on screen says so.
|
||||
// The button reads "Looking…" and goes insensitive off this.
|
||||
w.set_segmenting(true);
|
||||
w.global::<Masking>().set_segmenting(true);
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
// The only thing the button decides. Everything else about the
|
||||
@@ -433,7 +439,7 @@ pub(crate) fn wire(
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
let running = refining.clone();
|
||||
window.on_mask_refined(move |id| {
|
||||
window.global::<Masking>().on_mask_refined(move |id| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let Some(index) = session
|
||||
.borrow()
|
||||
@@ -452,7 +458,7 @@ pub(crate) fn wire(
|
||||
return;
|
||||
}
|
||||
|
||||
w.set_refining(true);
|
||||
w.global::<Masking>().set_refining(true);
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
@@ -515,8 +521,10 @@ pub(crate) fn wire(
|
||||
s.commit_gradient_drag();
|
||||
}
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.set_can_undo(session.borrow().as_ref().is_some_and(|s| s.can_undo()));
|
||||
w.set_can_redo(session.borrow().as_ref().is_some_and(|s| s.can_redo()));
|
||||
w.global::<Steps>()
|
||||
.set_can_undo(session.borrow().as_ref().is_some_and(|s| s.can_undo()));
|
||||
w.global::<Steps>()
|
||||
.set_can_redo(session.borrow().as_ref().is_some_and(|s| s.can_redo()));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -551,40 +559,42 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_mask_selected(move |id, extend| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
{
|
||||
let mut slot = session.borrow_mut();
|
||||
let Some(s) = slot.as_mut() else { return };
|
||||
if extend {
|
||||
// Control/command-click: add or remove this one layer,
|
||||
// keeping whatever else was already selected.
|
||||
s.toggle_active_mask(&id);
|
||||
} else if s.active_masks() == [id.to_string()] {
|
||||
// Clicking the sole selected layer again deselects it,
|
||||
// which is how the panel gets back to the whole
|
||||
// photograph without a separate "edit globally" control.
|
||||
// Only when it is the *only* one selected — a plain click
|
||||
// on one row of a multi-selection collapses to just that
|
||||
// row rather than clearing everything, which is the more
|
||||
// useful reading of "I clicked a specific layer".
|
||||
s.set_active_mask(None);
|
||||
} else {
|
||||
s.set_active_mask(Some(&id));
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_selected(move |id, extend| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
{
|
||||
let mut slot = session.borrow_mut();
|
||||
let Some(s) = slot.as_mut() else { return };
|
||||
if extend {
|
||||
// Control/command-click: add or remove this one layer,
|
||||
// keeping whatever else was already selected.
|
||||
s.toggle_active_mask(&id);
|
||||
} else if s.active_masks() == [id.to_string()] {
|
||||
// Clicking the sole selected layer again deselects it,
|
||||
// which is how the panel gets back to the whole
|
||||
// photograph without a separate "edit globally" control.
|
||||
// Only when it is the *only* one selected — a plain click
|
||||
// on one row of a multi-selection collapses to just that
|
||||
// row rather than clearing everything, which is the more
|
||||
// useful reading of "I clicked a specific layer".
|
||||
s.set_active_mask(None);
|
||||
} else {
|
||||
s.set_active_mask(Some(&id));
|
||||
}
|
||||
}
|
||||
}
|
||||
sync(&w, &session);
|
||||
// The scope changed, so the adjust panel below is now describing a
|
||||
// different chain.
|
||||
sync_rows(&w, &rows, &session);
|
||||
});
|
||||
sync(&w, &session);
|
||||
// The scope changed, so the adjust panel below is now describing a
|
||||
// different chain.
|
||||
sync_rows(&w, &rows, &session);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_mask_removed(move |id| {
|
||||
window.global::<Masking>().on_mask_removed(move |id| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.remove_mask(&id);
|
||||
@@ -598,7 +608,7 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_toggled(move |id, on| {
|
||||
window.global::<Masking>().on_mask_toggled(move |id, on| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_enabled(&id, on);
|
||||
@@ -611,27 +621,31 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_invert_toggled(move |id, on| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_invert(&id, on);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_invert_toggled(move |id, on| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_invert(&id, on);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_opacity_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_opacity(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_opacity_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_opacity(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
|
||||
// --- the edge treatment -------------------------------------------------
|
||||
@@ -643,14 +657,16 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_feather_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_feather(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_feather_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_feather(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
// TRACES: FR-DEV-10
|
||||
// A range mask's band, and a colour range's arc.
|
||||
@@ -667,27 +683,31 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_band_changed(move |id, lo, hi, softness| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_band(&id, lo, hi, softness);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_band_changed(move |id, lo, hi, softness| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_band(&id, lo, hi, softness);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_hue_changed(move |id, hue, width| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_hue(&id, hue, width);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_hue_changed(move |id, hue, width| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_hue(&id, hue, width);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
// The refine control. Same shape as the feather's handler, and a separate
|
||||
// one for the same reason every other mask control has its own: the
|
||||
@@ -700,53 +720,61 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_refine_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_refine(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_refine_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_refine(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_falloff_picked(move |id, index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_falloff(&id, index.max(0) as usize);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_falloff_picked(move |id, index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_falloff(&id, index.max(0) as usize);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_morphology_picked(move |id, index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_morphology(&id, index.max(0) as usize);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_morphology_picked(move |id, index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_morphology(&id, index.max(0) as usize);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_morph_radius_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_morph_radius(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
window
|
||||
.global::<Masking>()
|
||||
.on_mask_morph_radius_changed(move |id, value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_morph_radius(&id, value);
|
||||
}
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
|
||||
// --- adding layers ------------------------------------------------------
|
||||
@@ -761,7 +789,7 @@ pub(crate) fn wire(
|
||||
let redraw = redraw.clone();
|
||||
window.on_paint_began(move |x, y| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let erase = w.get_mask_tool() == 2;
|
||||
let erase = w.global::<Masking>().get_tool() == 2;
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.begin_mask_stroke(x, y, erase);
|
||||
}
|
||||
@@ -800,7 +828,7 @@ pub(crate) fn wire(
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
window.on_brush_changed(move |radius, hardness, flow| {
|
||||
window.global::<Masking>().on_brush_changed(move |radius, hardness, flow| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_brush(radius, hardness, flow);
|
||||
@@ -811,7 +839,7 @@ pub(crate) fn wire(
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
window.on_mask_tool_picked(move |_tool| {
|
||||
window.global::<Masking>().on_tool_picked(move |_tool| {
|
||||
// The tool itself lives in the interface — it arms a gesture and
|
||||
// changes no pixel — so nothing is set here. What this does is
|
||||
// re-sync, because arming the brush is what makes the parts of a
|
||||
@@ -826,7 +854,7 @@ pub(crate) fn wire(
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_mask_part_selected(move |_id, index| {
|
||||
window.global::<Masking>().on_part_selected(move |_id, index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_active_part(index.max(0) as usize);
|
||||
@@ -841,7 +869,7 @@ pub(crate) fn wire(
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_mask_part_added(move |id, join| {
|
||||
window.global::<Masking>().on_part_added(move |id, join| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.add_mask_part(&id, join.max(0) as usize);
|
||||
@@ -849,8 +877,8 @@ pub(crate) fn wire(
|
||||
// Armed, because adding an empty painted part and leaving the
|
||||
// pointer in "select" would look like the button did nothing —
|
||||
// the part covers no pixels until something is painted into it.
|
||||
if w.get_mask_tool() == 0 {
|
||||
w.set_mask_tool(1);
|
||||
if w.global::<Masking>().get_tool() == 0 {
|
||||
w.global::<Masking>().set_tool(1);
|
||||
}
|
||||
sync(&w, &session);
|
||||
sync_rows(&w, &rows, &session);
|
||||
@@ -862,7 +890,7 @@ pub(crate) fn wire(
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_mask_part_removed(move |id, index| {
|
||||
window.global::<Masking>().on_part_removed(move |id, index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.remove_mask_part(&id, index.max(0) as usize);
|
||||
@@ -876,7 +904,7 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_mask_part_join_picked(move |id, index, join| {
|
||||
window.global::<Masking>().on_part_join_picked(move |id, index, join| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_part_join(&id, index.max(0) as usize, join.max(0) as usize);
|
||||
@@ -890,7 +918,7 @@ pub(crate) fn wire(
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_add_gradient_mask(move |radial| {
|
||||
window.global::<Masking>().on_add_gradient(move |radial| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.add_gradient_mask(radial);
|
||||
@@ -906,7 +934,7 @@ pub(crate) fn wire(
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_add_range_mask(move |chromatic| {
|
||||
window.global::<Masking>().on_add_range(move |chromatic| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.add_range_mask(chromatic);
|
||||
@@ -921,7 +949,7 @@ pub(crate) fn wire(
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_add_category_mask(move |name| {
|
||||
window.global::<Masking>().on_add_category(move |name| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.add_category_mask(&name);
|
||||
@@ -937,7 +965,7 @@ pub(crate) fn wire(
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window.on_add_subject_mask(move |index| {
|
||||
window.global::<Masking>().on_add_subject(move |index| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.add_subject_mask(index.max(0) as usize);
|
||||
@@ -1012,7 +1040,7 @@ fn watch(
|
||||
Ok(None) => {}
|
||||
Err(e) => log::warn!("segmentation failed: {e}"),
|
||||
}
|
||||
w.set_segmenting(false);
|
||||
w.global::<Masking>().set_segmenting(false);
|
||||
sync(&w, &session);
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
@@ -1069,7 +1097,7 @@ fn watch_refine(
|
||||
Ok(None) => {}
|
||||
Err(e) => log::warn!("refine failed: {e}"),
|
||||
}
|
||||
w.set_refining(false);
|
||||
w.global::<Masking>().set_refining(false);
|
||||
sync(&w, &session);
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
@@ -1087,13 +1115,16 @@ fn watch_refine(
|
||||
/// follows the view mode, which `reset_view_state` returns to `photo`.
|
||||
pub(crate) fn reset(window: &AppWindow) {
|
||||
window.set_overlay_on(false);
|
||||
window.set_segmenting(false);
|
||||
window.set_refining(false);
|
||||
window.set_segmented(false);
|
||||
window.set_mask_rows(ModelRc::new(VecModel::<MaskRow>::default()));
|
||||
window.set_subject_rows(ModelRc::new(VecModel::<SubjectRow>::default()));
|
||||
window.set_category_rows(ModelRc::new(VecModel::<CategoryRow>::default()));
|
||||
window.set_adjust_scope(GLOBAL_SCOPE.into());
|
||||
let masking = window.global::<Masking>();
|
||||
masking.set_segmenting(false);
|
||||
masking.set_refining(false);
|
||||
masking.set_segmented(false);
|
||||
masking.set_masks(ModelRc::new(VecModel::<MaskRow>::default()));
|
||||
masking.set_subjects(ModelRc::new(VecModel::<SubjectRow>::default()));
|
||||
masking.set_categories(ModelRc::new(VecModel::<CategoryRow>::default()));
|
||||
window
|
||||
.global::<Adjustments>()
|
||||
.set_scope(GLOBAL_SCOPE.into());
|
||||
clear_handles(window);
|
||||
}
|
||||
|
||||
|
||||
+30
-10
@@ -40,7 +40,7 @@ use slint::ComponentHandle;
|
||||
|
||||
use crate::develop::DevelopSession;
|
||||
use crate::preset_store::PresetStore;
|
||||
use crate::{library, library_ui, settings_ui, AppWindow, ParamRow, ScopeKind};
|
||||
use crate::{library, library_ui, settings_ui, AppWindow, ParamRow, ScopeKind, Steps, Transfer};
|
||||
|
||||
/// Where the develop view's current edit is stored.
|
||||
///
|
||||
@@ -502,8 +502,8 @@ fn refresh_thumbnail_for(
|
||||
session: &Rc<RefCell<Option<DevelopSession>>>,
|
||||
library: &Rc<library_ui::LibraryController>,
|
||||
) {
|
||||
let worth_it =
|
||||
window.get_can_undo() || session.borrow().as_ref().is_some_and(|s| !s.is_neutral());
|
||||
let worth_it = window.global::<Steps>().get_can_undo()
|
||||
|| session.borrow().as_ref().is_some_and(|s| !s.is_neutral());
|
||||
if !worth_it {
|
||||
return;
|
||||
}
|
||||
@@ -557,12 +557,13 @@ pub fn render(
|
||||
settings: &Rc<settings_ui::SettingsController>,
|
||||
) {
|
||||
let scope = scope_for(&settings.snapshot());
|
||||
window.set_settings_armed(clipboard.is_armed());
|
||||
window.set_settings_summary(clipboard.describe(scope).into());
|
||||
let transfer = window.global::<Transfer>();
|
||||
transfer.set_armed(clipboard.is_armed());
|
||||
transfer.set_summary(clipboard.describe(scope).into());
|
||||
// Only worth saying when it is actually true of *this* copy: a clipboard
|
||||
// holding no crop loses nothing to the setting, and saying so anyway would
|
||||
// train the user to ignore the line.
|
||||
window.set_settings_framing_withheld(
|
||||
transfer.set_framing_withheld(
|
||||
clipboard.holds_framing() && !scope.has(dr_pipeline::Attribute::Compose),
|
||||
);
|
||||
}
|
||||
@@ -603,7 +604,7 @@ pub fn wire(
|
||||
let clipboard = clipboard.clone();
|
||||
let session = session.clone();
|
||||
let settings = settings.clone();
|
||||
window.on_copy_settings(move || {
|
||||
window.global::<Transfer>().on_copy(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow().as_ref() {
|
||||
clipboard.copy_from(s);
|
||||
@@ -622,7 +623,7 @@ pub fn wire(
|
||||
let redraw = redraw.clone();
|
||||
let open = open.clone();
|
||||
let library = library.clone();
|
||||
window.on_paste_settings(move || {
|
||||
window.global::<Transfer>().on_paste(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let Some(preset) = clipboard.peek() else {
|
||||
return;
|
||||
@@ -933,11 +934,22 @@ pub fn wire_named(
|
||||
});
|
||||
}
|
||||
|
||||
// --- what saving would capture ----------------------------------------
|
||||
// --- opening the sheet over the open photograph -----------------------
|
||||
//
|
||||
// The develop column's "Presets…" button, and everything opening the sheet
|
||||
// from there means. It used to be four lines inside the panel's
|
||||
// instantiation in `app.slint`, which is exactly the sort of behaviour a
|
||||
// second drawing of that panel would have had to copy; `Transfer` is a
|
||||
// global and a global cannot bind the window's own state, so it lands
|
||||
// here instead — where the summary it also has to compute already lived.
|
||||
//
|
||||
// The grid opens the same sheet with its own count and no capture summary,
|
||||
// still from `app.slint`: nothing there is worth saving, so `can-save` is
|
||||
// false and the summary would describe an edit that is not in hand.
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
window.on_presets_opened(move || {
|
||||
window.global::<Transfer>().on_open_presets(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
// At the scope a save would use, which is full: a preset keeps
|
||||
// the framing it was captured with and drops it at apply time.
|
||||
@@ -947,6 +959,14 @@ pub fn wire_named(
|
||||
.map(|s| describe(&s.copy_settings(), Scope::everything()))
|
||||
.unwrap_or_default();
|
||||
w.set_preset_capture_summary(summary.into());
|
||||
// Opened with no count, which is what tells the apply handler this
|
||||
// means the open photograph rather than a selection.
|
||||
w.set_preset_apply_count(0);
|
||||
// `crate::Develop` spelled out: this module has a `Develop` of its
|
||||
// own, and it is the bundle of session handles below, not the
|
||||
// interface global.
|
||||
w.set_preset_can_save(w.global::<crate::Develop>().get_enabled());
|
||||
w.set_presets_open(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ use dr_types::{
|
||||
use slint::ComponentHandle;
|
||||
|
||||
use crate::settings_store::SettingsStore;
|
||||
use crate::AppWindow;
|
||||
use crate::{Adjustments, AppWindow};
|
||||
use dr_sync::Connection;
|
||||
|
||||
/// Shared settings state for the running window.
|
||||
@@ -504,7 +504,8 @@ pub fn wire<F, G>(
|
||||
// control to the other, and without it the preference would
|
||||
// save correctly and change nothing until the next launch.
|
||||
render(&w, &ctl);
|
||||
w.set_groups_in_rail(choice.groups_in_rail(dr_plat::is_touch_first()));
|
||||
w.global::<Adjustments>()
|
||||
.set_groups_in_rail(choice.groups_in_rail(dr_plat::is_touch_first()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+28
-23
@@ -14,7 +14,7 @@ use std::rc::Rc;
|
||||
use slint::{ComponentHandle as _, Model as _, ModelRc, VecModel};
|
||||
|
||||
use crate::develop::DevelopSession;
|
||||
use crate::{AppWindow, SpotHandle};
|
||||
use crate::{AppWindow, Repair, SpotHandle, Steps};
|
||||
|
||||
/// TRACES: FR-DEV-8 | FR-UI-3
|
||||
/// Move the circles to where the repairs now are.
|
||||
@@ -51,7 +51,9 @@ pub(crate) fn sync_handles(window: &AppWindow, session: &DevelopSession) {
|
||||
}
|
||||
|
||||
window.set_spot_handles(ModelRc::from(model));
|
||||
window.set_selected_spot(session.selected_spot_id().unwrap_or_default().into());
|
||||
window
|
||||
.global::<Repair>()
|
||||
.set_selected(session.selected_spot_id().unwrap_or_default().into());
|
||||
}
|
||||
|
||||
/// The circles' model, held for the life of the process — one shared identity,
|
||||
@@ -73,8 +75,8 @@ pub(crate) fn reset(window: &AppWindow) {
|
||||
model.remove(model.row_count() - 1);
|
||||
}
|
||||
window.set_spot_handles(ModelRc::from(model));
|
||||
window.set_selected_spot(Default::default());
|
||||
window.set_spot_count(0);
|
||||
window.global::<Repair>().set_selected(Default::default());
|
||||
window.global::<Repair>().set_count(0);
|
||||
}
|
||||
|
||||
/// Install the repair tool's callbacks.
|
||||
@@ -179,7 +181,7 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.$install(move |value| {
|
||||
window.global::<Repair>().$install(move |value| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let changed = session
|
||||
.borrow_mut()
|
||||
@@ -198,18 +200,16 @@ pub(crate) fn wire(
|
||||
}};
|
||||
}
|
||||
|
||||
control!(on_spot_radius_changed, |spot: &mut dr_pipeline::Spot, v| {
|
||||
control!(on_radius_changed, |spot: &mut dr_pipeline::Spot, v| {
|
||||
spot.set_radius(v)
|
||||
});
|
||||
control!(
|
||||
on_spot_feather_changed,
|
||||
|spot: &mut dr_pipeline::Spot, v| { spot.set_feather(v) }
|
||||
);
|
||||
control!(
|
||||
on_spot_opacity_changed,
|
||||
|spot: &mut dr_pipeline::Spot, v| { spot.set_opacity(v) }
|
||||
);
|
||||
control!(on_spot_mode_picked, |spot: &mut dr_pipeline::Spot, i| {
|
||||
control!(on_feather_changed, |spot: &mut dr_pipeline::Spot, v| {
|
||||
spot.set_feather(v)
|
||||
});
|
||||
control!(on_opacity_changed, |spot: &mut dr_pipeline::Spot, v| {
|
||||
spot.set_opacity(v)
|
||||
});
|
||||
control!(on_mode_picked, |spot: &mut dr_pipeline::Spot, i| {
|
||||
spot.mode = if i == 1 {
|
||||
dr_pipeline::SpotMode::Clone
|
||||
} else {
|
||||
@@ -243,7 +243,7 @@ pub(crate) fn wire(
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.on_spot_removed(move |id| {
|
||||
window.global::<Repair>().on_removed(move |id| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let removed = session
|
||||
.borrow_mut()
|
||||
@@ -269,12 +269,13 @@ pub(crate) fn wire(
|
||||
/// change at different times: the circles move on every frame of a drag and on
|
||||
/// every pan, and these move when a repair is selected or a control is used.
|
||||
pub(crate) fn sync_panel(window: &AppWindow, session: &DevelopSession) {
|
||||
window.set_spot_count(session.spot_count() as i32);
|
||||
let repair = window.global::<Repair>();
|
||||
repair.set_count(session.spot_count() as i32);
|
||||
if let Some(spot) = session.selected_spot() {
|
||||
window.set_spot_radius(spot.radius);
|
||||
window.set_spot_feather(spot.feather);
|
||||
window.set_spot_opacity(spot.opacity);
|
||||
window.set_spot_mode(match spot.mode {
|
||||
repair.set_radius(spot.radius);
|
||||
repair.set_feather(spot.feather);
|
||||
repair.set_opacity(spot.opacity);
|
||||
repair.set_mode(match spot.mode {
|
||||
dr_pipeline::SpotMode::Heal => 0,
|
||||
dr_pipeline::SpotMode::Clone => 1,
|
||||
});
|
||||
@@ -287,6 +288,10 @@ pub(crate) fn sync_panel(window: &AppWindow, session: &DevelopSession) {
|
||||
/// disabled undo button after an edit that *is* undoable is the kind of small
|
||||
/// lie that stops people trusting the button at all.
|
||||
fn sync_undo(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSession>>>) {
|
||||
window.set_can_undo(session.borrow().as_ref().is_some_and(|s| s.can_undo()));
|
||||
window.set_can_redo(session.borrow().as_ref().is_some_and(|s| s.can_redo()));
|
||||
window
|
||||
.global::<Steps>()
|
||||
.set_can_undo(session.borrow().as_ref().is_some_and(|s| s.can_undo()));
|
||||
window
|
||||
.global::<Steps>()
|
||||
.set_can_redo(session.borrow().as_ref().is_some_and(|s| s.can_redo()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user