Fix #6419: Edit › Assign Profile… and Color Settings… scroll to the Color Settings

Both commands show the Separations Preview panel (#4542), whose Color Settings
section, with the Assign row, comes after the plates and Proof Setup. A
floated panel fits its content only up to 320 points, so the section opened
out of sight and the panel looked like the wrong one. The commands now leave a
one-shot flag that scrolls the section into view, without animation, the next
time the panel draws. Window › Separations Preview and edits made in the panel
leave the scroll alone.
This commit is contained in:
Sven Lafebre
2026-11-10 19:14:48 +01:00
parent 1d57048645
commit 64fb4d4554
3 changed files with 78 additions and 2 deletions
+6 -2
View File
@@ -206,9 +206,13 @@ fn run_ui_part2(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
}
// Edit → Color Settings… / Assign Profile… (no params): show the colour-management panel
// wherever it is, as Window › Separations Preview does (it isn't in an icon column, so a
// flyout for it never opened: #4542).
// flyout for it never opened: #4542), scrolled to its Color Settings section, which a short
// window shows only after scrolling past the plates and Proof Setup (#6419).
"edit.colorSettings" | "edit.assignProfile" if p.as_object().is_none_or(|o| o.is_empty()) => {
crate::panel_docking::reveal_panel(app, "separations").and_then(|_| app.session.execute(id, p).map_err(|e| e.to_string()))
crate::panel_docking::reveal_panel(app, "separations").and_then(|_| {
app.ui.color_settings_scroll = true;
app.session.execute(id, p).map_err(|e| e.to_string())
})
}
// Outline or Preview is a setting of the active document's view.
"view.outline" => match app.view_mut() {
+66
View File
@@ -84,6 +84,21 @@ fn profile_dropdown(ui: &mut Ui, id: &str, current: &str, kind: ProfileKind, ext
}
fn settings_section(app: &mut VectorcraftApp, ui: &mut Ui) {
let top = ui.cursor().top();
settings_rows(app, ui);
// Edit › Color Settings… / Assign Profile… opened the panel for this section: scroll it into
// view once, as the plates and Proof Setup above it fill a short window (#6419). At once, not
// animated: the panel has just opened. egui moves the scroll area on a later frame, so ask for
// one rather than wait for the pointer to move.
if app.ui.color_settings_scroll && !ui.is_sizing_pass() {
let rect = egui::Rect::from_x_y_ranges(ui.max_rect().x_range(), top..=ui.min_rect().bottom().max(top));
ui.scroll_to_rect_animation(rect, Some(egui::Align::TOP), egui::style::ScrollAnimation::none());
ui.ctx().request_repaint();
app.ui.color_settings_scroll = false;
}
}
fn settings_rows(app: &mut VectorcraftApp, ui: &mut Ui) {
let st = cms::active_settings();
widgets::section_header(ui, tl!("Color Settings"));
ui.horizontal(|ui| {
@@ -215,4 +230,55 @@ mod tests {
assert!(panel.iter().any(|(id, _)| id == "tritanopia"));
assert!(panel.iter().all(|(id, _)| cms::ProofTarget::parse(id).is_some_and(|t| t.id() == *id)));
}
/// #6419: Edit › Assign Profile… and Color Settings… show the panel scrolled to its Color
/// Settings section, which a window shorter than the panel (a floated panel stops at 320
/// points) otherwise shows only after scrolling past the plates and Proof Setup. Once: a
/// scroll away afterwards stays.
#[test]
fn color_settings_and_assign_profile_scroll_the_color_settings_into_view() {
const VIEW: f32 = 120.0;
fn find(s: &egui::Shape, text: &str) -> Option<f32> {
match s {
egui::Shape::Text(t) if t.galley.text() == text => Some(t.pos.y),
egui::Shape::Vec(v) => v.iter().find_map(|s| find(s, text)),
_ => None,
}
}
// One frame of the panel in a scroll area VIEW points high, scrolled to `offset` when
// given: where the section's Black Point Compensation box shows in the view.
fn frame(ctx: &egui::Context, app: &mut VectorcraftApp, offset: Option<f32>) -> f32 {
let mut top = 0.0;
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
top = ui.cursor().top();
let mut area = egui::ScrollArea::vertical().id_salt("separations").max_height(VIEW);
if let Some(y) = offset {
area = area.vertical_scroll_offset(y);
}
area.show(ui, |ui| show(app, ui));
});
out.textures_delta.clear();
out.shapes.iter().find_map(|c| find(&c.shape, "Use Black Point Compensation")).unwrap() - top
}
let visible = |y: f32| (0.0..VIEW).contains(&y);
for id in ["edit.assignProfile", "edit.colorSettings"] {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({})).unwrap();
let ctx = egui::Context::default();
let before = frame(&ctx, &mut app, None);
assert!(!visible(before), "the section starts below the view: {before}");
app.run(id, json!({})).unwrap();
frame(&ctx, &mut app, None);
assert!(!app.ui.color_settings_scroll);
// egui moves a scroll area on the frame after the request and paints it moved on the
// one after that; both ask for a repaint.
assert!(ctx.has_requested_repaint());
frame(&ctx, &mut app, None);
assert!(ctx.has_requested_repaint());
let shown = frame(&ctx, &mut app, None);
assert!(visible(shown), "{id} scrolls the section into the view: {shown}");
frame(&ctx, &mut app, Some(0.0));
assert_eq!(frame(&ctx, &mut app, None), before, "scrolled back to the top, it stays there");
}
}
}
+6
View File
@@ -392,6 +392,11 @@ pub struct UiState {
/// That popup's first text field is still to take the keyboard.
#[serde(skip)]
pub transient_focus: bool,
/// The Separations Preview panel is still to scroll its Color Settings section into view:
/// Edit › Color Settings… and Assign Profile… show the panel for it, and the section sits below
/// the plates and Proof Setup, out of sight in a short window (#6419).
#[serde(skip)]
pub color_settings_scroll: bool,
/// Type › Find Font…'s last Find In choice (`find_font::FIND_IN`), kept while the app runs.
#[serde(skip)]
pub find_font_in: String,
@@ -1009,6 +1014,7 @@ impl Default for UiState {
open_panel: None,
transient_panel: None,
transient_focus: false,
color_settings_scroll: false,
find_font_in: String::new(),
control_bar: false,
control_bar_bottom: false,