mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-25 02:47:02 -06:00
Merge main into #783
This commit is contained in:
Generated
+21
-21
@@ -1413,7 +1413,7 @@ checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693"
|
||||
|
||||
[[package]]
|
||||
name = "figcraft"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"eframe",
|
||||
@@ -1440,7 +1440,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-cli"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-doc",
|
||||
"figcraft-engine",
|
||||
@@ -1454,7 +1454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-collab"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"figcraft-color",
|
||||
@@ -1469,7 +1469,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-collab-net"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"figcraft-collab",
|
||||
@@ -1492,7 +1492,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-color"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"serde",
|
||||
@@ -1501,7 +1501,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-doc"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-color",
|
||||
"figcraft-geom",
|
||||
@@ -1513,7 +1513,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-engine"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"craft-docpath",
|
||||
@@ -1541,7 +1541,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-format"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-doc",
|
||||
"figcraft-pathops",
|
||||
@@ -1556,7 +1556,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-geom"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"serde",
|
||||
@@ -1564,7 +1564,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-layout"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-doc",
|
||||
"figcraft-geom",
|
||||
@@ -1572,7 +1572,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-mcp"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"figcraft-collab-net",
|
||||
@@ -1588,7 +1588,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-pathops"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-doc",
|
||||
"figcraft-geom",
|
||||
@@ -1597,7 +1597,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-pdf"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-color",
|
||||
"figcraft-doc",
|
||||
@@ -1614,7 +1614,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-plugin"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-color",
|
||||
"serde",
|
||||
@@ -1627,7 +1627,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-render"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"figcraft-color",
|
||||
@@ -1645,7 +1645,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-server"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"ctrlc",
|
||||
@@ -1669,7 +1669,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-svg"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"figcraft-color",
|
||||
@@ -1685,7 +1685,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-text"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-doc",
|
||||
"figcraft-geom",
|
||||
@@ -1699,7 +1699,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-ui-egui"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"egui",
|
||||
@@ -1724,7 +1724,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "figcraft-web"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -5934,7 +5934,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"figcraft-engine",
|
||||
"serde_json",
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ exclude = ["plugins"]
|
||||
# default-members = ["crates/*", "apps/figcraft", "apps/figcraft-cli", "xtask"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
|
||||
@@ -60,20 +60,23 @@ pub fn section(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// One export setting: size (typed or picked), suffix, format, advanced options and remove; a
|
||||
/// PDF's print settings under it.
|
||||
/// One export setting: size (typed or picked; images only, SVG and PDF are vector), suffix,
|
||||
/// format, advanced options and remove; a PDF's print settings under it.
|
||||
fn setting_row(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], i: usize, e: &ExportSetting) {
|
||||
row(ui, 32.0, |ui| {
|
||||
// Tighter spacing so the advanced options and remove buttons fit beside the fields.
|
||||
ui.spacing_mut().item_spacing.x = 4.0;
|
||||
let (size_w, format_w, button, gap) = (52.0, 54.0, 22.0, ui.spacing().item_spacing.x);
|
||||
let suffix_w = (ui.available_width() - size_w - format_w - button * 2.0 - gap * 4.0).max(32.0);
|
||||
let presets: Vec<String> =
|
||||
ExportSize::SCALES.iter().map(|value| ExportSize::Scale { value: *value }.label()).chain(["512w".into(), "512h".into()]).collect();
|
||||
if let Some(s) = widgets::combo_field(ui, Id::new(("export_size", i)), &e.size.label(), size_w, &presets)
|
||||
&& ExportSize::parse(&s) != Some(e.size)
|
||||
{
|
||||
app.run("export.update", json!({"ids": ids, "index": i, "size": s}));
|
||||
let (format_w, button, gap) = (54.0, 22.0, ui.spacing().item_spacing.x);
|
||||
let size_w = e.format.is_raster().then_some(52.0);
|
||||
let suffix_w = (ui.available_width() - size_w.map_or(0.0, |w| w + gap) - format_w - button * 2.0 - gap * 3.0).max(32.0);
|
||||
if let Some(size_w) = size_w {
|
||||
let presets: Vec<String> =
|
||||
ExportSize::SCALES.iter().map(|value| ExportSize::Scale { value: *value }.label()).chain(["512w".into(), "512h".into()]).collect();
|
||||
if let Some(s) = widgets::combo_field(ui, Id::new(("export_size", i)), &e.size.label(), size_w, &presets)
|
||||
&& ExportSize::parse(&s) != Some(e.size)
|
||||
{
|
||||
app.run("export.update", json!({"ids": ids, "index": i, "size": s}));
|
||||
}
|
||||
}
|
||||
if let Some(sfx) = widgets::text_field(ui, Id::new(("export_suffix", i)), &e.suffix, suffix_w, tr("Suffix"))
|
||||
&& sfx != e.suffix
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! The Export section's Preview row: a slice paints nothing itself, so its preview must show the
|
||||
//! page content under it — the same thing exporting the slice writes — and not a blank
|
||||
//! transparency checkerboard next to a correct export.
|
||||
//! transparency checkerboard next to a correct export. And its setting rows: a size only for
|
||||
//! images.
|
||||
|
||||
use figcraft_doc::{Document, NodeId};
|
||||
use figcraft_engine::Session;
|
||||
@@ -50,3 +51,28 @@ fn other_layers_preview_themselves() {
|
||||
assert_eq!([pix.width(), pix.height()], [200, 100], "the layer's own area");
|
||||
assert!(paints(&pix));
|
||||
}
|
||||
|
||||
/// SVG and PDF are vector: their export rows offer no size, which their export ignores (#256).
|
||||
#[test]
|
||||
fn export_rows_offer_a_size_for_images_only() {
|
||||
let mut s = Session::new();
|
||||
let r = crate::present_tests::id(&s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 100, "height": 80})).unwrap());
|
||||
s.execute("select.set", &json!({"ids": [r]})).unwrap();
|
||||
s.execute("export.add", &json!({"ids": [r]})).unwrap();
|
||||
let mut u = crate::layers_drag_tests::Ui::new(s);
|
||||
let size_menu = |u: &crate::layers_drag_tests::Ui| u.ctx.read_response(egui::Id::new(("export_size", 0)).with("menu")).is_some();
|
||||
u.frame(vec![]);
|
||||
assert!(size_menu(&u), "PNG has sizes");
|
||||
for format in ["svg", "pdf"] {
|
||||
u.app.run("export.update", json!({"ids": [r], "index": 0, "format": format}));
|
||||
u.frame(vec![]);
|
||||
u.frame(vec![]);
|
||||
assert!(!size_menu(&u), "{format} is vector: no size");
|
||||
}
|
||||
for format in ["jpg", "webp"] {
|
||||
u.app.run("export.update", json!({"ids": [r], "index": 0, "format": format}));
|
||||
u.frame(vec![]);
|
||||
u.frame(vec![]);
|
||||
assert!(size_menu(&u), "{format} has sizes again");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ icons! {
|
||||
FlipH,
|
||||
FlipV,
|
||||
CornerRadius,
|
||||
Angle,
|
||||
IndependentCorners,
|
||||
LinkOn,
|
||||
LinkOff,
|
||||
@@ -694,6 +695,12 @@ fn draw(p: &P, icon: Icon) {
|
||||
p.fill(&[(3.0, 9.0), (13.0, 9.0), (13.0, 14.0)]);
|
||||
}
|
||||
CornerRadius => p.corner(2.5, 2.5, 1.0, 1.0, 11.5, 6.0),
|
||||
Angle => {
|
||||
// Two rays from a vertex, the arc between them marking the angle (all unhinted, so
|
||||
// the arc meets the rays at every size).
|
||||
p.path(&[(13.5, 13.0), (2.5, 13.0), (10.5, 3.0)], false);
|
||||
p.path(&arc_pts(2.5, 13.0, 6.0, (-10.0f32).atan2(8.0), 0.0).collect::<Vec<_>>(), false);
|
||||
}
|
||||
IndependentCorners => {
|
||||
for (x, y, dx, dy) in [(2.5, 2.5, 1.0, 1.0), (13.5, 2.5, -1.0, 1.0), (2.5, 13.5, 1.0, -1.0), (13.5, 13.5, -1.0, -1.0)] {
|
||||
p.corner(x, y, dx, dy, 4.0, 2.0);
|
||||
@@ -1181,4 +1188,42 @@ mod tests {
|
||||
assert_eq!(triangulate(&rev).len(), (arrow.len() - 2) * 3);
|
||||
assert!(triangulate(&arrow[..2]).is_empty());
|
||||
}
|
||||
|
||||
/// The points painted inside `area`, relative to the top left of what's painted there (in
|
||||
/// quarter points), so the same icon gives the same marks wherever its field puts it.
|
||||
fn marks(shapes: &[egui::epaint::ClippedShape], area: Rect) -> Vec<(i32, i32)> {
|
||||
fn walk(shape: &Shape, out: &mut Vec<Pos2>) {
|
||||
match shape {
|
||||
Shape::Vec(v) => v.iter().for_each(|s| walk(s, out)),
|
||||
Shape::LineSegment { points, .. } => out.extend(points),
|
||||
Shape::Path(p) => out.extend(&p.points),
|
||||
Shape::Circle(c) => out.push(c.center),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut pts = Vec::new();
|
||||
shapes.iter().for_each(|c| walk(&c.shape, &mut pts));
|
||||
pts.retain(|p| area.contains(*p));
|
||||
let x0 = pts.iter().map(|p| p.x).fold(f32::INFINITY, f32::min);
|
||||
let y0 = pts.iter().map(|p| p.y).fold(f32::INFINITY, f32::min);
|
||||
let mut out: Vec<(i32, i32)> = pts.into_iter().map(|p| (((p.x - x0) * 4.0).round() as i32, ((p.y - y0) * 4.0).round() as i32)).collect();
|
||||
out.sort_unstable();
|
||||
out.dedup();
|
||||
out
|
||||
}
|
||||
|
||||
/// The Design panel's Rotation field shows an icon of its own, not Corner radius's (#256).
|
||||
#[test]
|
||||
fn rotation_and_corner_radius_fields_show_different_icons() {
|
||||
let mut s = figcraft_engine::Session::new();
|
||||
let r = s.execute("shape.rectangle", &serde_json::json!({"x": 0, "y": 0, "width": 100, "height": 80})).unwrap()["id"].clone();
|
||||
s.execute("select.set", &serde_json::json!({"ids": [r]})).unwrap();
|
||||
let mut u = crate::layers_drag_tests::Ui::new(s);
|
||||
u.frame(vec![]);
|
||||
let shapes = u.frame_output(vec![]).shapes;
|
||||
let lead = |name: &str| u.ctx.read_response(egui::Id::new(name).with("lead")).map(|r| r.rect).unwrap_or_else(|| panic!("{name} is shown"));
|
||||
let (rotation, radius) = (marks(&shapes, lead("pos_rot")), marks(&shapes, lead("ap_rad")));
|
||||
assert!(!rotation.is_empty() && !radius.is_empty(), "both draw an icon");
|
||||
assert_ne!(rotation, radius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +657,7 @@ fn position_fields(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]
|
||||
});
|
||||
widgets::sublabels(ui, tr("Rotation"), None);
|
||||
row(ui, 32.0, |ui| {
|
||||
let e = widgets::number_field_mixed(ui, Id::new("pos_rot"), Lead::Icon(Icon::CornerRadius, tr("Rotation")), rot, "°", col, 1.0);
|
||||
let e = widgets::number_field_mixed(ui, Id::new("pos_rot"), Lead::Icon(Icon::Angle, tr("Rotation")), rot, "°", col, 1.0);
|
||||
app.field_of(&ids, "rotation", e, rot_of, |v| json!(v));
|
||||
let items = [
|
||||
(Icon::Rotate, tr("Rotate 90° right")),
|
||||
|
||||
Reference in New Issue
Block a user