mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #2928 from nrq6993-a11y/stream/vc-issue-2719-wysiwyg-zoom
Fix #2719: honor Display Print Size for status-bar 100% zoom
This commit is contained in:
@@ -390,14 +390,14 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
|
||||
/// A document tab's title: "Name* @ 66.67 % (RGB/Preview)", or while an opacity mask is edited
|
||||
/// "Name* @ 66.67 % (<Opacity Mask>/Opacity Mask)".
|
||||
fn tab_title(d: &vectorcraft_engine::DocState, zoom: f64, outline: bool) -> String {
|
||||
fn tab_title(d: &vectorcraft_engine::DocState, zoom: f64, outline: bool, display_print_size: bool) -> String {
|
||||
let mode = if d.doc.mask_edit.is_some() {
|
||||
format!("<{0}>/{0}", tl!("Opacity Mask"))
|
||||
} else {
|
||||
let color = if d.doc.color_mode == vectorcraft_doc::ColorMode::Cmyk { "CMYK" } else { "RGB" };
|
||||
format!("{color}/{}", if outline { tl!("Outline") } else { tl!("Preview") })
|
||||
};
|
||||
format!("{}{} @ {} ({mode})", d.title(), if d.is_dirty() { "*" } else { "" }, zoom_label(zoom).replace('%', " %"))
|
||||
format!("{}{} @ {} ({mode})", d.title(), if d.is_dirty() { "*" } else { "" }, zoom_label(zoom, display_print_size).replace('%', " %"))
|
||||
}
|
||||
|
||||
/// Whether a document tab's × comes before its title (macOS) rather than after it (Windows,
|
||||
@@ -435,7 +435,7 @@ pub fn doc_tabs(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
.enumerate()
|
||||
.map(|(i, d)| {
|
||||
let zoom = app.views.get(i).map(|v| v.zoom).unwrap_or(1.0);
|
||||
let title = tab_title(d, zoom, app.ui.view.outline);
|
||||
let title = tab_title(d, zoom, app.ui.view.outline, app.session.prefs.display_print_size);
|
||||
let color = if Some(i) == active { t.text_strong } else { t.text_dim };
|
||||
(title.clone(), ui.painter().layout_no_wrap(title, theme::semibold(title_size), color))
|
||||
})
|
||||
@@ -653,11 +653,17 @@ pub fn status_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
let zoom = app.view().map(|v| v.zoom).unwrap_or(1.0);
|
||||
egui::ComboBox::from_id_salt("zoom-combo").selected_text(egui::RichText::new(zoom_label(zoom)).size(11.5)).width(76.0).show_ui(
|
||||
ui,
|
||||
|ui| {
|
||||
let print = app.session.prefs.display_print_size;
|
||||
let shown = crate::state::zoom_display_percent(zoom, print);
|
||||
egui::ComboBox::from_id_salt("zoom-combo")
|
||||
.selected_text(egui::RichText::new(zoom_label(zoom, print)).size(11.5))
|
||||
.width(76.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for z in ZOOM_STOPS.iter().rev() {
|
||||
if ui.selectable_label((z / 100.0 - zoom).abs() < 1e-4, zoom_label(z / 100.0)).clicked() {
|
||||
if ui
|
||||
.selectable_label((z - shown).abs() < 0.01, zoom_label(crate::state::zoom_from_display_percent(*z, print), print))
|
||||
.clicked()
|
||||
{
|
||||
app.run("view.setZoom", json!({"zoom": z})).ok();
|
||||
}
|
||||
}
|
||||
@@ -668,8 +674,7 @@ pub fn status_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if ui.selectable_label(false, tl!("Fit All")).clicked() {
|
||||
app.run("view.fitAll", json!({})).ok();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
let rot = app.view().map(|v| v.rotation).unwrap_or(0.0);
|
||||
egui::ComboBox::from_id_salt("rot-combo").selected_text(egui::RichText::new(format!("{rot:.0}°")).size(11.5)).width(52.0).show_ui(
|
||||
ui,
|
||||
@@ -1139,6 +1144,16 @@ mod tests {
|
||||
assert!(app.run("view.goToArtboard", json!({"index": "sideways"})).is_err());
|
||||
}
|
||||
|
||||
/// storytold/vectorcraft#2719: status-bar 100% matches Actual Size when Display Print Size is on.
|
||||
#[test]
|
||||
fn set_zoom_100_matches_actual_size_with_display_print_size() {
|
||||
let mut app = crate::VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
|
||||
app.session.execute("prefs.set", &json!({"key": "displayPrintSize", "value": true})).unwrap();
|
||||
app.run("view.setZoom", json!({"zoom": 100})).unwrap();
|
||||
assert!((app.view().unwrap().zoom - 96.0 / 72.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
/// Screen readers name the artboard navigator's four buttons, the ones that lead nowhere from
|
||||
/// the first artboard disabled.
|
||||
#[test]
|
||||
@@ -1413,7 +1428,7 @@ mod tests {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 50, "height": 50})).unwrap();
|
||||
let title = |s: &Session| super::tab_title(s.active().unwrap(), 1.0, false);
|
||||
let title = |s: &Session| super::tab_title(s.active().unwrap(), 1.0, false, false);
|
||||
assert!(title(&s).ends_with("(RGB/Preview)"), "{}", title(&s));
|
||||
s.execute("transparency.makeOpacityMask", &json!({})).unwrap();
|
||||
assert!(title(&s).ends_with("(<Opacity Mask>/Opacity Mask)"), "{}", title(&s));
|
||||
|
||||
@@ -708,6 +708,7 @@ impl VectorcraftApp {
|
||||
|
||||
/// [`Self::run`] it, inside what asks for file dialogs.
|
||||
fn run_now(&mut self, id: &str, params: Value) -> Result<Value, String> {
|
||||
crate::state::sync_zoom_stops_display_print_size(self.session.prefs.display_print_size);
|
||||
self.run_count = self.run_count.wrapping_add(1);
|
||||
// In debug builds the app's own calls are held to the params descriptions too (#1442): a
|
||||
// panel passing a key its command doesn't read shows up in the tests.
|
||||
@@ -1420,6 +1421,7 @@ impl VectorcraftApp {
|
||||
}
|
||||
|
||||
fn ui_frame(&mut self, ui: &mut egui::Ui) {
|
||||
crate::state::sync_zoom_stops_display_print_size(self.session.prefs.display_print_size);
|
||||
let ctx = ui.ctx().clone();
|
||||
if !self.fonts_ready {
|
||||
ctx.request_repaint();
|
||||
|
||||
@@ -1249,6 +1249,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
|
||||
"view.snapToPoint" => flag(&mut app.ui.view.snap_to_point),
|
||||
"view.zoomIn" | "view.zoomOut" => {
|
||||
let up = id == "view.zoomIn";
|
||||
let print = app.session.prefs.display_print_size;
|
||||
// Selection & Anchor Display › Zoom to Selection: the selection comes to the middle.
|
||||
let focus = if app.session.prefs.zoom_to_selection { app.selection_box().map(|b| b.center()) } else { None };
|
||||
match app.view_mut() {
|
||||
@@ -1257,13 +1258,14 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
|
||||
if let Some(c) = focus {
|
||||
v.center = c;
|
||||
}
|
||||
Ok(json!({"zoom": v.zoom * 100.0}))
|
||||
Ok(json!({"zoom": crate::state::zoom_display_percent(v.zoom, print)}))
|
||||
}
|
||||
None => Err("no document".into()),
|
||||
}
|
||||
}
|
||||
"view.setZoom" => {
|
||||
let z = p.get("zoom").and_then(Value::as_f64).unwrap_or(100.0) / 100.0;
|
||||
let print = app.session.prefs.display_print_size;
|
||||
let z = crate::state::zoom_from_display_percent(p.get("zoom").and_then(Value::as_f64).unwrap_or(100.0), print);
|
||||
let center =
|
||||
p.get("center").and_then(Value::as_array).and_then(|a| Some(vectorcraft_geom::Point::new(a.first()?.as_f64()?, a.get(1)?.as_f64()?)));
|
||||
match app.view_mut() {
|
||||
|
||||
+48
-10
@@ -62,25 +62,53 @@ pub const ROTATE_VIEW_ANGLES: [(&str, f64); 12] = [
|
||||
("-90°", -90.0),
|
||||
];
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
/// Zoom stop stepping reads this (set each UI frame and before commands) so canvas wheel zoom
|
||||
/// matches the status bar when Display Print Size at 100% Zoom is on.
|
||||
static ZOOM_STOPS_PRINT_SIZE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn sync_zoom_stops_display_print_size(v: bool) {
|
||||
ZOOM_STOPS_PRINT_SIZE.store(v, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Illustrator's preset zoom stops, in percent.
|
||||
pub const ZOOM_STOPS: [f64; 26] = [
|
||||
3.13, 4.17, 6.25, 8.33, 12.5, 16.67, 25.0, 33.33, 50.0, 66.67, 100.0, 150.0, 200.0, 300.0, 400.0, 600.0, 800.0, 1200.0, 1600.0, 2400.0, 3200.0,
|
||||
4800.0, 6400.0, 12800.0, 25600.0, 64000.0,
|
||||
];
|
||||
|
||||
/// Document zoom at 100% in the status bar: one screen point per document point, or with
|
||||
/// Preferences › General › Display Print Size at 100% Zoom, one document inch in 96 screen points.
|
||||
pub fn zoom_baseline(display_print_size: bool) -> f64 {
|
||||
if display_print_size { 96.0 / 72.0 } else { 1.0 }
|
||||
}
|
||||
|
||||
pub fn zoom_display_percent(z: f64, display_print_size: bool) -> f64 {
|
||||
z / zoom_baseline(display_print_size) * 100.0
|
||||
}
|
||||
|
||||
pub fn zoom_from_display_percent(pct: f64, display_print_size: bool) -> f64 {
|
||||
pct / 100.0 * zoom_baseline(display_print_size)
|
||||
}
|
||||
|
||||
pub fn next_zoom(z: f64, up: bool) -> f64 {
|
||||
let pct = z * 100.0;
|
||||
next_zoom_with_display(z, up, ZOOM_STOPS_PRINT_SIZE.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
pub fn next_zoom_with_display(z: f64, up: bool, display_print_size: bool) -> f64 {
|
||||
let pct = zoom_display_percent(z, display_print_size);
|
||||
let stop = if up {
|
||||
ZOOM_STOPS.iter().find(|s| **s > pct + 0.01).copied().unwrap_or(64000.0)
|
||||
} else {
|
||||
ZOOM_STOPS.iter().rev().find(|s| **s < pct - 0.01).copied().unwrap_or(3.13)
|
||||
};
|
||||
stop / 100.0
|
||||
zoom_from_display_percent(stop, display_print_size)
|
||||
}
|
||||
|
||||
/// Format like Illustrator's tab/zoom field: up to 2 decimals, no trailing zeros.
|
||||
pub fn zoom_label(z: f64) -> String {
|
||||
let s = format!("{:.2}", z * 100.0);
|
||||
pub fn zoom_label(z: f64, display_print_size: bool) -> String {
|
||||
let s = format!("{:.2}", zoom_display_percent(z, display_print_size));
|
||||
let s = s.trim_end_matches('0').trim_end_matches('.');
|
||||
format!("{s}%")
|
||||
}
|
||||
@@ -821,12 +849,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn zoom_steps() {
|
||||
assert_eq!(next_zoom(1.0, true), 1.5);
|
||||
assert!((next_zoom(1.0, false) - 0.6667).abs() < 1e-9);
|
||||
assert_eq!(next_zoom(640.0, true), 640.0);
|
||||
assert_eq!(zoom_label(0.6667), "66.67%");
|
||||
assert_eq!(zoom_label(1.0), "100%");
|
||||
assert_eq!(zoom_label(0.125), "12.5%");
|
||||
assert_eq!(next_zoom_with_display(1.0, true, false), 1.5);
|
||||
assert!((next_zoom_with_display(1.0, false, false) - 0.6667).abs() < 1e-9);
|
||||
assert_eq!(next_zoom_with_display(640.0, true, false), 640.0);
|
||||
assert_eq!(zoom_label(0.6667, false), "66.67%");
|
||||
assert_eq!(zoom_label(1.0, false), "100%");
|
||||
assert_eq!(zoom_label(0.125, false), "12.5%");
|
||||
}
|
||||
|
||||
/// storytold/vectorcraft#2719: 100% in the zoom field matches Actual Size when Display Print Size is on.
|
||||
#[test]
|
||||
fn zoom_percent_honors_display_print_size_baseline() {
|
||||
let b = zoom_baseline(true);
|
||||
assert!((zoom_from_display_percent(100.0, true) - b).abs() < 1e-12);
|
||||
assert_eq!(zoom_from_display_percent(100.0, false), 1.0);
|
||||
assert_eq!(zoom_label(b, true), "100%");
|
||||
assert_eq!(next_zoom_with_display(b, true, true), zoom_from_display_percent(150.0, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user