mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-28 04:38:11 +08:00
Improve previewing node data (#1446)
* Improve preview * Improve contrast * Restructure in order to duplicate code * Code review nits --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
c823016316
commit
e0ac073805
@@ -94,7 +94,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(responses);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), responses);
|
||||
}
|
||||
FitViewportToSelection => {
|
||||
if let Some(bounds) = selection_bounds {
|
||||
@@ -214,7 +214,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
}
|
||||
SetCanvasRotation { angle_radians } => {
|
||||
self.tilt = angle_radians;
|
||||
self.create_document_transform(responses);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), responses);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
@@ -224,7 +224,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(responses);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), responses);
|
||||
}
|
||||
TransformCanvasEnd { abort_transform } => {
|
||||
if abort_transform {
|
||||
@@ -235,12 +235,12 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
}
|
||||
TransformOperation::Pan { pre_commit_pan, .. } => {
|
||||
self.pan = pre_commit_pan;
|
||||
self.create_document_transform(responses);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), responses);
|
||||
}
|
||||
TransformOperation::Zoom { pre_commit_zoom, .. } => {
|
||||
self.zoom = pre_commit_zoom;
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
self.create_document_transform(responses);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,7 +264,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
self.pan += transformed_delta;
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
self.create_document_transform(responses);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), responses);
|
||||
}
|
||||
TranslateCanvasBegin => {
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
|
||||
@@ -281,7 +281,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
|
||||
self.pan += transformed_delta;
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
self.create_document_transform(responses);
|
||||
self.create_document_transform(ipp.viewport_bounds.center(), responses);
|
||||
}
|
||||
WheelCanvasTranslate { use_y_as_x } => {
|
||||
let delta = match use_y_as_x {
|
||||
@@ -381,21 +381,21 @@ impl NavigationMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_offset_transform(&self, offset: DVec2) -> DAffine2 {
|
||||
pub fn calculate_offset_transform(&self, viewport_center: DVec2) -> DAffine2 {
|
||||
// Try to avoid fractional coordinates to reduce anti aliasing.
|
||||
let scale = self.snapped_scale();
|
||||
let rounded_pan = ((self.pan + offset) * scale).round() / scale - offset;
|
||||
let rounded_pan = ((self.pan + viewport_center) * scale).round() / scale - viewport_center;
|
||||
|
||||
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
|
||||
let offset_transform = DAffine2::from_translation(offset);
|
||||
let offset_transform = DAffine2::from_translation(viewport_center);
|
||||
let scale_transform = DAffine2::from_scale(DVec2::splat(scale));
|
||||
let angle_transform = DAffine2::from_angle(self.snapped_angle());
|
||||
let translation_transform = DAffine2::from_translation(rounded_pan);
|
||||
scale_transform * offset_transform * angle_transform * translation_transform
|
||||
scale_transform * offset_transform * angle_transform * offset_transform.inverse() * translation_transform
|
||||
}
|
||||
|
||||
fn create_document_transform(&self, responses: &mut VecDeque<Message>) {
|
||||
let transform = self.calculate_offset_transform(DVec2::ZERO);
|
||||
fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque<Message>) {
|
||||
let transform = self.calculate_offset_transform(viewport_center);
|
||||
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
|
||||
}
|
||||
|
||||
|
||||
+9
-5
@@ -120,7 +120,11 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
|
||||
// Update the document metadata structure
|
||||
if let Some(new_id) = new_id {
|
||||
let parent = LayerNodeIdentifier::new(output_node_id, self.network);
|
||||
let parent = if self.network.nodes.get(&output_node_id).is_some_and(|node| node.name == "Layer") {
|
||||
LayerNodeIdentifier::new(output_node_id, self.network)
|
||||
} else {
|
||||
LayerNodeIdentifier::ROOT
|
||||
};
|
||||
let new_child = LayerNodeIdentifier::new(new_id, self.network);
|
||||
parent.push_front_child(self.document_metadata, new_child);
|
||||
self.responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
@@ -562,25 +566,25 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
}
|
||||
GraphOperationMessage::NewArtboard { id, artboard } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
|
||||
modify_inputs.insert_artboard(artboard, layer);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::NewBitmapLayer { id, image_frame } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
|
||||
modify_inputs.insert_image_data(image_frame, layer);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::NewVectorLayer { id, subpaths } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
|
||||
modify_inputs.insert_vector_data(subpaths, layer);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::NewTextLayer { id, text, font, size } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id, 0) {
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
|
||||
modify_inputs.insert_text(text, font, size, layer);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -475,7 +475,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
DocumentNodeType {
|
||||
name: "End Scope",
|
||||
category: "Ignore",
|
||||
identifier: NodeImplementation::proto("graphene_core::memo::EndLetNode<_>"),
|
||||
identifier: NodeImplementation::proto("graphene_core::memo::EndLetNode<_, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType {
|
||||
name: "Scope",
|
||||
|
||||
+1
-1
@@ -546,7 +546,7 @@ fn gradient_row(row: &mut Vec<WidgetHolder>, positions: &Vec<(f64, Option<Color>
|
||||
move |_: &IconButton| {
|
||||
let mut new_positions = positions.clone();
|
||||
|
||||
// Blend linearly between the two colours.
|
||||
// Blend linearly between the two colors.
|
||||
let get_color = |index: usize| match (new_positions[index].1, new_positions.get(index + 1).and_then(|x| x.1)) {
|
||||
(Some(a), Some(b)) => Color::from_rgbaf32((a.r() + b.r()) / 2., (a.g() + b.g()) / 2., (a.b() + b.b()) / 2., ((a.a() + b.a()) / 2.).clamp(0., 1.)),
|
||||
(Some(v), _) | (_, Some(v)) => Some(v),
|
||||
|
||||
@@ -9,13 +9,13 @@ use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscrimina
|
||||
use document_legacy::{LayerId, Operation};
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
|
||||
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graph_craft::graphene_compiler::Compiler;
|
||||
use graph_craft::imaginate_input::ImaginatePreferences;
|
||||
use graph_craft::{concrete, Type};
|
||||
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||
use graphene_core::raster::{Image, ImageFrame};
|
||||
use graphene_core::renderer::{ClickTarget, SvgSegment, SvgSegmentList};
|
||||
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegment, SvgSegmentList};
|
||||
use graphene_core::text::FontCache;
|
||||
use graphene_core::transform::{Footprint, Transform};
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
@@ -82,6 +82,7 @@ pub(crate) struct GenerationResponse {
|
||||
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
new_transforms: HashMap<LayerNodeIdentifier, DAffine2>,
|
||||
new_upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
enum NodeGraphUpdate {
|
||||
@@ -160,6 +161,7 @@ impl NodeRuntime {
|
||||
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
|
||||
new_transforms: self.transforms.clone().into_iter().map(|(id, transform)| (LayerNodeIdentifier::new_unchecked(id), transform)).collect(),
|
||||
new_upstream_transforms: self.upstream_transforms.clone(),
|
||||
transform,
|
||||
};
|
||||
self.sender.send_generation_response(response);
|
||||
}
|
||||
@@ -167,7 +169,7 @@ impl NodeRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_network<'a>(&'a mut self, path: &[LayerId], graph: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> (Result<TaggedValue, String>, MonitorNodes) {
|
||||
async fn execute_network<'a>(&'a mut self, path: &[LayerId], mut graph: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> (Result<TaggedValue, String>, MonitorNodes) {
|
||||
if self.wasm_io.is_none() {
|
||||
self.wasm_io = Some(WasmApplicationIo::new().await);
|
||||
}
|
||||
@@ -512,6 +514,7 @@ impl NodeGraphExecutor {
|
||||
new_click_targets,
|
||||
new_transforms,
|
||||
new_upstream_transforms,
|
||||
transform,
|
||||
}) => {
|
||||
self.thumbnails = new_thumbnails;
|
||||
document.metadata.update_transforms(new_transforms, new_upstream_transforms);
|
||||
@@ -519,7 +522,7 @@ impl NodeGraphExecutor {
|
||||
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {e:?}"))?;
|
||||
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||
responses.extend(updates);
|
||||
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
|
||||
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses, execution_context.document_id)?;
|
||||
responses.add(DocumentMessage::LayerChanged {
|
||||
affected_layer_path: execution_context.layer_path,
|
||||
});
|
||||
@@ -537,56 +540,40 @@ impl NodeGraphExecutor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
|
||||
fn render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
use graphene_core::renderer::{ImageRenderMode, RenderParams, SvgRender};
|
||||
|
||||
// Setup rendering
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::BlobUrl, None, false);
|
||||
|
||||
// Render SVG
|
||||
render_object.render_svg(&mut render, &render_params);
|
||||
|
||||
// Concatenate the defs and the SVG into one string
|
||||
render.wrap_with_transform(transform);
|
||||
let svg = render.svg.to_string();
|
||||
|
||||
// Send to frontend
|
||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
}
|
||||
|
||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, transform: DAffine2, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
|
||||
self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty()));
|
||||
match node_graph_output {
|
||||
TaggedValue::VectorData(vector_data) => {
|
||||
// Update the cached vector data on the layer
|
||||
let transform = vector_data.transform.to_cols_array();
|
||||
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
||||
responses.add(Operation::SetVectorData { path: layer_path, vector_data });
|
||||
}
|
||||
TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) => {
|
||||
let transform = transform.to_cols_array();
|
||||
responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform });
|
||||
responses.add(Operation::SetSurface { path: layer_path, surface_id });
|
||||
}
|
||||
TaggedValue::ImageFrame(ImageFrame { image, transform }) => {
|
||||
// Don't update the frame's transform if the new transform is DAffine2::ZERO.
|
||||
let transform = (!transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON)).then_some(transform.to_cols_array());
|
||||
|
||||
// If no image was generated, clear the frame
|
||||
if image.width == 0 || image.height == 0 {
|
||||
responses.add(DocumentMessage::FrameClear);
|
||||
|
||||
// Update the transform based on the graph output
|
||||
if let Some(transform) = transform {
|
||||
responses.add(Operation::SetLayerTransform { path: layer_path, transform });
|
||||
}
|
||||
} else {
|
||||
// Update the image data
|
||||
let image_data = vec![Self::to_frontend_image_data(image, transform, &layer_path, None, None)?];
|
||||
responses.add(FrontendMessage::UpdateImageData { document_id, image_data });
|
||||
}
|
||||
}
|
||||
TaggedValue::Artboard(artboard) => {
|
||||
warn!("Rendered graph produced artboard (which is not currently rendered): {artboard:#?}");
|
||||
return Err("Artboard (see console)".to_string());
|
||||
}
|
||||
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => {
|
||||
// Send to frontend
|
||||
//log::debug!("svg: {svg}");
|
||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
//responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
|
||||
//return Err("Graphic group (see console)".to_string());
|
||||
}
|
||||
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => {
|
||||
// Send to frontend
|
||||
//log::debug!("svg: {svg}");
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
//responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
let matrix = frame
|
||||
.transform
|
||||
.to_cols_array()
|
||||
@@ -600,30 +587,14 @@ impl NodeGraphExecutor {
|
||||
1920, 1080, matrix, frame.surface_id.0
|
||||
);
|
||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
|
||||
//return Err("Graphic group (see console)".to_string());
|
||||
}
|
||||
TaggedValue::GraphicGroup(graphic_group) => {
|
||||
use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender};
|
||||
|
||||
// Setup rendering
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::BlobUrl, None, false);
|
||||
|
||||
// Render svg
|
||||
graphic_group.render_svg(&mut render, &render_params);
|
||||
|
||||
// Conctenate the defs and the svg into one string
|
||||
let mut svg = "<defs>".to_string();
|
||||
svg.push_str(&render.svg_defs);
|
||||
svg.push_str("</defs>");
|
||||
use std::fmt::Write;
|
||||
write!(svg, "{}", render.svg).unwrap();
|
||||
|
||||
// Send to frontend
|
||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
}
|
||||
TaggedValue::Bool(render_object) => Self::render(render_object, transform, responses),
|
||||
TaggedValue::String(render_object) => Self::render(render_object, transform, responses),
|
||||
TaggedValue::F32(render_object) => Self::render(render_object, transform, responses),
|
||||
TaggedValue::F64(render_object) => Self::render(render_object, transform, responses),
|
||||
TaggedValue::OptionalColor(render_object) => Self::render(render_object, transform, responses),
|
||||
TaggedValue::VectorData(render_object) => Self::render(render_object, transform, responses),
|
||||
TaggedValue::ImageFrame(render_object) => Self::render(render_object, transform, responses),
|
||||
_ => {
|
||||
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
|
||||
}
|
||||
@@ -643,6 +614,6 @@ impl NodeGraphExecutor {
|
||||
return;
|
||||
}
|
||||
}
|
||||
warn!("Recieved blob url for invalid segment")
|
||||
warn!("Received blob url for invalid segment")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user