mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Simplify: Object › Path › Simplify opens its slider at the Auto-Simplify level for the selection
The Simplify help page says the slider starts at an auto-simplified value (https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html). The slider opened at a fixed level 50 (1 pt), whatever the size of the selection, so the first preview was the same tolerance for a 30 pt icon as for a 1000 pt drawing. The level Auto-Simplify picks for the selection is now one engine function, vectorcraft_engine::auto_simplify_level (the existing 1/400-of-the-longer- side rule in vectorcraft_pathops), which object.path.simplify {auto: true} and the slider's opening fields both use. Object › Path › Simplify (and ui.menuDialog, the Properties and contextual task bar buttons, which go through the same menu dialog) opens the slider there, and the canvas previews that result; Auto-Simplify still moves the slider back to it. The retained Simplify dialog path is unchanged. Verification: simplify_slider_opens_at_the_auto_simplify_level_of_the_selection opens the slider on 1140, 285 and 28.5 pt waves (levels 35, 55, 89) and checks the preview is exactly object.path.simplify {auto: true}'s result; without the change it fails with level 50 != 35, and simplify_opens_a_slider_whose_drag_previews_fewer_or_more_points fails with {"level": 50} != {"level": 55}. cargo xtask ci: all 10 steps passed.
This commit is contained in:
committed by
bflatastic
parent
4cd20845e0
commit
08cfb45561
@@ -55,7 +55,7 @@ mod paint;
|
||||
mod panelcmds;
|
||||
pub mod params;
|
||||
mod path;
|
||||
mod pathops;
|
||||
pub(crate) mod pathops;
|
||||
mod patterncmds;
|
||||
pub mod pdfcmds;
|
||||
pub mod perspgrid;
|
||||
|
||||
@@ -801,12 +801,19 @@ fn edit_paths(s: &mut Session, label: &str, f: impl Fn(&PathData) -> PathData) -
|
||||
Ok((ids, before, after))
|
||||
}
|
||||
|
||||
/// Auto-Simplify's level for the selection: [`po::auto_simplify_level`] for the longer side of the
|
||||
/// selected objects' bounds. Object › Path › Simplify's slider opens at it, and
|
||||
/// `object.path.simplify {auto: true}` simplifies at it.
|
||||
pub fn auto_simplify_level(s: &Session) -> Result<f64> {
|
||||
let roots = selected_roots(s)?;
|
||||
let size = s.doc()?.doc.bounds_of(&roots, false).map_or(0.0, |b| b.width().max(b.height()));
|
||||
Ok(po::auto_simplify_level(size))
|
||||
}
|
||||
|
||||
fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
// With auto, the slider's level is Auto-Simplify's for the selection's size; otherwise it is the level given.
|
||||
let level = if bool_or(p, "auto", false) {
|
||||
let roots = selected_roots(s)?;
|
||||
let size = s.doc()?.doc.bounds_of(&roots, false).map_or(0.0, |b| b.width().max(b.height()));
|
||||
Some(po::auto_simplify_level(size))
|
||||
Some(auto_simplify_level(s)?)
|
||||
} else {
|
||||
num_param(p, "level").map(|l| if l.is_finite() { l.round().clamp(0.0, 100.0) } else { po::SIMPLIFY_LEVEL })
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ pub use cmd::clipboard::Clipboard;
|
||||
pub use cmd::create::constrain_click;
|
||||
pub use cmd::distortcmds::perspective_click;
|
||||
pub use cmd::params::{check_keys, check_params};
|
||||
pub use cmd::pathops::auto_simplify_level;
|
||||
pub use cmd::rasterfx::{export_pdf, flatten_raster_effects};
|
||||
pub use cmd::{CommandInfo, CommandSpec, command_specs, find_command};
|
||||
pub use journal::Journal;
|
||||
|
||||
@@ -521,6 +521,8 @@ fn simplify_level_moves_the_anchor_count_monotonically() {
|
||||
fn auto_simplify_keeps_the_shape_within_a_400th_of_its_size() {
|
||||
let (mut s, id) = dense_wave(100, 15.0, 8.0, 0.2);
|
||||
let original = path_of(&s, id).0.clone();
|
||||
// The level the Simplify slider opens at for this selection is the one auto uses.
|
||||
assert_eq!(crate::auto_simplify_level(&s).unwrap(), 70.0);
|
||||
let out = s.execute("object.path.simplify", &json!({"auto": true})).unwrap();
|
||||
// 100 pt across: 0.25 pt at most, level 70 (0.25 pt).
|
||||
assert_eq!(out["level"], json!(70.0));
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn simplify(path: &PathData, tolerance: f64) -> PathData {
|
||||
simplify_with(path, &SimplifyOptions { tolerance, ..Default::default() })
|
||||
}
|
||||
|
||||
/// The Simplify slider's level for a tolerance of 1 pt, where the slider opens.
|
||||
/// The Simplify slider's level for a tolerance of 1 pt, the middle of its scale.
|
||||
pub const SIMPLIFY_LEVEL: f64 = 50.0;
|
||||
|
||||
/// The tolerance (points) of Simplify slider level `level`, from 0 (the fewest anchor points) to
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
//! the fewest anchor points (left) to the closest fit to the original (right), and More Options,
|
||||
//! which opens the Simplify dialog at the slider's tolerance. While the strip is open, the canvas
|
||||
//! previews `object.path.simplify {level}`; Enter or a click outside the strip keeps the result as
|
||||
//! one undo step, and Escape puts the paths back. The slider opens at level 50, a tolerance of
|
||||
//! 1 pt (`vectorcraft_pathops::simplify_level_tolerance` maps levels to tolerances).
|
||||
//! one undo step, and Escape puts the paths back. The slider opens at Auto-Simplify's level for
|
||||
//! the selection (`vectorcraft_engine::auto_simplify_level`), so the canvas first shows the
|
||||
//! auto-simplified paths, as the Simplify help page describes
|
||||
//! (https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html).
|
||||
//! `vectorcraft_pathops::simplify_level_tolerance` maps levels to tolerances.
|
||||
//!
|
||||
//! Fields: `level` (0..=100, the command's parameter).
|
||||
|
||||
use egui::{Color32, CornerRadius, Order, Pos2, Sense, Stroke, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_engine::{SIMPLIFY_LEVEL, simplify_level_tolerance};
|
||||
use vectorcraft_engine::{SIMPLIFY_LEVEL, auto_simplify_level, simplify_level_tolerance};
|
||||
use vectorcraft_geom::Rect;
|
||||
|
||||
use super::{DialogResult, DialogSpec, form, modal};
|
||||
@@ -26,9 +29,10 @@ pub(super) const SPEC: DialogSpec = DialogSpec { window: Some(show), confirm, pr
|
||||
const CMD: &str = "object.path.simplify";
|
||||
const LABEL: &str = "Simplify";
|
||||
|
||||
/// The fields the slider opens with.
|
||||
pub(crate) fn fields() -> Value {
|
||||
json!({ "level": SIMPLIFY_LEVEL })
|
||||
/// The fields the slider opens with: Auto-Simplify's level for the selection, or
|
||||
/// [`SIMPLIFY_LEVEL`] without a document.
|
||||
pub(crate) fn fields(app: &VectorcraftApp) -> Value {
|
||||
json!({ "level": auto_simplify_level(&app.session).unwrap_or(SIMPLIFY_LEVEL) })
|
||||
}
|
||||
|
||||
/// Whether the Simplify slider is open.
|
||||
|
||||
@@ -338,9 +338,9 @@ fn slider_strip(ctx: &egui::Context) -> (Rect, Pos2, Pos2, Pos2) {
|
||||
(r, Pos2::new(r.left() + 8.0 + 14.0, y), Pos2::new(r.left() + 8.0 + 28.0 + 6.0 + 90.0, y), Pos2::new(r.right() - 8.0 - 14.0, y))
|
||||
}
|
||||
|
||||
/// Object › Path › Simplify opens a slider under the selection that previews the 1 pt result.
|
||||
/// Dragging it to the right previews more anchor points and to the left fewer, and Enter keeps the
|
||||
/// result as one undo step.
|
||||
/// Object › Path › Simplify opens a slider under the selection that previews the auto-simplified
|
||||
/// result (the path is 285 pt wide: level 55, 0.7125 pt). Dragging it to the right previews more
|
||||
/// anchor points and to the left fewer, and Enter keeps the result as one undo step.
|
||||
#[test]
|
||||
fn simplify_opens_a_slider_whose_drag_previews_fewer_or_more_points() {
|
||||
let (ctx, mut app) = (ctx(), app_with_dense_path());
|
||||
@@ -348,8 +348,8 @@ fn simplify_opens_a_slider_whose_drag_previews_fewer_or_more_points() {
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(simplify_slider::KIND));
|
||||
settle(&ctx, &mut app);
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 50.0}));
|
||||
assert_eq!(anchor_count(&app), 2, "the 1 pt result");
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 55.0}));
|
||||
assert_eq!(anchor_count(&app), 2, "the auto-simplified result");
|
||||
let (_, _, rail, _) = slider_strip(&ctx);
|
||||
drag(&ctx, &mut app, rail, vec2(100.0, 0.0));
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 100.0}));
|
||||
@@ -369,6 +369,40 @@ fn simplify_opens_a_slider_whose_drag_previews_fewer_or_more_points() {
|
||||
assert_eq!(anchor_count(&app), 2);
|
||||
}
|
||||
|
||||
/// A wave of 20 anchors `width` pt wide and `width / 5` pt high, selected.
|
||||
fn app_with_wave(width: f64) -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 2000, "height": 2000})).unwrap();
|
||||
let anchors: Vec<_> = (0..20)
|
||||
.map(|i| {
|
||||
let t = i as f64 / 19.0;
|
||||
json!({"x": 30.0 + width * t, "y": 500.0 + width / 5.0 * (3.0 * std::f64::consts::PI * t).sin()})
|
||||
})
|
||||
.collect();
|
||||
app.run("path.create", json!({"anchors": anchors})).unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
/// Object › Path › Simplify opens the slider at Auto-Simplify's level for the selection's size,
|
||||
/// not at a fixed level, and the canvas previews the paths as Auto-Simplify leaves them.
|
||||
#[test]
|
||||
fn simplify_slider_opens_at_the_auto_simplify_level_of_the_selection() {
|
||||
let ctx = ctx();
|
||||
// 1/400 of the longer side: 2.85 pt (level 35), 0.7125 pt (55), 0.07125 pt (89).
|
||||
for (width, level) in [(1140.0, 35.0), (285.0, 55.0), (28.5, 89.0)] {
|
||||
let mut app = app_with_wave(width);
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().f64("level", -1.0), level, "{width} pt");
|
||||
settle(&ctx, &mut app);
|
||||
assert_eq!(simplify_preview(&app), json!({ "level": level }), "{width} pt");
|
||||
let previewed = app.session.doc().unwrap().doc.clone();
|
||||
cancel(&mut app);
|
||||
let auto = app.run("object.path.simplify", json!({"auto": true})).unwrap();
|
||||
assert_eq!(auto["level"], json!(level), "{width} pt");
|
||||
assert_eq!(app.session.doc().unwrap().doc, previewed, "{width} pt: the preview is Auto-Simplify's result");
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape, taken by the app's key handling before the dialog layer draws, closes the Simplify
|
||||
/// slider and puts the paths back as they were, with no undo step.
|
||||
#[test]
|
||||
@@ -420,15 +454,17 @@ fn simplify_slider_stays_where_it_opened_while_the_preview_changes_the_bounds()
|
||||
assert_eq!(rect_of(&ctx, egui::Id::new(("dialog", simplify_slider::KIND))), opened);
|
||||
}
|
||||
|
||||
/// Auto-Simplify moves the slider to the level that keeps the path within 1/400 of its size, and a
|
||||
/// click outside the strip keeps the result as one undo step.
|
||||
/// Auto-Simplify moves the slider back to the level that keeps the path within 1/400 of its size,
|
||||
/// and a click outside the strip keeps the result as one undo step.
|
||||
#[test]
|
||||
fn simplify_slider_auto_simplify_and_a_click_outside_keep_the_result() {
|
||||
let (ctx, mut app) = (ctx(), app_with_dense_path());
|
||||
let before = app.session.doc().unwrap().doc.clone();
|
||||
let undo_len = app.session.doc().unwrap().history.undo.len();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("level".into(), json!(100));
|
||||
settle(&ctx, &mut app);
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 100.0}));
|
||||
let (_, auto, ..) = slider_strip(&ctx);
|
||||
click(&ctx, &mut app, auto);
|
||||
// The path is 285 pt wide: 0.7125 pt at most, level 55.
|
||||
|
||||
@@ -801,7 +801,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"ui.simplifyAuto",
|
||||
"Auto-Simplify",
|
||||
"",
|
||||
"{} the open Simplify slider (Object › Path › Simplify opens it: dialog `simplifySlider` with field `level` 0..100, object.path.simplify's level, 50 as it opens, previewed on the canvas until ui.dialog.confirm keeps it as one undo step; after OK in the Simplify dialog with `retain` ticked, the menu item opens the dialog `simplify` with the fields of that OK instead) moves to the level object.path.simplify {auto: true} picks for the selection and previews it → {level}; an error when the slider isn't open",
|
||||
"{} the open Simplify slider (Object › Path › Simplify opens it: dialog `simplifySlider` with field `level` 0..100, object.path.simplify's level, opening at the level object.path.simplify {auto: true} picks for the selection, previewed on the canvas until ui.dialog.confirm keeps it as one undo step; after OK in the Simplify dialog with `retain` ticked, the menu item opens the dialog `simplify` with the fields of that OK instead) moves to the level object.path.simplify {auto: true} picks for the selection and previews it → {level}; an error when the slider isn't open",
|
||||
),
|
||||
(
|
||||
"ui.simplifyMoreOptions",
|
||||
@@ -3569,7 +3569,7 @@ fn menu_dialog(app: &VectorcraftApp, id: &str) -> Option<(&'static str, Value)>
|
||||
// Simplify opens its slider, or the dialog on the fields it was last confirmed with when
|
||||
// "Open this dialog with these settings next time" was ticked.
|
||||
"object.path.simplify" if app.ui.simplify.is_object() => ("simplify", app.ui.simplify.clone()),
|
||||
"object.path.simplify" => (crate::dialogs::simplify_slider::KIND, crate::dialogs::simplify_slider::fields()),
|
||||
"object.path.simplify" => (crate::dialogs::simplify_slider::KIND, crate::dialogs::simplify_slider::fields(app)),
|
||||
"object.path.splitIntoGrid" => {
|
||||
// The grid starts at the size of the selection.
|
||||
let size =
|
||||
|
||||
+3
-1
File diff suppressed because one or more lines are too long
+4
-3
@@ -2318,9 +2318,10 @@ whose tolerance is at most 1/400 of the selection's longer side, or level 100 wh
|
||||
```
|
||||
|
||||
In the desktop app, Object › Path › Simplify opens the slider (`ui.menuDialog {command: "object.path.simplify"}`,
|
||||
dialog `simplifySlider` with field `level`) and previews the result until `ui.dialog.confirm`. `ui.simplifyAuto` moves
|
||||
the open slider to the Auto-Simplify level, and `ui.simplifyMoreOptions` opens the Simplify dialog at the slider's
|
||||
tolerance. When OK confirms the dialog with `retain` ticked, the menu item opens the dialog on those settings next time.
|
||||
dialog `simplifySlider` with field `level`) at the Auto-Simplify level for the selection and previews the result until
|
||||
`ui.dialog.confirm`. `ui.simplifyAuto` moves the open slider back to the Auto-Simplify level, and `ui.simplifyMoreOptions`
|
||||
opens the Simplify dialog at the slider's tolerance. When OK confirms the dialog with `retain` ticked, the menu item opens
|
||||
the dialog on those settings next time.
|
||||
|
||||
## Editing a path with the Curvature tool
|
||||
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
-2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user