mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 01:58:11 +08:00
Migrate text layers to nodes (#1155)
* Initial work towards text to node * Add the text generate node * Implement live edit * Fix merge error * Cleanup text tool * Implement text * Fix transforms * Fix broken image frame * Double click to edit text * Fix rendering text on load * Moving whilst editing * Better text properties * Prevent changing vector when there is a Text node * Push node api * Use node fn macro * Stable ids * Image module as a seperate file * Explain check for "Input Frame" node * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
271f9d5158
commit
ef93f8442a
@@ -176,10 +176,6 @@ pub enum DocumentMessage {
|
||||
SetSnapping {
|
||||
snap: bool,
|
||||
},
|
||||
SetTextboxEditability {
|
||||
path: Vec<LayerId>,
|
||||
editable: bool,
|
||||
},
|
||||
SetViewMode {
|
||||
view_mode: ViewMode,
|
||||
},
|
||||
|
||||
@@ -29,13 +29,12 @@ use document_legacy::layers::blend_mode::BlendMode;
|
||||
use document_legacy::layers::folder_layer::FolderLayer;
|
||||
use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||
use document_legacy::layers::nodegraph_layer::CachedOutputData;
|
||||
use document_legacy::layers::style::{Fill, RenderData, ViewMode};
|
||||
use document_legacy::layers::text_layer::Font;
|
||||
use document_legacy::layers::style::{RenderData, ViewMode};
|
||||
use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||
use graphene_core::raster::{Color, ImageFrame};
|
||||
use graphene_core::Cow;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::text::Font;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -904,24 +903,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
SetSnapping { snap } => {
|
||||
self.snapping_enabled = snap;
|
||||
}
|
||||
SetTextboxEditability { path, editable } => {
|
||||
let text = self.document_legacy.layer(&path).unwrap().as_text().unwrap();
|
||||
responses.push_back(DocumentOperation::SetTextEditability { path, editable }.into());
|
||||
if editable {
|
||||
let color = if let Fill::Solid(solid_color) = text.path_style.fill() { *solid_color } else { Color::BLACK };
|
||||
responses.push_back(
|
||||
FrontendMessage::DisplayEditableTextbox {
|
||||
text: text.text.clone(),
|
||||
line_width: text.line_width,
|
||||
font_size: text.size,
|
||||
color,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
responses.push_back(FrontendMessage::DisplayRemoveEditableTextbox.into());
|
||||
}
|
||||
}
|
||||
SetViewMode { view_mode } => {
|
||||
self.view_mode = view_mode;
|
||||
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
|
||||
@@ -1055,37 +1036,41 @@ impl DocumentMessageHandler {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Find the primary input type of the node graph
|
||||
let primary_input_type = node_network.input_types().next().clone();
|
||||
let response = match primary_input_type {
|
||||
// Only calclate the frame if the primary input is an image
|
||||
Some(ty) if ty == concrete!(ImageFrame<Color>) => {
|
||||
// Calculate the size of the region to be exported
|
||||
let old_transforms = self.remove_document_transform();
|
||||
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
|
||||
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||
// Check if we use the "Input Frame" node.
|
||||
// TODO: Remove once rasterization is moved into a node.
|
||||
let input_frame = node_network.nodes.iter().find(|(_, node)| node.name == "Input Frame");
|
||||
let input_node_id = input_frame.map(|(&id, _)| id);
|
||||
let primary_input_type = input_node_id.filter(|&target_node_id| node_network.connected_to_output(target_node_id, true));
|
||||
|
||||
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
||||
self.restore_document_transform(old_transforms);
|
||||
// Only calculate the frame if the primary input is an image
|
||||
let response = if primary_input_type.is_some() {
|
||||
// Calculate the size of the region to be exported
|
||||
let old_transforms = self.remove_document_transform();
|
||||
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
|
||||
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||
|
||||
FrontendMessage::TriggerNodeGraphFrameGenerate {
|
||||
document_id,
|
||||
layer_path,
|
||||
svg,
|
||||
size,
|
||||
imaginate_node,
|
||||
}
|
||||
.into()
|
||||
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
||||
self.restore_document_transform(old_transforms);
|
||||
|
||||
FrontendMessage::TriggerNodeGraphFrameGenerate {
|
||||
document_id,
|
||||
layer_path,
|
||||
svg,
|
||||
size,
|
||||
imaginate_node,
|
||||
}
|
||||
// Skip processing under node graph frame input if not connected
|
||||
_ => PortfolioMessage::ProcessNodeGraphFrame {
|
||||
.into()
|
||||
}
|
||||
// Skip processing under node graph frame input if not connected
|
||||
else {
|
||||
PortfolioMessage::ProcessNodeGraphFrame {
|
||||
document_id,
|
||||
layer_path,
|
||||
image_data: Default::default(),
|
||||
size: (0, 0),
|
||||
imaginate_node,
|
||||
}
|
||||
.into(),
|
||||
.into()
|
||||
};
|
||||
Some(response)
|
||||
}
|
||||
@@ -1622,13 +1607,21 @@ impl DocumentMessageHandler {
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
LayerDataType::Text(text) => {
|
||||
fonts.insert(text.font.clone());
|
||||
}
|
||||
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||
if node_graph_frame.cached_output_data == CachedOutputData::None {
|
||||
responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path: path.clone() });
|
||||
}
|
||||
for node in node_graph_frame.network.nodes.values() {
|
||||
for input in &node.inputs {
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Font(font),
|
||||
..
|
||||
} = input
|
||||
{
|
||||
fonts.insert(font.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerIdentifier,
|
||||
fill: Fill,
|
||||
},
|
||||
|
||||
UpdateBounds {
|
||||
layer: LayerIdentifier,
|
||||
old_bounds: [DVec2; 2],
|
||||
new_bounds: [DVec2; 2],
|
||||
},
|
||||
StrokeSet {
|
||||
layer: LayerIdentifier,
|
||||
stroke: Stroke,
|
||||
|
||||
+26
-13
@@ -157,7 +157,27 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
fn update_bounds(&mut self, [old_bounds_min, old_bounds_max]: [DVec2; 2], [new_bounds_min, new_bounds_max]: [DVec2; 2]) {
|
||||
self.modify_inputs("Transform", false, |inputs| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let normalized_pivot = transform_utils::get_current_normalized_pivot(inputs);
|
||||
|
||||
let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalized_pivot + old_bounds_min;
|
||||
let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalized_pivot + new_bounds_min;
|
||||
let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot);
|
||||
let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot);
|
||||
|
||||
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
}
|
||||
|
||||
fn vector_modify(&mut self, modification: VectorDataModification) {
|
||||
// TODO: Allow modifying a graph with a "Text" node.
|
||||
if self.network.nodes.values().any(|node| node.name == "Text") {
|
||||
return;
|
||||
}
|
||||
|
||||
let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE];
|
||||
let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE];
|
||||
|
||||
@@ -186,18 +206,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||
});
|
||||
|
||||
self.modify_inputs("Transform", false, |inputs| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let normalized_pivot = transform_utils::get_current_normalized_pivot(inputs);
|
||||
|
||||
let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalized_pivot + old_bounds_min;
|
||||
let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalized_pivot + new_bounds_min;
|
||||
let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot);
|
||||
let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot);
|
||||
|
||||
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
self.update_bounds([old_bounds_min, old_bounds_max], [new_bounds_min, new_bounds_max]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +220,11 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
responses.add(Operation::SetLayerFill { path: layer, fill });
|
||||
}
|
||||
}
|
||||
|
||||
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.update_bounds(old_bounds, new_bounds);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::StrokeSet { layer, stroke } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.stroke_set(stroke);
|
||||
|
||||
+75
-39
@@ -1,4 +1,5 @@
|
||||
use super::{node_properties, FrontendGraphDataType, FrontendNodeType};
|
||||
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
|
||||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
@@ -8,6 +9,7 @@ use graph_craft::document::*;
|
||||
use graph_craft::imaginate_input::ImaginateSamplingMethod;
|
||||
use graph_craft::NodeIdentifier;
|
||||
use graphene_core::raster::{BlendMode, Color, Image, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::vector::VectorData;
|
||||
use graphene_core::*;
|
||||
|
||||
@@ -160,8 +162,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)],
|
||||
nodes: [DocumentNode {
|
||||
name: "Identity".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
|
||||
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ExtractImageFrame")),
|
||||
metadata: Default::default(),
|
||||
}]
|
||||
.into_iter()
|
||||
@@ -193,7 +195,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "SetNode".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
||||
inputs: vec![NodeInput::Network(concrete!(EditorApi))],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
|
||||
metadata: Default::default(),
|
||||
},
|
||||
@@ -226,7 +228,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
inputs: vec![DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
default: NodeInput::value(TaggedValue::EditorApi(EditorApi::empty()), true),
|
||||
}],
|
||||
outputs: vec![
|
||||
DocumentOutputType {
|
||||
@@ -828,6 +830,19 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Text",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_core::text::TextGenerator<_, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::none(),
|
||||
DocumentInputType::value("Text", TaggedValue::String("hello world".to_string()), false),
|
||||
DocumentInputType::value("Font", TaggedValue::Font(Font::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into())), false),
|
||||
DocumentInputType::value("Size", TaggedValue::F64(24.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::node_section_font,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Transform",
|
||||
category: "Transform",
|
||||
@@ -1048,23 +1063,23 @@ pub fn wrap_network_in_scope(network: NodeNetwork) -> NodeNetwork {
|
||||
}
|
||||
|
||||
pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork {
|
||||
NodeNetwork {
|
||||
let mut network = NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
resolve_document_node_type("Input Frame")
|
||||
.expect("Input Frame node does not exist")
|
||||
.to_document_node_default_inputs([], DocumentNodeMetadata::position((8, 4))),
|
||||
resolve_document_node_type("Output")
|
||||
.expect("Output node does not exist")
|
||||
.to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
network.push_node(
|
||||
resolve_document_node_type("Input Frame")
|
||||
.expect("Input Frame node does not exist")
|
||||
.to_document_node_default_inputs([], DocumentNodeMetadata::position((8, 4))),
|
||||
false,
|
||||
);
|
||||
network.push_node(
|
||||
resolve_document_node_type("Output")
|
||||
.expect("Output node does not exist")
|
||||
.to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
|
||||
false,
|
||||
);
|
||||
network
|
||||
}
|
||||
|
||||
pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGroupId>>) -> NodeNetwork {
|
||||
@@ -1074,27 +1089,48 @@ pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGrou
|
||||
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist");
|
||||
let output = resolve_document_node_type("Output").expect("Output node does not exist");
|
||||
|
||||
let mut pos = 0;
|
||||
let mut next_pos = || {
|
||||
let node_pos = DocumentNodeMetadata::position((pos, 4));
|
||||
pos += 8;
|
||||
node_pos
|
||||
let mut network = NodeNetwork {
|
||||
inputs: vec![0],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
NodeNetwork {
|
||||
inputs: vec![],
|
||||
outputs: vec![NodeOutput::new(4, 0)],
|
||||
nodes: [
|
||||
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], next_pos()),
|
||||
transform.to_document_node_default_inputs([Some(NodeInput::node(0, 0))], next_pos()),
|
||||
fill.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], next_pos()),
|
||||
stroke.to_document_node_default_inputs([Some(NodeInput::node(2, 0))], next_pos()),
|
||||
output.to_document_node_default_inputs([Some(NodeInput::node(3, 0))], next_pos()),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
network.push_node(
|
||||
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], DocumentNodeMetadata::position((0, 4))),
|
||||
false,
|
||||
);
|
||||
network.push_node(transform.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network.push_node(fill.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network.push_node(stroke.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network.push_node(output.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network
|
||||
}
|
||||
|
||||
pub fn new_text_network(text: String, font: Font, size: f64) -> NodeNetwork {
|
||||
let text_generator = resolve_document_node_type("Text").expect("Text node does not exist");
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
|
||||
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist");
|
||||
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist");
|
||||
let output = resolve_document_node_type("Output").expect("Output node does not exist");
|
||||
|
||||
let mut network = NodeNetwork {
|
||||
inputs: vec![0],
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
network.push_node(
|
||||
text_generator.to_document_node(
|
||||
[
|
||||
NodeInput::Network(concrete!(graphene_core::EditorApi)),
|
||||
NodeInput::value(TaggedValue::String(text.clone()), false),
|
||||
NodeInput::value(TaggedValue::Font(font.clone()), false),
|
||||
NodeInput::value(TaggedValue::F64(size), false),
|
||||
],
|
||||
DocumentNodeMetadata::position((0, 4)),
|
||||
),
|
||||
false,
|
||||
);
|
||||
network.push_node(transform.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network.push_node(fill.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network.push_node(stroke.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network.push_node(output.to_document_node_default_inputs([None], Default::default()), true);
|
||||
network
|
||||
}
|
||||
|
||||
+50
-3
@@ -9,7 +9,9 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::imaginate_input::*;
|
||||
use graphene_core::raster::{BlendMode, Color, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin};
|
||||
use graphene_core::EditorApi;
|
||||
|
||||
use super::document_node_types::NodePropertiesContext;
|
||||
use super::{FrontendGraphDataType, IMAGINATE_NODE};
|
||||
@@ -155,6 +157,38 @@ fn vec_f32_input(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
||||
}
|
||||
widgets
|
||||
}
|
||||
fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
|
||||
let mut first_widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
let mut second_widgets = None;
|
||||
|
||||
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Font(font),
|
||||
exposed: false,
|
||||
} = &document_node.inputs[index]
|
||||
{
|
||||
first_widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
FontInput::new(font.font_family.clone(), font.font_style.clone())
|
||||
.on_update(update_value(from_font_input, node_id, index))
|
||||
.widget_holder(),
|
||||
]);
|
||||
second_widgets = Some(vec![
|
||||
TextLabel::new("").widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
FontInput::new(font.font_family.clone(), font.font_style.clone())
|
||||
.is_style_picker(true)
|
||||
.on_update(update_value(from_font_input, node_id, index))
|
||||
.widget_holder(),
|
||||
]);
|
||||
}
|
||||
(first_widgets, second_widgets)
|
||||
}
|
||||
|
||||
fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, number_props: NumberInput, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
|
||||
@@ -847,6 +881,19 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
||||
vec![translation, rotation, scale]
|
||||
}
|
||||
|
||||
pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let text = text_area_widget(document_node, node_id, 1, "Text", true);
|
||||
let (font, style) = font_inputs(document_node, node_id, 2, "Font", true);
|
||||
let size = number_widget(document_node, node_id, 3, "Size", NumberInput::default().unit(" px").min(1.), true);
|
||||
|
||||
let mut result = vec![LayoutGroup::Row { widgets: text }, LayoutGroup::Row { widgets: font }];
|
||||
if let Some(style) = style {
|
||||
result.push(LayoutGroup::Row { widgets: style });
|
||||
}
|
||||
result.push(LayoutGroup::Row { widgets: size });
|
||||
result
|
||||
}
|
||||
|
||||
pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let imaginate_node = [context.nested_path, &[node_id]].concat();
|
||||
let layer_path = context.layer_path.to_vec();
|
||||
@@ -1098,9 +1145,9 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
};
|
||||
|
||||
// Create the input to the graph using an empty image
|
||||
let image_frame = std::borrow::Cow::Owned(graphene_core::raster::ImageFrame {
|
||||
image: graphene_core::raster::Image::empty(),
|
||||
transform: glam::DAffine2::IDENTITY,
|
||||
let image_frame = std::borrow::Cow::Owned(EditorApi {
|
||||
image_frame: None,
|
||||
font_cache: Some(&context.persistent_data.font_cache),
|
||||
});
|
||||
// Compute the transform input to the node graph frame
|
||||
let image_frame: graphene_core::raster::ImageFrame<Color> = context.executor.compute_input(context.network, &imaginate_node, 0, image_frame).unwrap_or_default();
|
||||
|
||||
@@ -19,11 +19,9 @@ pub enum PropertiesPanelMessage {
|
||||
Deactivate,
|
||||
Init,
|
||||
ModifyFill { fill: Fill },
|
||||
ModifyFont { font_family: String, font_style: String, size: f64 },
|
||||
ModifyName { name: String },
|
||||
ModifyPreserveAspect { preserve_aspect: bool },
|
||||
ModifyStroke { stroke: Stroke },
|
||||
ModifyText { new_text: String },
|
||||
ModifyTransform { value: f64, transform_op: TransformOp },
|
||||
ResendActiveProperties,
|
||||
SetActiveLayers { paths: Vec<Vec<LayerId>>, document: TargetDocument },
|
||||
|
||||
-10
@@ -94,12 +94,6 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
ModifyFont { font_family, font_style, size } => {
|
||||
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
|
||||
self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size }, true, responses);
|
||||
responses.push_back(ResendActiveProperties.into());
|
||||
}
|
||||
ModifyTransform { value, transform_op } => {
|
||||
let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
|
||||
let layer = get_document(*target_document).layer(path).unwrap();
|
||||
@@ -124,10 +118,6 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
||||
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
self.create_document_operation(Operation::SetLayerStroke { path, stroke }, true, responses);
|
||||
}
|
||||
ModifyText { new_text } => {
|
||||
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
self.create_document_operation(Operation::SetTextContent { path, new_text }, true, responses);
|
||||
}
|
||||
SetPivot { new_position } => {
|
||||
let (layer, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
let position: Option<glam::DVec2> = new_position.into();
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup,
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widgets::assist_widgets::PivotAssist;
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton, TextButton};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, ColorInput, FontInput, NumberInput, NumberInputMode, RadioEntryData, RadioInput, TextAreaInput, TextInput};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, ColorInput, NumberInput, NumberInputMode, RadioEntryData, RadioInput, TextInput};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, TextLabel};
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
@@ -13,7 +13,6 @@ use crate::node_graph_executor::NodeGraphExecutor;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
|
||||
use document_legacy::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, RenderData, Stroke, ViewMode};
|
||||
use document_legacy::layers::text_layer::TextLayer;
|
||||
use graphene_core::raster::color::Color;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -265,11 +264,6 @@ pub fn register_artwork_layer_properties(
|
||||
tooltip: "Shape".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
LayerDataType::Text(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
icon: "NodeText".into(),
|
||||
tooltip: "Text".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
LayerDataType::NodeGraphFrame(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
icon: "Layer".into(),
|
||||
tooltip: "Layer".into(),
|
||||
@@ -311,14 +305,6 @@ pub fn register_artwork_layer_properties(
|
||||
vec![node_section_transform(layer, persistent_data), node_section_stroke(&shape.style.stroke().unwrap_or_default())]
|
||||
}
|
||||
}
|
||||
LayerDataType::Text(text) => {
|
||||
vec![
|
||||
node_section_transform(layer, persistent_data),
|
||||
node_section_font(text),
|
||||
node_section_fill(text.path_style.fill()).expect("Text should have fill"),
|
||||
node_section_stroke(&text.path_style.stroke().unwrap_or_default()),
|
||||
]
|
||||
}
|
||||
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||
let mut properties_sections = Vec::new();
|
||||
|
||||
@@ -522,115 +508,6 @@ fn node_section_transform(layer: &Layer, persistent_data: &PersistentData) -> La
|
||||
}
|
||||
}
|
||||
|
||||
fn node_section_font(layer: &TextLayer) -> LayoutGroup {
|
||||
let font = layer.font.clone();
|
||||
let size = layer.size;
|
||||
LayoutGroup::Section {
|
||||
name: "Font".into(),
|
||||
layout: vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Text".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::TextAreaInput(TextAreaInput {
|
||||
value: layer.text.clone(),
|
||||
on_update: WidgetCallback::new(|text_area: &TextAreaInput| PropertiesPanelMessage::ModifyText { new_text: text_area.value.clone() }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Font".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: false,
|
||||
font_family: layer.font.font_family.clone(),
|
||||
font_style: layer.font.font_style.clone(),
|
||||
on_update: WidgetCallback::new(move |font_input: &FontInput| {
|
||||
PropertiesPanelMessage::ModifyFont {
|
||||
font_family: font_input.font_family.clone(),
|
||||
font_style: font_input.font_style.clone(),
|
||||
size,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Style".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: true,
|
||||
font_family: layer.font.font_family.clone(),
|
||||
font_style: layer.font.font_style.clone(),
|
||||
on_update: WidgetCallback::new(move |font_input: &FontInput| {
|
||||
PropertiesPanelMessage::ModifyFont {
|
||||
font_family: font_input.font_family.clone(),
|
||||
font_style: font_input.font_style.clone(),
|
||||
size,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Size".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(layer.size),
|
||||
min: Some(1.),
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
PropertiesPanelMessage::ModifyFont {
|
||||
font_family: font.font_family.clone(),
|
||||
font_style: font.font_style.clone(),
|
||||
size: number_input.value.unwrap(),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn node_gradient_type(gradient: &Gradient) -> LayoutGroup {
|
||||
let selected_index = match gradient.gradient_type {
|
||||
GradientType::Linear => 0,
|
||||
|
||||
@@ -327,7 +327,6 @@ impl<'a> Selected<'a> {
|
||||
continue;
|
||||
};
|
||||
let Some(vector_data) = layer.as_vector_data() else {
|
||||
warn!("Didn't find a vectordata for {:?}", layer);
|
||||
continue;
|
||||
};
|
||||
let get_manipulator_point_position = |point_id: ManipulatorPointId| {
|
||||
|
||||
Reference in New Issue
Block a user