mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Update graph UI and improve simplicity and robustness of sending graph and layer panel updates (#1564)
* WIP * Fix loading the structure of layers * Fix broken indents * Remove debugging stuff * Fix displaying errors and node graph UI fixes/improvements * Fix compilation failure --------- Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
@@ -158,6 +158,7 @@ pub struct RenderConfig {
|
||||
}
|
||||
|
||||
pub struct EditorApi<'a, Io> {
|
||||
// TODO: Is `image_frame` still used? I think it's only ever set to None.
|
||||
pub image_frame: Option<ImageFrame<Color>>,
|
||||
pub font_cache: &'a FontCache,
|
||||
pub application_io: &'a Io,
|
||||
|
||||
@@ -60,7 +60,7 @@ impl ClickTarget {
|
||||
|
||||
/// Mutable state used whilst rendering to an SVG
|
||||
pub struct SvgRender {
|
||||
pub svg: SvgSegmentList,
|
||||
pub svg: Vec<SvgSegment>,
|
||||
pub svg_defs: String,
|
||||
pub transform: DAffine2,
|
||||
pub image_data: Vec<(u64, Image<Color>)>,
|
||||
@@ -70,7 +70,7 @@ pub struct SvgRender {
|
||||
impl SvgRender {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
svg: SvgSegmentList::default(),
|
||||
svg: Vec::default(),
|
||||
svg_defs: String::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
image_data: Vec::new(),
|
||||
@@ -79,8 +79,8 @@ impl SvgRender {
|
||||
}
|
||||
|
||||
pub fn indent(&mut self) {
|
||||
self.svg.push("\n");
|
||||
self.svg.push("\t".repeat(self.indent));
|
||||
self.svg.push("\n".into());
|
||||
self.svg.push("\t".repeat(self.indent).into());
|
||||
}
|
||||
|
||||
/// Add an outer `<svg>...</svg>` tag with a `viewBox` and the `<defs />`
|
||||
@@ -90,7 +90,7 @@ impl SvgRender {
|
||||
let defs = &self.svg_defs;
|
||||
let svg_header = format!(r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{x} {y} {size_x} {size_y}"><defs>{defs}</defs>"#,);
|
||||
self.svg.insert(0, svg_header.into());
|
||||
self.svg.push("</svg>");
|
||||
self.svg.push("</svg>".into());
|
||||
}
|
||||
|
||||
/// Wraps the SVG with `<svg><g transform="...">...</g></svg>`, which allows for rotation
|
||||
@@ -106,43 +106,43 @@ impl SvgRender {
|
||||
format_transform_matrix(transform)
|
||||
);
|
||||
self.svg.insert(0, svg_header.into());
|
||||
self.svg.push("</g></svg>");
|
||||
self.svg.push("</g></svg>".into());
|
||||
}
|
||||
|
||||
pub fn leaf_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs)) {
|
||||
self.indent();
|
||||
self.svg.push("<");
|
||||
self.svg.push(name);
|
||||
self.svg.push("<".into());
|
||||
self.svg.push(name.into());
|
||||
attributes(&mut SvgRenderAttrs(self));
|
||||
|
||||
self.svg.push("/>");
|
||||
self.svg.push("/>".into());
|
||||
}
|
||||
|
||||
pub fn leaf_node(&mut self, content: impl Into<SvgSegment>) {
|
||||
self.indent();
|
||||
self.svg.push(content);
|
||||
self.svg.push(content.into());
|
||||
}
|
||||
|
||||
pub fn parent_tag(&mut self, name: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs), inner: impl FnOnce(&mut Self)) {
|
||||
let name = name.into();
|
||||
self.indent();
|
||||
self.svg.push("<");
|
||||
self.svg.push("<".into());
|
||||
self.svg.push(name.clone());
|
||||
// Wraps `self` in a newtype (1-tuple) which is then mutated by the `attributes` closure
|
||||
attributes(&mut SvgRenderAttrs(self));
|
||||
self.svg.push(">");
|
||||
self.svg.push(">".into());
|
||||
let length = self.svg.len();
|
||||
self.indent += 1;
|
||||
inner(self);
|
||||
self.indent -= 1;
|
||||
if self.svg.len() != length {
|
||||
self.indent();
|
||||
self.svg.push("</");
|
||||
self.svg.push("</".into());
|
||||
self.svg.push(name);
|
||||
self.svg.push(">");
|
||||
self.svg.push(">".into());
|
||||
} else {
|
||||
self.svg.pop();
|
||||
self.svg.push("/>");
|
||||
self.svg.push("/>".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,8 +154,6 @@ impl Default for SvgRender {
|
||||
}
|
||||
|
||||
pub enum ImageRenderMode {
|
||||
BlobUrl,
|
||||
Canvas,
|
||||
Base64,
|
||||
}
|
||||
|
||||
@@ -209,10 +207,10 @@ pub trait GraphicElementRendered {
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>);
|
||||
fn to_usvg_node(&self) -> usvg::Node {
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams::new(crate::vector::style::ViewMode::Normal, ImageRenderMode::BlobUrl, None, false, false, false);
|
||||
let render_params = RenderParams::new(crate::vector::style::ViewMode::Normal, ImageRenderMode::Base64, None, false, false, false);
|
||||
self.render_svg(&mut render, &render_params);
|
||||
render.format_svg(DVec2::ZERO, DVec2::ONE);
|
||||
let svg = render.svg.to_string();
|
||||
let svg = render.svg.to_svg_string();
|
||||
|
||||
let opt = usvg::Options::default();
|
||||
|
||||
@@ -384,7 +382,7 @@ impl GraphicElementRendered for Artboard {
|
||||
},
|
||||
|render| {
|
||||
// TODO: Use the artboard's layer name
|
||||
render.svg.push("Artboard");
|
||||
render.svg.push("Artboard".into());
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -448,22 +446,8 @@ impl GraphicElementRendered for Artboard {
|
||||
impl GraphicElementRendered for ImageFrame<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
let transform: String = format_transform_matrix(self.transform * render.transform);
|
||||
let uuid = generate_uuid();
|
||||
|
||||
match render_params.image_render_mode {
|
||||
ImageRenderMode::BlobUrl => {
|
||||
render.leaf_tag("image", move |attributes| {
|
||||
attributes.push("width", 1.to_string());
|
||||
attributes.push("height", 1.to_string());
|
||||
attributes.push("preserveAspectRatio", "none");
|
||||
attributes.push("transform", transform);
|
||||
attributes.push("href", SvgSegment::BlobUrl(uuid));
|
||||
if self.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
render.image_data.push((uuid, self.image.clone()))
|
||||
}
|
||||
ImageRenderMode::Base64 => {
|
||||
let image = &self.image;
|
||||
if image.data.is_empty() {
|
||||
@@ -486,9 +470,6 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
||||
}
|
||||
});
|
||||
}
|
||||
ImageRenderMode::Canvas => {
|
||||
todo!("Canvas rendering is not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,34 +664,21 @@ impl From<&'static str> for SvgSegment {
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of [`SvgSegment`]s.
|
||||
///
|
||||
/// Can be modified with `list.push("hello".into())`. Use `list.to_string()` to convert the segments into one string.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct SvgSegmentList(Vec<SvgSegment>);
|
||||
|
||||
impl core::ops::Deref for SvgSegmentList {
|
||||
type Target = Vec<SvgSegment>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl core::ops::DerefMut for SvgSegmentList {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
pub trait RenderSvgSegmentList {
|
||||
fn to_svg_string(&self) -> String;
|
||||
}
|
||||
|
||||
impl core::fmt::Display for SvgSegmentList {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
impl RenderSvgSegmentList for Vec<SvgSegment> {
|
||||
fn to_svg_string(&self) -> String {
|
||||
let mut result = String::new();
|
||||
for segment in self.iter() {
|
||||
f.write_str(match segment {
|
||||
result.push_str(match segment {
|
||||
SvgSegment::Slice(x) => x,
|
||||
SvgSegment::String(x) => x,
|
||||
SvgSegment::BlobUrl(_) => "<!-- Blob url not yet loaded -->",
|
||||
})?;
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,22 +686,16 @@ pub struct SvgRenderAttrs<'a>(&'a mut SvgRender);
|
||||
|
||||
impl<'a> SvgRenderAttrs<'a> {
|
||||
pub fn push_complex(&mut self, name: impl Into<SvgSegment>, value: impl FnOnce(&mut SvgRender)) {
|
||||
self.0.svg.push(" ");
|
||||
self.0.svg.push(name);
|
||||
self.0.svg.push("=\"");
|
||||
self.0.svg.push(" ".into());
|
||||
self.0.svg.push(name.into());
|
||||
self.0.svg.push("=\"".into());
|
||||
value(self.0);
|
||||
self.0.svg.push("\"");
|
||||
self.0.svg.push("\"".into());
|
||||
}
|
||||
pub fn push(&mut self, name: impl Into<SvgSegment>, value: impl Into<SvgSegment>) {
|
||||
self.push_complex(name, move |renderer| renderer.svg.push(value));
|
||||
self.push_complex(name, move |renderer| renderer.svg.push(value.into()));
|
||||
}
|
||||
pub fn push_val(&mut self, value: impl Into<SvgSegment>) {
|
||||
self.0.svg.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgSegmentList {
|
||||
pub fn push(&mut self, value: impl Into<SvgSegment>) {
|
||||
self.0.push(value.into());
|
||||
self.0.svg.push(value.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores both what a node was called with and what it returned.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IORecord<I, O> {
|
||||
pub input: I,
|
||||
|
||||
@@ -347,6 +347,11 @@ impl DocumentNode {
|
||||
// TODO: Or, more fundamentally separate the concept of a layer from a node.
|
||||
self.name == "Artboard"
|
||||
}
|
||||
|
||||
pub fn is_folder(&self, network: &NodeNetwork) -> bool {
|
||||
let input_connection = self.inputs.get(0).and_then(|input| input.as_node()).and_then(|node_id| network.nodes.get(&node_id));
|
||||
input_connection.map(|node| node.is_layer()).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the possible inputs to a node.
|
||||
@@ -485,14 +490,19 @@ impl NodeOutput {
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// A network of nodes containing each [`DocumentNode`] and its ID, as well as a list of input nodes and [`NodeOutput`]s
|
||||
/// A network (subgraph) of nodes containing each [`DocumentNode`] and its ID, as well as a list of input nodes and [`NodeOutput`]s
|
||||
pub struct NodeNetwork {
|
||||
/// The list of nodes that are imported into this network from the parent network. Each is a reference to the node that the input is connected to.
|
||||
/// Presently, only one is supported— use an Identity node to split an input to multiple user nodes (although this could be changed in the future).
|
||||
pub inputs: Vec<NodeId>,
|
||||
/// The list of data outputs that are exported from this network to the parent network. Each is a reference to the node, and its output index, that is the source of the output data.
|
||||
pub outputs: Vec<NodeOutput>,
|
||||
/// The list of all nodes in this network.
|
||||
pub nodes: HashMap<NodeId, DocumentNode>,
|
||||
/// These nodes are replaced with identity nodes during the graph flattening step
|
||||
/// Nodes that the user has disabled/hidden with the visibility eye icon.
|
||||
/// These nodes get replaced with Identity nodes during the graph flattening step.
|
||||
pub disabled: Vec<NodeId>,
|
||||
/// In the case when a new node is chosen as a temporary output, this stores what it used to be so it can be restored later
|
||||
/// In the case when another node is previewed (chosen by the user as a temporary output), this stores what it used to be so it can be restored later.
|
||||
pub previous_outputs: Option<Vec<NodeOutput>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use dyn_any::StaticType;
|
||||
use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, RenderConfig, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::raster::{color::SRGBA8, ImageFrame};
|
||||
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
|
||||
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::Node;
|
||||
@@ -303,7 +303,7 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
|
||||
data.render_svg(&mut render, &render_params);
|
||||
render.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2()));
|
||||
|
||||
RenderOutput::Svg(render.svg.to_string())
|
||||
RenderOutput::Svg(render.svg.to_svg_string())
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
||||
@@ -321,7 +321,7 @@ fn render_canvas(
|
||||
let min = footprint.transform.inverse().transform_point2((0., 0.).into());
|
||||
let max = footprint.transform.inverse().transform_point2(resolution.as_dvec2());
|
||||
render.format_svg(min, max);
|
||||
let string = render.svg.to_string();
|
||||
let string = render.svg.to_svg_string();
|
||||
let array = string.as_bytes();
|
||||
let canvas = &surface_handle.surface;
|
||||
canvas.set_width(resolution.x);
|
||||
|
||||
Reference in New Issue
Block a user