Add Table<Gradient> as a graphical type (#3051)

This commit is contained in:
Keavon Chambers
2025-08-13 01:42:51 -07:00
committed by GitHub
parent 1b351aca76
commit bdc029c692
33 changed files with 689 additions and 573 deletions
+2 -2
View File
@@ -69,7 +69,7 @@ mod test {
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
// Print the current node
let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() {
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
("├── ", format!("{}", prefix))
} else {
if is_last {
@@ -97,7 +97,7 @@ mod test {
// Print handler field if any
if let Some(data) = tree.message_handler_fields() {
let len = data.fields().len();
let (branch, child_prefix) = if tree.has_message_handler_data_fields() {
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
("├── ", format!("{}", prefix))
} else {
("└── ", format!("{} ", prefix))
@@ -7,6 +7,7 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::Context;
use graphene_std::gradient::GradientStops;
use graphene_std::memo::IORecord;
use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
@@ -153,7 +154,6 @@ macro_rules! generate_layout_downcast {
else { None }
}
}
// TODO: We simply try all these types sequentially. Find a better strategy.
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
generate_layout_downcast!(introspected_data, data, [
@@ -163,6 +163,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
Table<Raster<CPU>>,
Table<Raster<GPU>>,
Table<Color>,
Table<GradientStops>,
f64,
u32,
u64,
@@ -263,6 +264,7 @@ impl TableRowLayout for Graphic {
Self::RasterCPU(table) => table.identifier(),
Self::RasterGPU(table) => table.identifier(),
Self::Color(table) => table.identifier(),
Self::Gradient(table) => table.identifier(),
}
}
// Don't put a breadcrumb for Graphic
@@ -276,6 +278,7 @@ impl TableRowLayout for Graphic {
Self::RasterCPU(table) => table.layout_with_breadcrumb(data),
Self::RasterGPU(table) => table.layout_with_breadcrumb(data),
Self::Color(table) => table.layout_with_breadcrumb(data),
Self::Gradient(table) => table.layout_with_breadcrumb(data),
}
}
}
@@ -335,10 +338,6 @@ impl TableRowLayout for Vector {
TextLabel::new(format_dvec2(gradient.start)).widget_holder(),
]);
table_rows.push(vec![TextLabel::new("Fill Gradient End").widget_holder(), TextLabel::new(format_dvec2(gradient.end)).widget_holder()]);
table_rows.push(vec![
TextLabel::new("Fill Gradient Transform").widget_holder(),
TextLabel::new(format_transform_matrix(&gradient.transform)).widget_holder(),
]);
}
}
@@ -485,6 +484,25 @@ impl TableRowLayout for Color {
}
}
impl TableRowLayout for GradientStops {
fn type_name() -> &'static str {
"Gradient"
}
fn identifier(&self) -> String {
format!("Gradient ({} stops)", self.0.len())
}
fn element_widget(&self, _index: usize) -> WidgetHolder {
ColorInput::new(FillChoice::Gradient(self.clone()))
.disabled(true)
.menu_direction(Some(MenuDirection::Top))
.widget_holder()
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![self.element_widget(0)];
vec![LayoutGroup::Row { widgets }]
}
}
impl TableRowLayout for f64 {
fn type_name() -> &'static str {
"Number (f64)"
@@ -545,7 +563,7 @@ impl TableRowLayout for String {
"String".to_string()
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
let widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_holder()];
vec![LayoutGroup::Row { widgets }]
}
}
@@ -426,7 +426,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
Fill::Gradient(Gradient {
start,
end,
transform: DAffine2::IDENTITY,
gradient_type: GradientType::Linear,
stops,
})
@@ -453,7 +452,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
Fill::Gradient(Gradient {
start,
end,
transform: DAffine2::IDENTITY,
gradient_type: GradientType::Radial,
stops,
})
@@ -182,6 +182,7 @@ pub(crate) fn property_from_type(
// STRUCT TYPES
// ============
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Font>() => font_widget(default_info),
Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),
@@ -922,6 +923,20 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::GradientTable(gradient_table) => widgets.push(
color_button
.value(match gradient_table.iter().next() {
Some(row) => FillChoice::Gradient(row.element.clone()),
None => FillChoice::None,
})
.on_update(update_value(
|input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()),
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
),
TaggedValue::GradientStops(gradient_stops) => widgets.push(
color_button
.value(FillChoice::Gradient(gradient_stops.clone()))
@@ -14,6 +14,8 @@ pub enum FrontendGraphDataType {
Raster,
Vector,
Color,
Gradient,
Typography,
}
impl FrontendGraphDataType {
@@ -32,6 +34,8 @@ impl FrontendGraphDataType {
TaggedValue::Raster(_) => Self::Raster,
TaggedValue::Vector(_) => Self::Vector,
TaggedValue::Color(_) => Self::Color,
TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => Self::Gradient,
TaggedValue::String(_) => Self::Typography,
_ => Self::General,
}
}
@@ -204,7 +204,6 @@ impl SelectedGradient {
/// Update the layer fill to the current gradient
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
self.gradient.transform = self.transform;
if let Some(layer) = self.layer {
responses.add(GraphOperationMessage::FillSet {
layer,
@@ -436,14 +435,7 @@ impl Fsm for GradientToolFsmState {
gradient.clone()
} else {
// Generate a new gradient
Gradient::new(
DVec2::ZERO,
global_tool_data.secondary_color,
DVec2::ONE,
global_tool_data.primary_color,
DAffine2::IDENTITY,
tool_options.gradient_type,
)
Gradient::new(DVec2::ZERO, global_tool_data.secondary_color, DVec2::ONE, global_tool_data.primary_color, tool_options.gradient_type)
};
let selected_gradient = SelectedGradient::new(gradient, layer, document).with_gradient_start(input.mouse.position);
+27 -61
View File
@@ -8,13 +8,10 @@ use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_std::application_io::TimingInformation;
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig};
use graphene_std::renderer::RenderSvgSegmentList;
use graphene_std::renderer::{Render, RenderParams, SvgRender};
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
use graphene_std::text::FontCache;
use graphene_std::transform::Footprint;
use graphene_std::vector::Vector;
use graphene_std::vector::style::ViewMode;
use graphene_std::wasm_application_io::RenderOutputType;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
@@ -34,7 +31,6 @@ pub struct ExecutionResponse {
execution_id: u64,
result: Result<TaggedValue, String>,
responses: VecDeque<FrontendMessage>,
footprint: Footprint,
vector_modify: HashMap<NodeId, Vector>,
/// The resulting value from the temporary inspected during execution
inspect_result: Option<InspectResult>,
@@ -131,6 +127,7 @@ impl NodeGraphExecutor {
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect }))
.map_err(|e| e.to_string())?;
}
Ok(())
}
@@ -252,6 +249,7 @@ impl NodeGraphExecutor {
let size = (size * scale_factor).into();
responses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });
}
Ok(())
}
@@ -264,7 +262,6 @@ impl NodeGraphExecutor {
execution_id,
result,
responses: existing_responses,
footprint,
vector_modify,
inspect_result,
} = execution_response;
@@ -289,7 +286,7 @@ impl NodeGraphExecutor {
// Special handling for exporting the artwork
self.export(node_graph_output, export_config, responses)?;
} else {
self.process_node_graph_output(node_graph_output, footprint, responses)?;
self.process_node_graph_output(node_graph_output, responses)?;
}
responses.add_front(DeferMessage::TriggerGraphRun(execution_id, execution_context.document_id));
@@ -330,73 +327,41 @@ impl NodeGraphExecutor {
}
}
}
Ok(())
}
fn debug_render(render_object: impl Render, footprint: Footprint, responses: &mut VecDeque<Message>) {
// Setup rendering
let mut render = SvgRender::new();
let render_params = RenderParams {
view_mode: ViewMode::Normal,
footprint,
thumbnail: false,
hide_artboards: false,
for_export: false,
for_mask: false,
alignment_parent_transform: None,
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, responses: &mut VecDeque<Message>) -> Result<(), String> {
let TaggedValue::RenderOutput(render_output) = node_graph_output else {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
};
// Render SVG
render_object.render_svg(&mut render, &render_params);
// Concatenate the defs and the SVG into one string
render.wrap_with_transform(footprint.transform, None);
let svg = render.svg.to_svg_string();
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, footprint: Footprint, responses: &mut VecDeque<Message>) -> Result<(), String> {
let mut render_output_metadata = RenderMetadata::default();
match node_graph_output {
TaggedValue::RenderOutput(render_output) => {
match render_output.data {
RenderOutputType::Svg { svg, image_data } => {
// Send to frontend
responses.add(FrontendMessage::UpdateImageData { image_data });
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
RenderOutputType::CanvasFrame(frame) => {
let matrix = format_transform_matrix(frame.transform);
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
let svg = format!(
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="{}"></div></foreignObject></svg>"#,
frame.resolution.x, frame.resolution.y, frame.surface_id.0
);
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
RenderOutputType::Texture { .. } => {}
_ => return Err(format!("Invalid node graph output type: {:#?}", render_output.data)),
}
render_output_metadata = render_output.metadata;
match render_output.data {
RenderOutputType::Svg { svg, image_data } => {
// Send to frontend
responses.add(FrontendMessage::UpdateImageData { image_data });
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
TaggedValue::Bool(render_object) => Self::debug_render(render_object, footprint, responses),
TaggedValue::F64(render_object) => Self::debug_render(render_object, footprint, responses),
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, footprint, responses),
TaggedValue::String(render_object) => Self::debug_render(render_object, footprint, responses),
_ => return Err(format!("Invalid node graph output type: {node_graph_output:#?}")),
};
RenderOutputType::CanvasFrame(frame) => {
let matrix = format_transform_matrix(frame.transform);
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
let svg = format!(
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="{}"></div></foreignObject></svg>"#,
frame.resolution.x, frame.resolution.y, frame.surface_id.0
);
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
RenderOutputType::Texture { .. } => {}
_ => return Err(format!("Invalid node graph output type: {:#?}", render_output.data)),
}
let graphene_std::renderer::RenderMetadata {
let RenderMetadata {
upstream_footprints,
local_transforms,
first_element_source_id,
click_targets,
clip_targets,
} = render_output_metadata;
} = render_output.metadata;
// Run these update state messages immediately
responses.add(DocumentMessage::UpdateUpstreamTransforms {
@@ -409,6 +374,7 @@ impl NodeGraphExecutor {
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
Ok(())
}
}
+1 -7
View File
@@ -16,7 +16,6 @@ use graphene_std::table::{Table, TableRow};
use graphene_std::text::FontCache;
use graphene_std::transform::RenderQuality;
use graphene_std::vector::Vector;
use graphene_std::vector::style::ViewMode;
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
use graphene_std::{Artboard, Context, Graphic};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
@@ -228,7 +227,6 @@ impl NodeRuntime {
execution_id,
result,
responses,
footprint: render_config.viewport,
vector_modify: self.vector_modify.clone(),
inspect_result,
});
@@ -361,13 +359,9 @@ impl NodeRuntime {
// Render the thumbnail from a `Graphic` into an SVG string
let render_params = RenderParams {
view_mode: ViewMode::Normal,
footprint,
thumbnail: true,
hide_artboards: false,
for_export: false,
for_mask: false,
alignment_parent_transform: None,
..Default::default()
};
let mut render = SvgRender::new();
graphic.render_svg(&mut render, &render_params);
-14
View File
@@ -82,18 +82,4 @@ impl DebugMessageTree {
pub fn message_handler_fields(&self) -> Option<&MessageData> {
self.message_handler.as_ref()
}
pub fn has_message_handler_data_fields(&self) -> bool {
match self.message_handler_data_fields() {
Some(_) => true,
None => false,
}
}
pub fn has_message_handler_fields(&self) -> bool {
match self.message_handler_fields() {
Some(_) => true,
None => false,
}
}
}