mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 20:36:02 +00:00
Merge pull request #7148 from dotCooCoo/fix/blend-without-spine-widget
Blend: a blend without a spine shows the Appearance widget
This commit is contained in:
@@ -1381,6 +1381,14 @@ pub fn blend_spine(keys: &[Arc<Node>], spec: &BlendSpec) -> Option<(PathData, Op
|
||||
layout_spine(keys, spec)
|
||||
}
|
||||
|
||||
/// The line the steps of a blend of `keys` are placed along: [`blend_spine`]'s path for a blend
|
||||
/// with a spine, and the straight lines between the key centers, in key order, for a blend made
|
||||
/// without one ([`BlendSpec::no_spine`]). For a blend without a spine the line places the steps
|
||||
/// only; it is not a spine and is not drawn as one. `None` with fewer than two keys.
|
||||
pub fn blend_layout_line(keys: &[Arc<Node>], spec: &BlendSpec) -> Option<PathData> {
|
||||
layout_spine(keys, spec).map(|(path, _)| path)
|
||||
}
|
||||
|
||||
/// The spine `spec` stores, unless the blend was made without one.
|
||||
fn stored_spine(spec: &BlendSpec) -> Option<&PathData> {
|
||||
spec.spine.as_ref().filter(|_| !spec.no_spine)
|
||||
@@ -1852,6 +1860,28 @@ mod tests {
|
||||
assert_eq!(moved.subpaths[0].anchors[2].p, Point::new(305.0, 5.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_blend_without_a_spine_has_a_layout_line_through_its_key_centers() {
|
||||
let at =
|
||||
|x: f64, y: f64| Arc::new(Node::path(NodeId(1), shapes::rectangle(Rect::new(x - 5.0, y - 5.0, x + 5.0, y + 5.0)), Appearance::default()));
|
||||
let line_of = |path: &PathData| path.subpaths.iter().flat_map(|s| s.anchors.iter().map(|a| a.p)).collect::<Vec<_>>();
|
||||
// A stored spine is ignored once the blend is made without one.
|
||||
let stored = PathData::single(SubPath::polyline(&[Point::new(0.0, 50.0), Point::new(300.0, 50.0)], false));
|
||||
let spec = BlendSpec { no_spine: true, spine: Some(stored), ..Default::default() };
|
||||
for keys in [vec![at(10.0, 20.0), at(110.0, 20.0)], vec![at(10.0, 20.0), at(110.0, 20.0), at(110.0, 220.0), at(-40.0, 0.0)]] {
|
||||
assert!(blend_spine(&keys, &spec).is_none(), "still no spine");
|
||||
let line = blend_layout_line(&keys, &spec).unwrap();
|
||||
assert_eq!(line.subpaths.len(), 1);
|
||||
assert!(!line.subpaths[0].closed);
|
||||
let centers: Vec<Point> = keys.iter().map(|k| center_of(k)).collect();
|
||||
assert_eq!(line_of(&line), centers, "in key order");
|
||||
}
|
||||
// With a spine, the layout line is the spine.
|
||||
let keys = vec![key(0.0), key(100.0), key(300.0)];
|
||||
assert_eq!(blend_layout_line(&keys, &BlendSpec::default()), blend_spine(&keys, &BlendSpec::default()).map(|(p, _)| p));
|
||||
assert!(blend_layout_line(&keys[..1], &spec).is_none(), "one key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn steps_follow_a_bent_spine() {
|
||||
let keys = vec![key(0.0), key(100.0)];
|
||||
|
||||
@@ -39,7 +39,7 @@ fn fifty() -> f64 {
|
||||
// =====================================================================================
|
||||
|
||||
pub use crate::blend::{
|
||||
Spine, blend_expand, blend_spine, blend_step_count, lerp_appearance, lerp_color, lerp_node, lerp_paint, lerp_path, pin_spine,
|
||||
Spine, blend_expand, blend_layout_line, blend_spine, blend_step_count, lerp_appearance, lerp_color, lerp_node, lerp_paint, lerp_path, pin_spine,
|
||||
steps_knockout_shows,
|
||||
};
|
||||
|
||||
|
||||
@@ -542,6 +542,47 @@ fn dragging_a_blend_widget_is_one_undo_step_and_a_click_or_escape_none() {
|
||||
assert_eq!(spec(&s, g).1.color_shift, 0.0);
|
||||
}
|
||||
|
||||
/// A blend made without a spine has the Appearance widget alone: a drag of it with the Selection
|
||||
/// tool or the Blend tool changes the color shift in one undo step, and there is no Easing widget
|
||||
/// to drag.
|
||||
#[test]
|
||||
fn dragging_the_appearance_widget_of_a_blend_without_a_spine_is_one_undo_step() {
|
||||
let mut s = session();
|
||||
let (a, b) = (rect(&mut s, 0.0, 0.0, 40.0), rect(&mut s, 200.0, 0.0, 40.0));
|
||||
sel(&mut s, &[a, b]);
|
||||
let g = id_of(&s.execute("object.blend.make", &json!({"steps": 3, "createSpine": "never", "orientation": "path"})).unwrap());
|
||||
assert!(spec(&s, g).1.no_spine);
|
||||
let v = ViewInfo::default();
|
||||
let depth = |s: &Session| s.doc().unwrap().history.undo.len();
|
||||
let ptr = |s: &mut Session, kind: PointerKind, x: f64, y: f64| {
|
||||
s.pointer(&PointerEvent::new(kind, x, y), ViewInfo::default()).unwrap();
|
||||
};
|
||||
for (n, tool) in [(1.0, "selection"), (2.0, "blend")] {
|
||||
s.select_tool(tool, v).unwrap();
|
||||
sel(&mut s, &[g]);
|
||||
let d0 = depth(&s);
|
||||
// The Appearance widget sits 16 px above the halfway point (120, 20) of the line between
|
||||
// the key centers.
|
||||
ptr(&mut s, PointerKind::Down, 120.0, 4.0);
|
||||
ptr(&mut s, PointerKind::Drag, 135.0, 4.0);
|
||||
ptr(&mut s, PointerKind::Up, 135.0, 4.0);
|
||||
let sp = spec(&s, g).1;
|
||||
assert_eq!((sp.color_shift, sp.ease.ramp, depth(&s)), (15.0 * n, 0.0, d0 + 1), "{tool}");
|
||||
assert_eq!(s.doc().unwrap().selection.objects, vec![g], "{tool}: the blend stays selected");
|
||||
}
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(spec(&s, g).1.color_shift, 15.0, "each drag is its own undo step");
|
||||
// 16 px below there is no widget: a drag there leaves the easing and the color shift alone.
|
||||
s.select_tool("selection", v).unwrap();
|
||||
sel(&mut s, &[g]);
|
||||
let before = spec(&s, g).1;
|
||||
ptr(&mut s, PointerKind::Down, 120.0, 36.0);
|
||||
ptr(&mut s, PointerKind::Drag, 150.0, 36.0);
|
||||
ptr(&mut s, PointerKind::Up, 150.0, 36.0);
|
||||
let after = spec(&s, g).1;
|
||||
assert_eq!((after.ease, after.color_shift), (before.ease, before.color_shift), "no Easing widget");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_blend_easing_values_clamp_and_non_finite_become_default() {
|
||||
for (bad, ramp, shift) in [
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
//! the canvas draws from the halfway point to the widget grows with it. The upper one sets the
|
||||
//! blend's color shift (`colorShift`, -100 to 100) and the lower one its easing strength (`ramp`,
|
||||
//! 0 to 100). The lower one shows only when the steps follow a spine (a replaced or edited spine,
|
||||
//! or Align to Path), where easing moves them; a blend made without a spine shows neither. A drag along the spine's direction at the press
|
||||
//! changes the value by one per screen pixel, starting from the value at the press, and the arrow
|
||||
//! or Align to Path), where easing moves them. A blend made without a spine shows the upper one
|
||||
//! alone, placed in the same way against the straight lines between its key centers that its steps
|
||||
//! are laid out along ([`blend_layout_line`]); the canvas does not draw those lines. A drag along
|
||||
//! the spine's direction at the press changes the value by one per screen pixel, starting from the
|
||||
//! value at the press, and the arrow
|
||||
//! keys during the drag change it by one (Shift: by ten); Escape or a switch to another tool
|
||||
//! cancels the drag and puts the value back. Each drag previews
|
||||
//! `object.blend.setEasing` and is one undo step, or none when the value ends where it started; a
|
||||
@@ -14,7 +17,7 @@
|
||||
//! from the same geometry.
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::live::{Spine, blend_spine};
|
||||
use vectorcraft_doc::live::{Spine, blend_layout_line};
|
||||
use vectorcraft_doc::{Document, Node, NodeId, NodeKind, Selection};
|
||||
use vectorcraft_geom::{Point, Rect, Vec2};
|
||||
|
||||
@@ -69,7 +72,8 @@ impl BlendControl {
|
||||
pub struct BlendWidget {
|
||||
pub id: NodeId,
|
||||
pub control: BlendControl,
|
||||
/// The spine's halfway point, in the document.
|
||||
/// The spine's halfway point, in the document (for a blend without a spine, the halfway point of
|
||||
/// the line its steps are laid out along).
|
||||
pub on: Point,
|
||||
/// The widget's center in the document: [`OFFSET_PX`] from `on`, or further out where other
|
||||
/// artwork crowds that spot.
|
||||
@@ -84,9 +88,10 @@ impl BlendWidget {
|
||||
/// The widgets of the selection at `zoom` (screen px per document point), with `up` the
|
||||
/// document direction of the screen's up: the Appearance widget, then the Easing widget. Empty
|
||||
/// unless the selection is one whole blend, visible and unlocked, with two or more keys and a
|
||||
/// spine of some length, and no anchors are selected. Each widget sits at the first spot out
|
||||
/// from the spine, in [`OFFSET_PX`] steps, that the boxes of fewer than three other visible
|
||||
/// objects hold, at most nine steps out; where every spot is crowded it stays at the first.
|
||||
/// spine (or, without one, a step layout line) of some length, and no anchors are selected.
|
||||
/// Each widget sits at the first spot out from the spine, in [`OFFSET_PX`] steps, that the
|
||||
/// boxes of fewer than three other visible objects hold, at most nine steps out; where every
|
||||
/// spot is crowded it stays at the first.
|
||||
pub fn of(doc: &Document, selection: &Selection, zoom: f64, up: Vec2) -> Vec<Self> {
|
||||
let up_len = up.hypot();
|
||||
let usable = |v: f64| v.is_finite() && v > 0.0;
|
||||
@@ -98,7 +103,7 @@ impl BlendWidget {
|
||||
if children.len() < 2 || !doc.is_editable(*id) {
|
||||
return vec![];
|
||||
}
|
||||
let Some(spine) = blend_spine(children, spec).and_then(|(path, _)| Spine::new(&path)) else { return vec![] };
|
||||
let Some(spine) = blend_layout_line(children, spec).and_then(|path| Spine::new(&path)) else { return vec![] };
|
||||
if !usable(spine.length()) {
|
||||
return vec![];
|
||||
}
|
||||
@@ -430,9 +435,60 @@ mod tests {
|
||||
// One key.
|
||||
let (d, _, s) = blend_doc(&[(100.0, 200.0)], eased());
|
||||
assert!(BlendWidget::of(&d, &s, 1.0, UP).is_empty(), "one key");
|
||||
// A blend made without a spine (Create Spine › Never), Align to Path included.
|
||||
let (d, _, s) = blend_doc(&[(100.0, 200.0), (300.0, 200.0)], BlendSpec { no_spine: true, ..eased() });
|
||||
assert!(BlendWidget::of(&d, &s, 1.0, UP).is_empty(), "no spine");
|
||||
// Without a spine: locked or hidden, or keys on one spot.
|
||||
let (d, id, s) = blend_doc(&[(100.0, 200.0), (300.0, 200.0)], BlendSpec { no_spine: true, ..eased() });
|
||||
for hide in [false, true] {
|
||||
let mut d = d.clone();
|
||||
let n = d.node_mut(id).unwrap();
|
||||
(n.visible, n.locked) = (!hide, !hide);
|
||||
assert!(BlendWidget::of(&d, &s, 1.0, UP).is_empty(), "no spine, hidden: {hide}");
|
||||
}
|
||||
let (d, _, s) = blend_doc(&[(100.0, 200.0), (100.0, 200.0)], BlendSpec { no_spine: true, ..eased() });
|
||||
assert!(BlendWidget::of(&d, &s, 1.0, UP).is_empty(), "no spine, no length");
|
||||
}
|
||||
|
||||
/// A blend made without a spine (Create Spine › Never), Align to Path included, shows the
|
||||
/// Appearance widget alone, at the halfway point by length of the straight lines between its
|
||||
/// key centers.
|
||||
#[test]
|
||||
fn a_blend_without_a_spine_shows_the_appearance_widget_alone() {
|
||||
let no_spine = || BlendSpec { no_spine: true, ..eased() };
|
||||
let (d, id, s) = blend_doc(&[(100.0, 200.0), (300.0, 200.0)], no_spine());
|
||||
let w = BlendWidget::of(&d, &s, 1.0, UP);
|
||||
assert_eq!(w.iter().map(|w| w.control).collect::<Vec<_>>(), [BlendControl::Appearance], "{w:?}");
|
||||
assert!(w[0].id == id && near(w[0].on, Point::new(200.0, 200.0)) && near(w[0].at, Point::new(200.0, 184.0)), "{w:?}");
|
||||
assert_eq!(w[0].value, -20.0);
|
||||
// Halfway along by length, not the middle key: (0, 0), (100, 0), (100, 300) is 400 long.
|
||||
let (d, _, s) = blend_doc(&[(0.0, 0.0), (100.0, 0.0), (100.0, 300.0)], no_spine());
|
||||
let w = BlendWidget::of(&d, &s, 1.0, UP);
|
||||
assert!(w.len() == 1 && near(w[0].on, Point::new(100.0, 100.0)), "{w:?}");
|
||||
// There the line runs down, so a drag down adds.
|
||||
assert!(near(Point::new(w[0].along.x, w[0].along.y), Point::new(0.0, 1.0)), "{w:?}");
|
||||
// Two keys on one spot and a third away: the line still has length.
|
||||
let (d, _, s) = blend_doc(&[(100.0, 200.0), (100.0, 200.0), (300.0, 200.0)], no_spine());
|
||||
let w = BlendWidget::of(&d, &s, 1.0, UP);
|
||||
assert!(w.len() == 1 && near(w[0].on, Point::new(200.0, 200.0)), "{w:?}");
|
||||
}
|
||||
|
||||
/// With the Selection or the Blend tool, a drag of a spineless blend's Appearance widget
|
||||
/// previews the color shift alone, in one undo step.
|
||||
#[test]
|
||||
fn dragging_the_appearance_widget_of_a_blend_without_a_spine() {
|
||||
let (d, id, s) = blend_doc(&[(100.0, 200.0), (300.0, 200.0)], BlendSpec { no_spine: true, ..eased() });
|
||||
let p = paint();
|
||||
let c = cx(&d, &s, &p);
|
||||
let at = Point::new(200.0, 184.0);
|
||||
assert!(over_widget(&c, at) && !over_widget(&c, Point::new(200.0, 216.0)), "no Easing widget below");
|
||||
let tools: [Box<dyn Tool>; 2] = [Box::new(SelectionTool::default()), Box::new(crate::meshblend::BlendTool::default())];
|
||||
for mut t in tools {
|
||||
assert!(t.pointer(&c, &ev(PointerKind::Down, at)).is_empty());
|
||||
let a = t.pointer(&c, &ev(PointerKind::Drag, at + Vec2::new(30.0, 0.0)));
|
||||
assert_eq!(
|
||||
a,
|
||||
vec![Action::Begin(LABEL.into()), Action::Preview("object.blend.setEasing".into(), json!({"id": id.0, "colorShift": 10.0}))]
|
||||
);
|
||||
assert_eq!(t.pointer(&c, &ev(PointerKind::Up, at + Vec2::new(30.0, 0.0))), vec![Action::Commit]);
|
||||
}
|
||||
}
|
||||
|
||||
/// A drag of the Easing widget along the spine changes the ramp by one per pixel from its
|
||||
|
||||
@@ -252,7 +252,9 @@ pub(super) fn selection_overlay(app: &mut VectorcraftApp, p: &egui::Painter, xf:
|
||||
|
||||
/// The Appearance and Easing widgets of one selected blend (Selection and Blend tools), each joined
|
||||
/// by a line to the spine's halfway point: the Appearance widget hollow above it, the Easing widget
|
||||
/// filled below it. Drawn with the edges hidden too, as they are controls.
|
||||
/// filled below it. For a blend without a spine the line goes to the halfway point of the straight
|
||||
/// lines between its key centers, which are not drawn. Drawn with the edges hidden too, as they are
|
||||
/// controls.
|
||||
pub(super) fn blend_widgets_overlay(app: &VectorcraftApp, p: &egui::Painter, xf: &Xf) {
|
||||
use vectorcraft_tools::blendwidget::{BlendControl, BlendWidget, RADIUS_PX};
|
||||
if !matches!(app.session.tool_id(), "selection" | "blend") {
|
||||
|
||||
+1
-1
@@ -541,7 +541,7 @@ macOS cursor delivery (#2292) is narrowed to a native host adapter using the exi
|
||||
Ease Out In), Ramp 0–100% and independent color Shift; easing is disabled without a spine.
|
||||
The original polynomial distributions approximate the public description. With the Selection or
|
||||
Blend tool, on-canvas widgets on a blend with a spine set the color Shift and, when the steps follow
|
||||
that spine, the Ramp; a blend made without a spine shows neither widget. The Blend panel shows the
|
||||
that spine, the Ramp; a blend made without a spine shows the color Shift widget alone. The Blend panel shows the
|
||||
values they set. [Public source](https://helpx.adobe.com/illustrator/desktop/manage-colors/apply-transparency-and-blending/blend-panel-overview.html).
|
||||
|
||||
- **Done:** gradient Dither (30.0): the Gradient panel's Dither box (`paint.editGradient` `dither`, also in `paint.setFill`'s
|
||||
|
||||
+3
-2
@@ -9973,8 +9973,9 @@ the Selection and Blend tools show the same values as widgets
|
||||
16 px above and below the spine's halfway point (where the boxes of three or more other visible objects hold that
|
||||
spot, a widget moves out in 16 px steps, up to 144 px, joined to the halfway point by a line): the upper one sets
|
||||
`colorShift` and the lower one, shown only when the steps follow the spine as `ramp` needs, sets `ramp`, by one per screen pixel dragged along the spine and by one per arrow key
|
||||
during the drag (ten with Shift). Each drag is one undo step, and `press_key` Escape or a switch to another tool
|
||||
during it puts the value back:
|
||||
during the drag (ten with Shift). A blend made without a spine shows the upper widget alone, placed against the
|
||||
straight lines between its key centers that its steps are laid out along, which are not drawn. Each drag is one undo
|
||||
step, and `press_key` Escape or a switch to another tool during it puts the value back:
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"object.blend.setEasing","params":{"id":40,"ramp":35}}}
|
||||
|
||||
@@ -1821,7 +1821,10 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
|
||||
- **Blend, mesh and envelope points by number:** `object.blend.spine.moveAnchor`, `reshapeSegment` and `removeAnchor`, `object.mesh.setPointColor`, `movePoint` and `deletePoint`, and `object.envelope.setMeshPoint` refuse an `anchor`, `segment` or `index` that is not a whole number, and `object.envelope.setMeshPoint` refuses a negative `index`. Specified Steps (`object.blend.make`, `object.blend.options`) and the rows and columns of `object.mesh.create`, `object.envelope.makeWithMesh` and `resetWithMesh` must be whole numbers too.
|
||||
|
||||
- **Blend widgets on the canvas:** with one blend selected that has a spine, the Selection and Blend tools show two widgets 16 px above and below the spine's halfway point (further out, joined to that point by a line, where the boxes of three or more other objects crowd the spot), as the [Blend panel help page](https://helpx.adobe.com/illustrator/desktop/manage-colors/apply-transparency-and-blending/blend-panel-overview.html) describes an upper widget for Appearance and a lower one for Easing. The upper one sets Shift and the lower one Ramp, keeping the Type, and the lower one shows only when the steps follow that spine; a blend made without a spine shows neither. A drag along the spine changes the value by one per screen pixel from its value at the press, the arrow keys during the drag by one (Shift: ten), and Escape or a tool switch cancels it. A drag is one undo step, or none when the value ends where it started. The placement, the drag and the keys are VectorCraft's own rules. `object.blend.setEasing` sets the same values for agents. Evidence: `blendwidget::tests`, `tests::blendopts::{set_easing_edits_one_blend_and_refuses_what_it_cannot_set, dragging_a_blend_widget_is_one_undo_step_and_a_click_or_escape_none}`, `shortcuts::tests::shift_arrows_step_a_pressed_blend_widget_by_ten`.
|
||||
- **Blend widgets on the canvas:** with one blend selected that has a spine, the Selection and Blend tools show two widgets 16 px above and below the spine's halfway point (further out, joined to that point by a line, where the boxes of three or more other objects crowd the spot), as the [Blend panel help page](https://helpx.adobe.com/illustrator/desktop/manage-colors/apply-transparency-and-blending/blend-panel-overview.html) describes an upper widget for Appearance and a lower one for Easing. The upper one sets Shift and the lower one Ramp, keeping the Type, and the lower one shows only when the steps follow that spine. A drag along the spine changes the value by one per screen pixel from its value at the press, the arrow keys during the drag by one (Shift: ten), and Escape or a tool switch cancels it. A drag is one undo step, or none when the value ends where it started. The placement, the drag and the keys are VectorCraft's own rules. `object.blend.setEasing` sets the same values for agents. Evidence: `blendwidget::tests`, `tests::blendopts::{set_easing_edits_one_blend_and_refuses_what_it_cannot_set, dragging_a_blend_widget_is_one_undo_step_and_a_click_or_escape_none}`, `shortcuts::tests::shift_arrows_step_a_pressed_blend_widget_by_ten`.
|
||||
|
||||
- **Blend widgets without a spine:** a blend made with Create Spine set to Never shows the Appearance widget alone with the Selection and Blend tools, and its drag sets Shift as on a blend with a spine; there is no Easing widget, as the [Blend panel help page](https://helpx.adobe.com/illustrator/desktop/manage-colors/apply-transparency-and-blending/blend-panel-overview.html) makes Easing available only on a blend with a spine. The widget sits 16 px above the halfway point, by length, of the straight lines between the key centers that the steps are laid out along, and a drag follows those lines' direction there; the lines are not drawn. Showing the widget without a spine and its placement are VectorCraft's own rules. Evidence: `blendwidget::tests::{a_blend_without_a_spine_shows_the_appearance_widget_alone, dragging_the_appearance_widget_of_a_blend_without_a_spine}`, `tests::blendopts::dragging_the_appearance_widget_of_a_blend_without_a_spine_is_one_undo_step`, `blend::tests::a_blend_without_a_spine_has_a_layout_line_through_its_key_centers`.
|
||||
|
||||
- **Positions along a blend spine:** blend steps, keys spread along a spine, the blend widgets and Objects on Path find the spine segment for each arc length with a binary search over the anchors' arc lengths, so a spine of 4,096 segments takes at most 13 comparisons per position instead of one per segment before it. Points and tangents are unchanged, including at anchors shared by zero-length segments. Evidence: `blend::tests::{spine_lookup_compares_a_few_segment_ends, spine_lookup_matches_walking_the_segments}`.
|
||||
|
||||
- **Blend, mesh and envelope numbers given as text:** `object.blend.make` and `object.blend.options` (`steps`, `distance`, `value`), `object.envelope.makeWithWarp`, `resetWithWarp` and `options` (`bend`, `h`, `v`, `horizontalDistortion`, `verticalDistortion`), `object.envelope.makeWithMesh` and `resetWithMesh` (`rows`, `cols`, `columns`), `object.envelope.options` (`fidelity`), `object.mesh.create` (`rows`, `cols`, `columns`, `highlight`) and `object.mesh.setPointColor` (`opacity`) refuse a value other than a number or null, such as `"8"`, and change nothing. `object.mesh.create` refuses an `at` that is not `[x, y]`.
|
||||
|
||||
+1
-1
@@ -437,7 +437,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
|
||||
| Canvas context menu | done | Layers rows, panels and type have none (gap) |
|
||||
| SVG Interactivity panel: JavaScript for each object's events and the document's linked JavaScript files, written to SVG and read back as text | done | `svgInteractivity.*`, `ui.javascriptFilesDialog` |
|
||||
| Retype, Comments, Version History panels | missing | the last two are cloud features (out of scope) |
|
||||
| Blend panel (30.7) | partial | Window › Blend provides Type (Ease In, Ease Out, Ease In Out, Ease Out In), Ramp 0–100% and independent color Shift, alongside the existing blend controls. Easing is disabled without a spine. Original polynomial distributions approximate the publicly documented behavior; on-canvas easing handles remain missing. [Public source](https://helpx.adobe.com/illustrator/desktop/manage-colors/apply-transparency-and-blending/blend-panel-overview.html) |
|
||||
| Blend panel (30.7) | partial | Window › Blend provides Type (Ease In, Ease Out, Ease In Out, Ease Out In), Ramp 0–100% and independent color Shift, alongside the existing blend controls. Easing is disabled without a spine. Original polynomial distributions approximate the publicly documented behavior. On-canvas Appearance and Easing widgets set Shift and Ramp; a blend without a spine shows the Appearance widget alone. [Public source](https://helpx.adobe.com/illustrator/desktop/manage-colors/apply-transparency-and-blending/blend-panel-overview.html) |
|
||||
| Gradient panel: Dither (30.0) | done | Dither box under the stops (`paint.editGradient` `dither`); dimmed behind the Stroke proxy, fills only; `panels::gradient` test |
|
||||
| Gradient panel: Method, Classic or Perceptual (30.0) | done | Method menu under the Stroke buttons for linear and radial gradients (`paint.editGradient` `method`); [public source](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-gradients/control-perceptual-interpolation-in-gradients.html); `panels::gradient` test |
|
||||
| Gradient panel: Presets (30.0) | done | Presets menu by library (theme) plus Saved: applies a preset to the gradient behind the active proxy, keeping its placement, one undo step; panel menu Save Gradient Preset…, Delete Gradient Preset, Hide/Show Presets (`gradient.presets.*`); `panels::gradient` tests |
|
||||
|
||||
Reference in New Issue
Block a user