mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 01:50:22 -06:00
Merge pull request #5207 from dotCooCoo/fix/tabs-above-min-width
Tabs: Above Text keeps the ruler at the zoom when the panel comes out wider
This commit is contained in:
@@ -39,6 +39,8 @@ pub(crate) const COLUMN_MIN: f32 = 220.0;
|
||||
pub(crate) const COLUMN_MAX: f32 = 720.0;
|
||||
/// Width of a column collapsed to icons.
|
||||
pub(crate) const ICON_WIDTH: f32 = 38.0;
|
||||
/// The narrowest a panel other than the canvas is drawn, docked or floating.
|
||||
pub(crate) const PANEL_MIN_WIDTH: f32 = 220.0;
|
||||
|
||||
/// Docking state that lives for the session only.
|
||||
#[derive(Default)]
|
||||
@@ -1298,7 +1300,7 @@ impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
|
||||
}
|
||||
|
||||
fn limits(&self, id: &String) -> craft_ui::docking::PanelLimits {
|
||||
let min = if id == CANVAS { egui::vec2(200.0, 120.0) } else { egui::vec2(220.0, 80.0) };
|
||||
let min = if id == CANVAS { egui::vec2(200.0, 120.0) } else { egui::vec2(PANEL_MIN_WIDTH, 80.0) };
|
||||
craft_ui::docking::PanelLimits { min, ..Default::default() }
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ fn align_id(a: TabAlign) -> &'static str {
|
||||
/// The panel body's margin and the ruler's own inset: the ruler's 0 is this far right of the panel.
|
||||
const RULER_LEFT: f32 = 10.0 + 4.0;
|
||||
|
||||
/// The most ticks the ruler draws.
|
||||
const MAX_TICKS: usize = 10_000;
|
||||
|
||||
/// The largest stop position `text.tabs.set` takes, in points.
|
||||
const MAX_STOP: f64 = 100_000.0;
|
||||
|
||||
@@ -248,6 +251,27 @@ fn current_para(app: &VectorcraftApp) -> Option<(Node, ParaStyle, f64)> {
|
||||
Some((n, para, span.max(72.0)))
|
||||
}
|
||||
|
||||
/// [`above_text`], then note the active document and text `n`. A floating panel is
|
||||
/// at least [`crate::panel_docking::PANEL_MIN_WIDTH`] wide, so for narrow text at a low zoom the
|
||||
/// panel comes out wider than asked; the ruler then draws text `n` at [`screen_scale`] and runs
|
||||
/// on past the text's width.
|
||||
fn place_above_text(app: &mut VectorcraftApp, ctx: &egui::Context, n: &Node, span: f64) {
|
||||
if above_text(app, ctx, n, span).is_some() {
|
||||
set_pstate(ctx, "tabs-above", app.session.active().map(|doc| (doc.uid, n.id)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Screen points per point of text `n` at the canvas zoom, as [`above_text`] sizes the ruler:
|
||||
/// across the frame for area type, the zoom for point type.
|
||||
fn screen_scale(app: &VectorcraftApp, n: &Node, span: f64) -> Option<f64> {
|
||||
let xf = crate::canvas::Xf::new(app.canvas_rect?, app.view()?);
|
||||
let per = match &n.kind {
|
||||
NodeKind::Text(t) if matches!(t.kind, TextKind::Area { .. }) => f64::from(xf.rect_to_screen(n.geometric_bounds()?).width()) / span,
|
||||
_ => xf.zoom,
|
||||
};
|
||||
(per.is_finite() && per > 0.0).then_some(per)
|
||||
}
|
||||
|
||||
/// Position Panel Above Text: float the panel just above text `n`, as wide as its ruler needs to
|
||||
/// put 0 on the text's left edge and its marks at the canvas zoom, so each stop sits over the
|
||||
/// column it sets (#729). `span`: the ruler's length in the text's points.
|
||||
@@ -383,7 +407,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button(tl!("Above Text")).on_hover_text(tl!("Position Panel Above Text")).clicked() {
|
||||
above_text(app, ui.ctx(), &node, span);
|
||||
place_above_text(app, ui.ctx(), &node, span);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -430,7 +454,20 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let p = ui.painter_at(rect.expand(2.0));
|
||||
p.rect_filled(rect, 2.0, t.input);
|
||||
p.rect_stroke(rect, 2.0, Stroke::new(1.0, t.input_border), egui::StrokeKind::Inside);
|
||||
let scale = (w as f64 - 8.0) / span;
|
||||
// After Position Panel Above Text on this text, while the panel floats no wider than the
|
||||
// narrowest floating window, a ruler wider than the text is on screen draws the text at its
|
||||
// screen scale and runs on past its width. Any other ruler fits the span to its width.
|
||||
let above: Option<(u64, vectorcraft_doc::NodeId)> = pstate(ui.ctx(), "tabs-above");
|
||||
let narrowest = || {
|
||||
let floating = app.ui.docking.as_ref().and_then(|l| l.floating.iter().find(|f| f.contains(&"tabs".to_string())));
|
||||
floating.is_some_and(|f| f.rect[2] <= crate::panel_docking::PANEL_MIN_WIDTH + 0.5)
|
||||
};
|
||||
let scale = above
|
||||
.filter(|at| Some(*at) == app.session.active().map(|doc| (doc.uid, node.id)) && narrowest())
|
||||
.and_then(|_| screen_scale(app, &node, span))
|
||||
.filter(|per| span * per + 8.0 < w as f64 - 0.5)
|
||||
.unwrap_or((w as f64 - 8.0) / span);
|
||||
let length = span.max((w as f64 - 8.0) / scale);
|
||||
let to_x = |pt: f64| rect.left() + 4.0 + (pt * scale) as f32;
|
||||
let to_pt = |x: f32| (((x - rect.left() - 4.0) as f64) / scale).max(0.0);
|
||||
// The ruler is numbered in the General unit, with the canvas rulers' label step at its scale
|
||||
@@ -438,7 +475,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let unit = app.session.general_unit();
|
||||
let step = unit.ruler_step(scale);
|
||||
let minor = step / 10.0;
|
||||
for k in 0..=(unit.from_pt(span) / minor + 1e-6).floor() as usize {
|
||||
for k in 0..=((unit.from_pt(length) / minor + 1e-6).floor() as usize).min(MAX_TICKS) {
|
||||
let v = k as f64 * minor;
|
||||
let x = to_x(unit.to_pt(v));
|
||||
let len = match k % 10 {
|
||||
|
||||
@@ -984,6 +984,76 @@ fn delete_tab_after_the_last_stop_deletes_the_stop_the_ruler_shows_selected() {
|
||||
assert!(current(&app).unwrap().1.is_empty());
|
||||
}
|
||||
|
||||
/// Position Panel Above Text on text narrower than a floating panel can be at the zoom: the panel
|
||||
/// comes out wider than asked, and its ruler draws the text at the zoom, running on past the text,
|
||||
/// so its marks stay over the text. A ruler wider than the narrowest panel, for other text or in
|
||||
/// another document fits the span to its width as before.
|
||||
#[test]
|
||||
fn above_text_keeps_the_zoom_on_a_ruler_wider_than_asked() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 600, "height": 400})).unwrap();
|
||||
app.session.execute("text.create", &json!({"x": 40, "y": 200, "text": "a b", "area": {"width": 100, "height": 100}})).unwrap();
|
||||
app.canvas_rect = Some(egui::Rect::from_min_size(egui::pos2(50.0, 40.0), vec2(1000.0, 700.0)));
|
||||
if let Some(v) = app.view_mut() {
|
||||
v.zoom = 0.5;
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
let (node, _, span) = current(&app).unwrap();
|
||||
place_above_text(&mut app, &ctx, &node, span);
|
||||
// The panel asked for: a 50 pt ruler, less than the narrowest panel's.
|
||||
let asked = app.ui.docking.clone().unwrap().floating.iter().find(|w| w.contains(&"tabs".to_string())).unwrap().rect[2];
|
||||
assert!((asked - RULER_LEFT * 2.0 - 50.0).abs() < 0.01, "{asked}");
|
||||
// Screen points per point of text on a ruler `width` wide, from its first two labels.
|
||||
let scale = |app: &mut VectorcraftApp, width: f32| {
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(width, 400.0));
|
||||
let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), ..Default::default() }, |ui| show(app, ui));
|
||||
out.textures_delta.clear();
|
||||
let labels: Vec<(f64, f32)> = out
|
||||
.shapes
|
||||
.iter()
|
||||
.filter_map(|c| match &c.shape {
|
||||
egui::Shape::Text(t) if t.galley.job.sections.first().is_some_and(|s| s.format.font_id.size == 9.0) => {
|
||||
Some((t.galley.text().parse::<f64>().ok()?, t.pos.x))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let [(0.0, a), (next, b), ..] = labels[..] else { panic!("ruler labels {labels:?}") };
|
||||
(b - a) as f64 / next
|
||||
};
|
||||
let narrowest = crate::panel_docking::PANEL_MIN_WIDTH;
|
||||
let fitted = (f64::from(narrowest) - 8.0) / span;
|
||||
let at_zoom = scale(&mut app, narrowest);
|
||||
assert!((at_zoom - 0.5).abs() < 1e-3, "{at_zoom}");
|
||||
let other =
|
||||
app.session.execute("text.create", &json!({"x": 300, "y": 200, "text": "c", "area": {"width": 100, "height": 100}})).unwrap()["id"].clone();
|
||||
app.session.execute("select.set", &json!({"ids": [other]})).unwrap();
|
||||
assert!((scale(&mut app, narrowest) - fitted).abs() < 1e-3, "other text fits its span to the ruler");
|
||||
app.session.execute("select.set", &json!({"ids": [node.id.0]})).unwrap();
|
||||
assert!((scale(&mut app, narrowest) - 0.5).abs() < 1e-3);
|
||||
// At another zoom the ruler follows it.
|
||||
if let Some(v) = app.view_mut() {
|
||||
v.zoom = 2.0;
|
||||
}
|
||||
assert!((scale(&mut app, narrowest) - 2.0).abs() < 1e-3, "another zoom");
|
||||
if let Some(v) = app.view_mut() {
|
||||
v.zoom = 0.5;
|
||||
}
|
||||
app.session.execute("file.new", &json!({"width": 600, "height": 400})).unwrap();
|
||||
app.session.execute("text.create", &json!({"x": 40, "y": 200, "text": "d", "area": {"width": 100, "height": 100}})).unwrap();
|
||||
if let Some(v) = app.view_mut() {
|
||||
v.zoom = 0.5;
|
||||
}
|
||||
assert!((scale(&mut app, narrowest) - fitted).abs() < 1e-3, "another document");
|
||||
assert!(app.session.set_active(0));
|
||||
assert!((scale(&mut app, narrowest) - 0.5).abs() < 1e-3);
|
||||
// The panel made wider than the narrowest floating window fits the span to its ruler again.
|
||||
let docking = app.ui.docking.as_mut().unwrap();
|
||||
docking.floating.iter_mut().find(|w| w.contains(&"tabs".to_string())).unwrap().rect[2] = 400.0;
|
||||
let wide = scale(&mut app, 400.0);
|
||||
assert!((wide - (400.0 - 8.0) / span).abs() < 1e-3, "{wide}");
|
||||
}
|
||||
|
||||
/// Position Panel Above Text floats the panel over the text, its ruler's 0 on the frame's left
|
||||
/// edge and a stop's mark over the column it sets, at any zoom (#729).
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user