mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 14:16:56 -06:00
143 lines
6.7 KiB
Rust
143 lines
6.7 KiB
Rust
//! Blend panel (Window → Blend): Make, the Spacing and Orientation of the selected blend (with no
|
|
//! blend selected, the options new blends start with), Reverse Front to Back, Reverse Spine,
|
|
//! Replace Spine, Expand and Release. Each control runs its Object → Blend command
|
|
//! (`object.blend.make`, `object.blend.options` and the others), so a change of the selected
|
|
//! blend's spacing or orientation shows on the canvas at once as one undo step. Make is dimmed
|
|
//! until two or more objects are selected. Spacing and Orientation are dimmed until a blend or two
|
|
//! or more objects are selected, and the other buttons are dimmed until a blend is selected.
|
|
|
|
use egui::Ui;
|
|
use serde_json::{Value, json};
|
|
|
|
use super::selection_len;
|
|
use crate::VectorcraftApp;
|
|
use crate::dialogs::blend_options::{SPACING, orientation_button};
|
|
use crate::widgets;
|
|
|
|
pub const ID: &str = "blend";
|
|
|
|
/// Width of the label column, as in Blend Options.
|
|
const LABEL_W: f32 = 82.0;
|
|
|
|
/// Width of the Specified Steps and Specified Distance field.
|
|
const FIELD_W: f32 = 72.0;
|
|
|
|
/// A row of flat buttons sharing the row's width, each (label, command): a click runs the command,
|
|
/// and a button whose command is unavailable is dimmed.
|
|
fn buttons(app: &mut VectorcraftApp, ui: &mut Ui, row: &[(&str, &str)]) {
|
|
let gap = ui.spacing().item_spacing.x;
|
|
let n = row.len() as f32;
|
|
let width = ((ui.available_width() - gap * (n - 1.0)) / n).max(48.0);
|
|
ui.horizontal(|ui| {
|
|
for &(label, cmd) in row {
|
|
let on = available(app, cmd);
|
|
if ui.add_enabled_ui(on, |ui| widgets::flat_button(ui, label, width)).inner.clicked() {
|
|
app.run(cmd, json!({})).ok();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Can `cmd` run on the selection? Make needs two or more objects, and the other commands need a
|
|
/// selected blend.
|
|
fn available(app: &VectorcraftApp, cmd: &str) -> bool {
|
|
crate::menus::enabled(app, cmd) && (cmd != "object.blend.make" || selection_len(app) >= 2)
|
|
}
|
|
|
|
/// `object.blend.options` parameters for spacing mode `mode`, with the step count and distance
|
|
/// the panel shows.
|
|
fn spacing_params(mode: &str, steps: f64, distance: f64) -> Value {
|
|
match mode {
|
|
"steps" => json!({"spacing": "steps", "steps": steps}),
|
|
"distance" => json!({"spacing": "distance", "distance": distance}),
|
|
_ => json!({"spacing": "smooth"}),
|
|
}
|
|
}
|
|
|
|
/// Spacing and Orientation, as `object.blend.info` reports them; a change runs
|
|
/// `object.blend.options`.
|
|
fn settings(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|
let info = app.session.execute("object.blend.info", &json!({})).unwrap_or_default();
|
|
let unit = app.session.general_unit();
|
|
let mode = info["spacing"].as_str().unwrap_or("smooth");
|
|
let steps = info["steps"].as_f64().unwrap_or(5.0);
|
|
let distance = info["distance"].as_f64().unwrap_or(10.0);
|
|
let path = info["orientation"] == "path";
|
|
let mut change = None;
|
|
let shown = SPACING.iter().find(|(v, _)| *v == mode).map_or(SPACING[0].1, |(_, l)| l);
|
|
let labels = SPACING.map(|(_, l)| l);
|
|
widgets::label_row(ui, tl!("Spacing:"), LABEL_W, |ui| {
|
|
let width = ui.available_width().max(FIELD_W);
|
|
if let Some((value, _)) = widgets::dropdown(ui, "blend-panel-spacing", shown, &labels, width).and_then(|i| SPACING.get(i)) {
|
|
change = Some(spacing_params(value, steps, distance));
|
|
}
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.add_space(LABEL_W);
|
|
if mode == "distance" {
|
|
if let Some(d) = widgets::num_field(ui, "blend-panel-distance", Some(distance), unit, FIELD_W) {
|
|
change = Some(spacing_params("distance", steps, d));
|
|
}
|
|
} else {
|
|
// Smooth Color picks the count itself: the field shows the steps, disabled.
|
|
ui.add_enabled_ui(mode == "steps", |ui| {
|
|
if let Some(n) = widgets::range_field(ui, "blend-panel-steps", steps, 1.0..=1000.0, "", 0, FIELD_W) {
|
|
change = Some(spacing_params("steps", n, distance));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
widgets::label_row(ui, tl!("Orientation:"), LABEL_W, |ui| {
|
|
for (value, along, tip) in [("page", false, tl!("Align to Page")), ("path", true, tl!("Align to Path"))] {
|
|
if orientation_button(ui, along, path == along, tip) {
|
|
change = Some(json!({ "orientation": value }));
|
|
}
|
|
}
|
|
});
|
|
let selected = info["target"] == "blend";
|
|
// Position easing needs a spine to run along: a replaced one, or Align to Path.
|
|
let spined = info["spine"]["explicit"] == true || info["orientation"] == "path";
|
|
ui.add_enabled_ui(selected && spined, |ui| {
|
|
let modes = ["easeIn", "easeOut", "easeInOut", "easeOutIn"];
|
|
let labels = [tl!("Ease In"), tl!("Ease Out"), tl!("Ease In Out"), tl!("Ease Out In")];
|
|
let mode = info["easingType"].as_str().unwrap_or(modes[0]);
|
|
let index = modes.iter().position(|m| *m == mode).unwrap_or(0);
|
|
widgets::label_row(ui, tl!("Type:"), LABEL_W, |ui| {
|
|
if let Some(i) = widgets::dropdown(ui, "blend-panel-easing", labels[index], &labels, ui.available_width().max(FIELD_W))
|
|
&& let Some(mode) = modes.get(i)
|
|
{
|
|
change = Some(json!({"easingType": mode}));
|
|
}
|
|
});
|
|
widgets::label_row(ui, tl!("Ramp:"), LABEL_W, |ui| {
|
|
let mut ramp = info["ramp"].as_f64().unwrap_or(0.0);
|
|
if ui.add(egui::Slider::new(&mut ramp, 0.0..=100.0).suffix("%")).changed() {
|
|
change = Some(json!({"ramp": ramp}));
|
|
}
|
|
});
|
|
});
|
|
ui.add_enabled_ui(selected, |ui| {
|
|
widgets::label_row(ui, tl!("Shift:"), LABEL_W, |ui| {
|
|
let mut shift = info["colorShift"].as_f64().unwrap_or(0.0);
|
|
if ui.add(egui::Slider::new(&mut shift, -100.0..=100.0).suffix("%")).changed() {
|
|
change = Some(json!({"colorShift": shift}));
|
|
}
|
|
});
|
|
});
|
|
if let Some(p) = change {
|
|
app.run("object.blend.options", p).ok();
|
|
}
|
|
}
|
|
|
|
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|
let blend = crate::menus::enabled(app, "object.blend.release");
|
|
buttons(app, ui, &[(tl!("Make"), "object.blend.make")]);
|
|
widgets::divider(ui);
|
|
ui.add_enabled_ui(blend || selection_len(app) >= 2, |ui| settings(app, ui));
|
|
widgets::label_row(ui, tl!("Direction:"), LABEL_W, |ui| buttons(app, ui, &[(tl!("Reverse Front to Back"), "object.blend.reverseFrontToBack")]));
|
|
widgets::divider(ui);
|
|
buttons(app, ui, &[(tl!("Reverse Spine"), "object.blend.reverseSpine"), (tl!("Replace Spine"), "object.blend.replaceSpine")]);
|
|
widgets::divider(ui);
|
|
buttons(app, ui, &[(tl!("Expand"), "object.blend.expand"), (tl!("Release"), "object.blend.release")]);
|
|
}
|