Merge pull request #916 from pravdomil/claude/composition-maximize-zoom-fit-86627e
Viewer: Fit up to 100% follows the panel size # Conflicts: # crates/ui-egui/src/panels/viewer_tools.rs # crates/ui-egui/tests/ui_viewer.rs
This commit is contained in:
commit
2ea7220e49
|
|
@ -561,6 +561,7 @@ pub fn invoke(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, params: V
|
|||
}
|
||||
"view.fit" => {
|
||||
app.ui.viewer.zoom = None;
|
||||
app.ui.viewer.fit_up_to_100 = false;
|
||||
app.ui.viewer.pan = [0.0, 0.0];
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,10 +122,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
// Bottom bar: magnification and View menus.
|
||||
p.rect_filled(bar, 0.0, t.panel_bg);
|
||||
let mr = Rect::from_min_size(pos2(bar.min.x + 8.0, bar.center().y - 10.0), vec2(80.0, 20.0));
|
||||
if let Some(z) =
|
||||
super::viewer_tools::magnification_menu(app, ui, mr, "layer-mag", "layerPanel.magnification", "layerMagnification", zoom as f32, app.ui.layer_zoom, fit)
|
||||
if let Some((z, up)) =
|
||||
super::viewer_tools::magnification_menu(app, ui, mr, "layer-mag", "layerPanel.magnification", "layerMagnification", zoom as f32, app.ui.layer_zoom, false)
|
||||
{
|
||||
app.ui.layer_zoom = z;
|
||||
// The Layer panel keeps no fit mode: Fit up to 100% is the fit, at most 100 %.
|
||||
app.ui.layer_zoom = if up { Some(fit.min(1.0 / ui.ctx().pixels_per_point())) } else { z };
|
||||
app.ui.layer_pan = [0.0, 0.0];
|
||||
}
|
||||
let vx = mr.max.x + 10.0;
|
||||
|
|
|
|||
|
|
@ -795,6 +795,8 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
|
||||
let (cw, ch) = (comp.width as f32, comp.height as f32);
|
||||
let fit = ((area.width() - 40.0) / cw).min((area.height() - 40.0) / ch).max(0.01);
|
||||
// Fit up to 100% follows the panel's size like Fit does, and stops at 100%.
|
||||
let fit = if app.ui.viewer.fit_up_to_100 { fit.min(1.0 / ctx.pixels_per_point()) } else { fit };
|
||||
ctx.data_mut(|d| d.insert_temp(egui::Id::new("viewer-fit"), fit));
|
||||
let zoom = app.ui.viewer.zoom.unwrap_or(fit);
|
||||
let center = area.center() + vec2(app.ui.viewer.pan[0], app.ui.viewer.pan[1]);
|
||||
|
|
@ -1952,7 +1954,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
|
||||
vt::draw_snap(&ctx, &painter, &map, &ectx);
|
||||
vt::rulers(app, ui, &map, outer, area);
|
||||
vt::bottom_bar(app, ui, bar, zoom, fit, time, &comp);
|
||||
vt::bottom_bar(app, ui, bar, zoom, time, &comp);
|
||||
if err_h > 0.0 {
|
||||
super::expr_bar::draw(app, ui, err_bar, cid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -523,8 +523,8 @@ pub(crate) fn magnification_menu(
|
|||
auto_items: &str,
|
||||
zoom: f32,
|
||||
current: Option<f32>,
|
||||
fit: f32,
|
||||
) -> Option<Option<f32>> {
|
||||
fit_up: bool,
|
||||
) -> Option<(Option<f32>, bool)> {
|
||||
let t = app.tokens;
|
||||
let ppp = ui.ctx().pixels_per_point();
|
||||
let pop = format!("{id}-pop");
|
||||
|
|
@ -540,17 +540,20 @@ pub(crate) fn magnification_menu(
|
|||
.iter()
|
||||
.map(|m| {
|
||||
let on = match *m {
|
||||
"Fit" => current.is_none(),
|
||||
"Fit" => current.is_none() && !fit_up,
|
||||
"Fit up to 100%" => current.is_none() && fit_up,
|
||||
s => s.trim_end_matches('%').parse::<f32>().is_ok_and(|v| current.is_some() && (v - cur).abs() < 0.06),
|
||||
};
|
||||
(m.to_string(), on)
|
||||
})
|
||||
.collect();
|
||||
let i = popup(app, ui, &pop, r, &items, auto_items)?;
|
||||
// `(zoom, fit up to 100 %)`: Fit and Fit up to 100% leave the zoom unset (it follows the
|
||||
// panel size).
|
||||
match *MAGS.get(i)? {
|
||||
"Fit" => Some(None),
|
||||
"Fit up to 100%" => Some(Some(fit.min(1.0 / ppp))),
|
||||
s => s.trim_end_matches('%').parse::<f32>().ok().map(|v| Some(v / 100.0 / ppp)),
|
||||
"Fit" => Some((None, false)),
|
||||
"Fit up to 100%" => Some((None, true)),
|
||||
s => s.trim_end_matches('%').parse::<f32>().ok().map(|v| (Some(v / 100.0 / ppp), false)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -583,7 +586,7 @@ pub(crate) fn resolution_label(res: Resolution, scale: f64) -> String {
|
|||
/// transparency grid, mask and shape path visibility, region of interest, grid and guide
|
||||
/// options, show channel, reset exposure, exposure, take / show snapshot, Fast Previews, the 3D
|
||||
/// renderer and view, and the current time.
|
||||
pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, zoom: f32, fit: f32, time: Tick, comp: &Comp) {
|
||||
pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, zoom: f32, time: Tick, comp: &Comp) {
|
||||
let t = app.tokens;
|
||||
let p = ui.painter().clone();
|
||||
p.rect_filled(bar, 0.0, t.panel_bg);
|
||||
|
|
@ -599,7 +602,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect,
|
|||
egui::ScrollArea::horizontal().id_salt("viewer-controls").auto_shrink([false, false]).show(ui, |ui| {
|
||||
let row = Rect::from_min_size(ui.max_rect().min, vec2(bar.width(), 30.0));
|
||||
let first_control = app.auto.elements.len();
|
||||
let width = bottom_bar_contents(app, ui, row, zoom, fit, time, comp);
|
||||
let width = bottom_bar_contents(app, ui, row, zoom, time, comp);
|
||||
ui.allocate_rect(Rect::from_min_size(row.min, vec2(width, row.height())), Sense::hover());
|
||||
if let Some(focused) = ui.ctx().memory(|memory| memory.focused())
|
||||
&& app.auto.elements.get(first_control..).is_some_and(|controls| controls.iter().any(|control| control.response_id == Some(focused)))
|
||||
|
|
@ -612,7 +615,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect,
|
|||
});
|
||||
}
|
||||
|
||||
fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, zoom: f32, fit: f32, time: Tick, comp: &Comp) -> f32 {
|
||||
fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, zoom: f32, time: Tick, comp: &Comp) -> f32 {
|
||||
let t = app.tokens;
|
||||
let p = ui.painter().clone();
|
||||
let cy = bar.center().y;
|
||||
|
|
@ -621,8 +624,10 @@ fn bottom_bar_contents(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, z
|
|||
|
||||
// Magnification.
|
||||
let r = Rect::from_min_size(pos2(x, cy - 10.0), vec2(80.0, 20.0));
|
||||
if let Some(z) = magnification_menu(app, ui, r, "vw-mag", "viewer.magnification", "magnification", zoom, app.ui.viewer.zoom, fit) {
|
||||
let fit_up = app.ui.viewer.fit_up_to_100;
|
||||
if let Some((z, up)) = magnification_menu(app, ui, r, "vw-mag", "viewer.magnification", "magnification", zoom, app.ui.viewer.zoom, fit_up) {
|
||||
app.ui.viewer.pan = [0.0, 0.0];
|
||||
app.ui.viewer.fit_up_to_100 = up;
|
||||
app.ui.viewer.zoom = z;
|
||||
}
|
||||
x = r.max.x + 4.0;
|
||||
|
|
|
|||
|
|
@ -233,6 +233,8 @@ pub use effectcraft_engine::project::Resolution;
|
|||
pub struct ViewerState {
|
||||
/// Magnification (1 = 100%); None = fit.
|
||||
pub zoom: Option<f32>,
|
||||
/// Fit up to 100%: while fitting (`zoom` is None), the comp is never magnified past 100%.
|
||||
pub fit_up_to_100: bool,
|
||||
/// Pan offset in points from the centred position.
|
||||
pub pan: [f32; 2],
|
||||
pub transparency_grid: bool,
|
||||
|
|
@ -272,6 +274,7 @@ impl Default for ViewerState {
|
|||
fn default() -> Self {
|
||||
ViewerState {
|
||||
zoom: None,
|
||||
fit_up_to_100: false,
|
||||
pan: [0.0, 0.0],
|
||||
transparency_grid: false,
|
||||
show_masks: true,
|
||||
|
|
|
|||
|
|
@ -2046,3 +2046,45 @@ fn frame_sits_on_the_physical_pixel_grid() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Magnification ▸ Fit up to 100% keeps fitting as the panel changes size (Maximize Panel), like
|
||||
/// Fit, and never magnifies past 100%.
|
||||
#[test]
|
||||
fn fit_up_to_100_follows_the_maximized_panel() {
|
||||
use effectcraft_ui_egui::dock::PanelKind;
|
||||
let pick = |h: &mut Harness<'_, EffectcraftApp>| {
|
||||
let at = rect(h, "viewer.magnification").center();
|
||||
click(h, at);
|
||||
let at = rect(h, "viewer.magnification.1").center();
|
||||
click(h, at);
|
||||
assert_eq!(h.state().ui.viewer.zoom, None, "a fit mode, not a fixed magnification");
|
||||
assert!(h.state().ui.viewer.fit_up_to_100);
|
||||
h.run_steps(2);
|
||||
};
|
||||
// A comp larger than the panel: it fills the docked panel, then the maximized one.
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Big", "width": 3840, "height": 2160, "duration": 4})).unwrap();
|
||||
EffectcraftApp::new(s)
|
||||
});
|
||||
h.run_steps(3);
|
||||
pick(&mut h);
|
||||
let docked = rect(&h, "viewer.comp").width();
|
||||
h.state_mut().toggle_maximize(PanelKind::Composition);
|
||||
h.run_steps(3);
|
||||
let area = rect(&h, "viewer.area");
|
||||
let maximized = rect(&h, "viewer.comp");
|
||||
assert!(maximized.width() > docked + 100.0, "{docked} -> {}", maximized.width());
|
||||
assert!(area.contains_rect(maximized), "{maximized:?} in {area:?}");
|
||||
h.state_mut().toggle_maximize(PanelKind::Composition);
|
||||
h.run_steps(3);
|
||||
assert_eq!(rect(&h, "viewer.comp").width(), docked, "and back when the panel is restored");
|
||||
// A comp smaller than the panel stops at 100%.
|
||||
let mut h = harness();
|
||||
pick(&mut h);
|
||||
let ppp = h.ctx.pixels_per_point();
|
||||
assert!(rect(&h, "viewer.comp").width() <= 640.0 / ppp + 0.01);
|
||||
h.state_mut().toggle_maximize(PanelKind::Composition);
|
||||
h.run_steps(3);
|
||||
assert!((rect(&h, "viewer.comp").width() - 640.0 / ppp).abs() < 0.01);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue