Fix the Text node's Max Width/Height parameters with OptionalF64 losing the value when unticked (#3643)
* WIP * Fix widget * Fix migration * Remove OptionalF64 * Custom attributes for optional f64 widget * Code review * Move comments to another PR --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
73682b482b
commit
c60ddcf875
|
|
@ -206,8 +206,10 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
Some(NodeInput::value(TaggedValue::F64(typesetting.font_size), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.line_height_ratio), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
|
||||
Some(NodeInput::value(TaggedValue::Bool(typesetting.max_width.is_some()), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.max_width.unwrap_or(100.)), false)),
|
||||
Some(NodeInput::value(TaggedValue::Bool(typesetting.max_width.is_some()), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.max_width.unwrap_or(100.)), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
||||
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ use graphene_std::extract_xy::XY;
|
|||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
#[allow(unused_imports)]
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::Vector;
|
||||
|
|
@ -1653,103 +1652,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||
properties: None,
|
||||
},
|
||||
// TODO: Auto-generate this from its proto node macro
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Text",
|
||||
category: "Text",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
|
||||
NodeInput::value(
|
||||
TaggedValue::Font(Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.into(), graphene_std::consts::DEFAULT_FONT_STYLE.into())),
|
||||
false,
|
||||
),
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().font_size), false),
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false),
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
|
||||
NodeInput::value(TaggedValue::TextAlign(text::TextAlign::default()), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![
|
||||
("Editor API", "TODO").into(),
|
||||
InputMetadata::with_name_description_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
|
||||
InputMetadata::with_name_description_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Size",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Line Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some("x".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Character Spacing",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Max Width",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Max Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Tilt",
|
||||
"Faux italic.",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(-85.),
|
||||
max: Some(85.),
|
||||
unit: Some("°".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
|
||||
("Per-Glyph Instances", "Splits each text glyph into its own row in the table of vector geometry.").into(),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Transform",
|
||||
category: "Math: Transform",
|
||||
|
|
@ -2297,6 +2199,43 @@ fn static_input_properties() -> InputProperties {
|
|||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
// The custom number input settings are only available on proto nodes
|
||||
"optional_f64".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let node_metadata = registry::NODE_METADATA.lock().unwrap();
|
||||
let mut number_input = NumberInput::default();
|
||||
if let Some(field) = context
|
||||
.network_interface
|
||||
.implementation(&node_id, context.selection_network_path)
|
||||
.and_then(|implementation| if let DocumentNodeImplementation::ProtoNode(id) = implementation { Some(id) } else { None })
|
||||
.and_then(|proto_node_identifier| node_metadata.get(proto_node_identifier))
|
||||
.and_then(|metadata| metadata.fields.get(index))
|
||||
{
|
||||
if let Some(unit) = field.unit {
|
||||
number_input = number_input.unit(unit);
|
||||
}
|
||||
if let Some(number_min) = field.number_min {
|
||||
number_input = number_input.min(number_min);
|
||||
}
|
||||
if let Some(number_max) = field.number_max {
|
||||
number_input = number_input.max(number_max);
|
||||
}
|
||||
if let Some((range_min, range_max)) = field.number_mode_range {
|
||||
number_input = number_input.range_min(Some(range_min));
|
||||
number_input = number_input.range_max(Some(range_max));
|
||||
}
|
||||
number_input = number_input.is_integer(false);
|
||||
if let Some(number_step) = field.number_step {
|
||||
number_input = number_input.step(number_step);
|
||||
}
|
||||
};
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
// NOTE: The bool input MUST be at the input index directly before the f64 input!
|
||||
widgets: node_properties::optional_f64_widget(ParameterWidgetsInfo::new(node_id, index, false, context), index - 1, number_input),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"vec2".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
|
|
|
|||
|
|
@ -939,6 +939,49 @@ pub fn progression_widget(parameter_widgets_info: ParameterWidgetsInfo, number_p
|
|||
widgets
|
||||
}
|
||||
|
||||
/// `parameter_widgets_info` is for the f64 parameter. `bool_input_index` is the input index of the bool parameter for the checkbox.
|
||||
pub fn optional_f64_widget(parameter_widgets_info: ParameterWidgetsInfo, bool_input_index: usize, number_props: NumberInput) -> Vec<WidgetInstance> {
|
||||
let ParameterWidgetsInfo {
|
||||
document_node,
|
||||
node_id,
|
||||
index: number_input_index,
|
||||
..
|
||||
} = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(number_input) = document_node.inputs.get(number_input_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
let Some(bool_input) = document_node.inputs.get(bool_input_index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
if let (Some(&TaggedValue::Bool(enabled)), Some(&TaggedValue::F64(number))) = (bool_input.as_non_exposed_value(), number_input.as_non_exposed_value()) {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
// The checkbox toggles if the value is Some or None
|
||||
CheckboxInput::new(enabled)
|
||||
.on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, bool_input_index))
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
number_props
|
||||
.value(Some(number))
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap_or_default()), node_id, number_input_index))
|
||||
.disabled(!enabled)
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
]);
|
||||
}
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetInstance> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
|
|
@ -982,27 +1025,6 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
|||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
]),
|
||||
Some(&TaggedValue::OptionalF64(x)) => {
|
||||
// TODO: Don't wipe out the previously set value (setting it back to the default of 100) when reenabling this checkbox back to Some from None
|
||||
let toggle_enabled = move |checkbox_input: &CheckboxInput| TaggedValue::OptionalF64(if checkbox_input.checked { Some(100.) } else { None });
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
// The checkbox toggles if the value is Some or None
|
||||
CheckboxInput::new(x.is_some())
|
||||
.on_update(update_value(toggle_enabled, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
number_props
|
||||
.value(x)
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::OptionalF64(x.value), node_id, index))
|
||||
.disabled(x.is_none())
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
]);
|
||||
}
|
||||
Some(&TaggedValue::DVec2(dvec2)) => widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||
number_props
|
||||
|
|
@ -1087,14 +1109,6 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
),
|
||||
TaggedValue::OptionalColorNotInTable(color) => widgets.push(
|
||||
color_button
|
||||
.value(color.map_or(FillChoice::None, FillChoice::Solid))
|
||||
.allow_none(true)
|
||||
.on_update(update_value(|input: &ColorInput| TaggedValue::OptionalColorNotInTable(input.value.as_solid()), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
),
|
||||
TaggedValue::Color(color_table) => widgets.push(
|
||||
color_button
|
||||
.value(match color_table.iter().next() {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
|
|||
"core::option::Option<alloc::sync::Arc<core_types::context::OwnedContextImpl>>",
|
||||
),
|
||||
("graphene_core::transform::Footprint", "graphene_core::transform::Footprint"),
|
||||
("\"OptionalF64\":", "\"F64\":"),
|
||||
];
|
||||
|
||||
pub struct NodeReplacement<'a> {
|
||||
|
|
@ -961,11 +962,48 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
|||
const REPLACEMENTS: &[(&str, &str)] = &[];
|
||||
|
||||
pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {
|
||||
let document_serialized_content = replace_optional_f64_null(&document_serialized_content);
|
||||
|
||||
TEXT_REPLACEMENTS
|
||||
.iter()
|
||||
.fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new))
|
||||
}
|
||||
|
||||
fn replace_optional_f64_null(input: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let mut last_end = 0;
|
||||
let key = "\"OptionalF64\":";
|
||||
|
||||
for (start, _) in input.match_indices(key) {
|
||||
let search_start = start + key.len();
|
||||
if search_start >= input.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut after_key_start = search_start;
|
||||
for (i, c) in input[search_start..].char_indices() {
|
||||
if !c.is_whitespace() {
|
||||
after_key_start = search_start + i;
|
||||
break;
|
||||
}
|
||||
// If we reach the end and it's all whitespace, update after_key_start
|
||||
if search_start + i + c.len_utf8() == input.len() {
|
||||
after_key_start = input.len();
|
||||
}
|
||||
}
|
||||
|
||||
if input[after_key_start..].starts_with("null") {
|
||||
result.push_str(&input[last_end..start]);
|
||||
result.push_str(key);
|
||||
result.push_str("0.0");
|
||||
last_end = after_key_start + "null".len();
|
||||
}
|
||||
}
|
||||
|
||||
result.push_str(&input[last_end..]);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn document_migration_reset_node_definition(document_serialized_content: &str) -> bool {
|
||||
// Upgrade a document being opened to use fresh copies of all nodes
|
||||
if document_serialized_content.contains("node_output_index") {
|
||||
|
|
@ -1035,7 +1073,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
// Only nodes that have not been modified and still refer to a definition can be updated
|
||||
let reference = document.network_interface.reference(node_id, network_path)?;
|
||||
|
||||
let inputs_count = node.inputs.len();
|
||||
let mut inputs_count = node.inputs.len();
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) && inputs_count == 8 {
|
||||
|
|
@ -1134,7 +1172,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
}
|
||||
|
||||
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count != 11 {
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count == 8 {
|
||||
let mut template: NodeTemplate = resolve_document_node_type(&reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut template);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;
|
||||
|
|
@ -1166,7 +1204,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
if inputs_count >= 7 {
|
||||
old_inputs[6].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false)
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().max_width.unwrap_or_default()), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
|
|
@ -1175,7 +1213,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
if inputs_count >= 8 {
|
||||
old_inputs[7].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false)
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().max_width.unwrap_or_default()), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
|
|
@ -1190,7 +1228,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 9),
|
||||
if inputs_count >= 11 {
|
||||
if inputs_count >= 10 {
|
||||
old_inputs[9].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::TextAlign(TextAlign::default()), false)
|
||||
|
|
@ -1206,6 +1244,49 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
},
|
||||
network_path,
|
||||
);
|
||||
inputs_count = 11
|
||||
}
|
||||
|
||||
// Insert bool parameters for `has_max_width` and `has_max_height`:
|
||||
// https://github.com/GraphiteEditor/Graphite/pull/3643
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count == 11 {
|
||||
let mut template: NodeTemplate = resolve_document_node_type(&reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut template);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;
|
||||
|
||||
// Copy over old inputs
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..=5 {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i].clone(), network_path);
|
||||
}
|
||||
|
||||
// Max Width
|
||||
let Some(&TaggedValue::F64(old_max_width)) = old_inputs[6].as_value() else { return None };
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(old_max_width != 0.), false), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
NodeInput::value(TaggedValue::F64(if old_max_width == 0. { 100. } else { old_max_width }), false),
|
||||
network_path,
|
||||
);
|
||||
|
||||
// Max Height
|
||||
let Some(&TaggedValue::F64(old_max_height)) = old_inputs[7].as_value() else { return None };
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 8), NodeInput::value(TaggedValue::Bool(old_max_height != 0.), false), network_path);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 9),
|
||||
NodeInput::value(TaggedValue::F64(if old_max_height == 0. { 100. } else { old_max_height }), false),
|
||||
network_path,
|
||||
);
|
||||
|
||||
// Copy over old inputs
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 10..=12 {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i - 2].clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
|
||||
|
|
|
|||
|
|
@ -388,23 +388,49 @@ pub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
|||
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?;
|
||||
|
||||
let Some(TaggedValue::String(text)) = &inputs[1].as_value() else { return None };
|
||||
let Some(TaggedValue::Font(font)) = &inputs[2].as_value() else { return None };
|
||||
let Some(&TaggedValue::F64(font_size)) = inputs[3].as_value() else { return None };
|
||||
let Some(&TaggedValue::F64(line_height_ratio)) = inputs[4].as_value() else { return None };
|
||||
let Some(&TaggedValue::F64(character_spacing)) = inputs[5].as_value() else { return None };
|
||||
let Some(&TaggedValue::OptionalF64(max_width)) = inputs[6].as_value() else { return None };
|
||||
let Some(&TaggedValue::OptionalF64(max_height)) = inputs[7].as_value() else { return None };
|
||||
let Some(&TaggedValue::F64(tilt)) = inputs[8].as_value() else { return None };
|
||||
let Some(&TaggedValue::TextAlign(align)) = inputs[9].as_value() else { return None };
|
||||
let Some(&TaggedValue::Bool(per_glyph_instances)) = inputs[10].as_value() else { return None };
|
||||
let Some(TaggedValue::String(text)) = &inputs[graphene_std::text::text::TextInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(TaggedValue::Font(font)) = &inputs[graphene_std::text::text::FontInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::F64(font_size)) = inputs[graphene_std::text::text::SizeInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::F64(line_height_ratio)) = inputs[graphene_std::text::text::LineHeightInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::F64(character_spacing)) = inputs[graphene_std::text::text::CharacterSpacingInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::Bool(has_max_width)) = inputs[graphene_std::text::text::HasMaxWidthInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::F64(max_width)) = inputs[graphene_std::text::text::MaxWidthInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::Bool(has_max_height)) = inputs[graphene_std::text::text::HasMaxHeightInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::F64(max_height)) = inputs[graphene_std::text::text::MaxHeightInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::F64(tilt)) = inputs[graphene_std::text::text::TiltInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::TextAlign(align)) = inputs[graphene_std::text::text::AlignInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::Bool(per_glyph_instances)) = inputs[graphene_std::text::text::SeparateGlyphElementsInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
line_height_ratio,
|
||||
max_width,
|
||||
max_width: has_max_width.then_some(max_width),
|
||||
max_height: has_max_height.then_some(max_height),
|
||||
character_spacing,
|
||||
max_height,
|
||||
tilt,
|
||||
align,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ use crate::messages::tool::common_functionality::utility_functions::text_boundin
|
|||
use crate::messages::tool::utility_types::ToolRefreshOptions;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping};
|
||||
use graphene_std::vector::style::Fill;
|
||||
use graphene_std::{Color, NodeInputDecleration};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct TextTool {
|
||||
|
|
@ -800,13 +800,22 @@ impl Fsm for TextToolFsmState {
|
|||
// Find the translation necessary from the original position in viewport space
|
||||
let translation_viewport = bounds.original_bound_transform.transform_vector2(translation_bounds_space);
|
||||
|
||||
// TODO: Don't set both max_width and max_height to true at the same time, only do one based on which edge is being dragged (or both if a corner is being dragged)
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 6),
|
||||
input: NodeInput::value(TaggedValue::OptionalF64(Some(size_layer.x)), false),
|
||||
input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxWidthInput::INDEX),
|
||||
input: NodeInput::value(TaggedValue::Bool(true), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 7),
|
||||
input: NodeInput::value(TaggedValue::OptionalF64(Some(size_layer.y)), false),
|
||||
input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxWidthInput::INDEX),
|
||||
input: NodeInput::value(TaggedValue::F64(size_layer.x), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxHeightInput::INDEX),
|
||||
input: NodeInput::value(TaggedValue::Bool(true), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxHeightInput::INDEX),
|
||||
input: NodeInput::value(TaggedValue::F64(size_layer.y), false),
|
||||
});
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: dragging_layer.id,
|
||||
|
|
|
|||
|
|
@ -173,9 +173,7 @@ tagged_value! {
|
|||
U64(u64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
OptionalF64(Option<f64>),
|
||||
ColorNotInTable(Color),
|
||||
OptionalColorNotInTable(Option<Color>),
|
||||
// ========================
|
||||
// LISTS OF PRIMITIVE TYPES
|
||||
// ========================
|
||||
|
|
@ -366,9 +364,9 @@ impl TaggedValue {
|
|||
x if x == TypeId::of::<u32>() => FromStr::from_str(string).map(TaggedValue::U32).ok()?,
|
||||
x if x == TypeId::of::<DVec2>() => to_dvec2(string).map(TaggedValue::DVec2)?,
|
||||
x if x == TypeId::of::<bool>() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?,
|
||||
x if x == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
|
||||
x if x == TypeId::of::<Color>() => to_color(string).map(TaggedValue::ColorNotInTable)?,
|
||||
x if x == TypeId::of::<Option<Color>>() => TaggedValue::ColorNotInTable(to_color(string)?),
|
||||
x if x == TypeId::of::<Table<Color>>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?,
|
||||
x if x == TypeId::of::<Fill>() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?,
|
||||
x if x == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
|
||||
_ => return None,
|
||||
|
|
|
|||
|
|
@ -90,18 +90,6 @@ impl From<Table<Color>> for Graphic {
|
|||
}
|
||||
}
|
||||
// Note: Table conversions handled by blanket impl in gcore
|
||||
|
||||
// Option<Color>
|
||||
impl From<Option<Color>> for Graphic {
|
||||
fn from(color: Option<Color>) -> Self {
|
||||
if let Some(color) = color {
|
||||
Graphic::Color(Table::new_from_element(color))
|
||||
} else {
|
||||
Graphic::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: Table conversions handled by blanket impl in gcore
|
||||
// Note: Table<Color> -> Option<Color> is in gcore (Color is defined there)
|
||||
|
||||
// GradientStops
|
||||
|
|
|
|||
|
|
@ -244,7 +244,6 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
|
|||
#[default(100.)]
|
||||
fill: Percentage,
|
||||
/// Whether the content inherits the alpha of the content beneath it.
|
||||
#[default(false)]
|
||||
clip: bool,
|
||||
) -> T {
|
||||
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
|
||||
|
|
|
|||
|
|
@ -3,41 +3,52 @@ use graph_craft::wasm_application_io::WasmEditorApi;
|
|||
use graphic_types::Vector;
|
||||
pub use text_nodes::*;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn text<'i: 'n>(
|
||||
_: impl Ctx,
|
||||
editor: &'i WasmEditorApi,
|
||||
#[scope("editor-api")] editor_resources: &'i WasmEditorApi,
|
||||
#[widget(ParsedWidgetOverride::Custom = "text_area")]
|
||||
#[default("Lorem ipsum")]
|
||||
text: String,
|
||||
font: Font,
|
||||
#[widget(ParsedWidgetOverride::Custom = "text_font")] font: Font,
|
||||
#[unit(" px")]
|
||||
#[default(24.)]
|
||||
font_size: f64,
|
||||
#[hard_min(1.)]
|
||||
size: f64,
|
||||
#[unit("x")]
|
||||
#[hard_min(0.)]
|
||||
#[step(0.1)]
|
||||
#[default(1.2)]
|
||||
line_height_ratio: f64,
|
||||
line_height: f64,
|
||||
#[unit(" px")]
|
||||
#[default(0.)]
|
||||
#[step(0.1)]
|
||||
character_spacing: f64,
|
||||
#[unit(" px")] max_width: Option<f64>,
|
||||
#[unit(" px")] max_height: Option<f64>,
|
||||
/// Faux italic.
|
||||
#[widget(ParsedWidgetOverride::Hidden)] has_max_width: bool,
|
||||
#[unit(" px")]
|
||||
#[hard_min(1.)]
|
||||
#[widget(ParsedWidgetOverride::Custom = "optional_f64")]
|
||||
max_width: f64,
|
||||
#[widget(ParsedWidgetOverride::Hidden)] has_max_height: bool,
|
||||
#[unit(" px")]
|
||||
#[hard_min(1.)]
|
||||
#[widget(ParsedWidgetOverride::Custom = "optional_f64")]
|
||||
max_height: f64,
|
||||
#[unit("°")]
|
||||
#[default(0.)]
|
||||
#[hard_min(-85.)]
|
||||
#[hard_max(85.)]
|
||||
tilt: f64,
|
||||
align: TextAlign,
|
||||
/// Splits each text glyph into its own row in the table of vector geometry.
|
||||
#[default(false)]
|
||||
per_glyph_instances: bool,
|
||||
#[widget(ParsedWidgetOverride::Custom = "text_align")] align: TextAlign,
|
||||
separate_glyph_elements: bool,
|
||||
) -> Table<Vector> {
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
line_height_ratio,
|
||||
font_size: size,
|
||||
line_height_ratio: line_height,
|
||||
character_spacing,
|
||||
max_width,
|
||||
max_height,
|
||||
max_width: has_max_width.then_some(max_width),
|
||||
max_height: has_max_height.then_some(max_height),
|
||||
tilt,
|
||||
align,
|
||||
};
|
||||
|
||||
to_path(&text, &font, &editor.font_cache, typesetting, per_glyph_instances)
|
||||
to_path(&text, &font, &editor_resources.font_cache, typesetting, separate_glyph_elements)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
|
|||
image.element.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>()
|
||||
}
|
||||
|
||||
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing workflows to continue.
|
||||
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
|
||||
#[node_macro::node(category("Web Request"))]
|
||||
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor_resources: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
|
||||
let Some(api) = editor_resources.application_io.as_ref() else {
|
||||
|
|
|
|||
|
|
@ -405,7 +405,6 @@ fn random(
|
|||
/// Seed to determine the unique variation of which number is generated.
|
||||
seed: u64,
|
||||
/// The smaller end of the range within which the random number is generated.
|
||||
#[default(0.)]
|
||||
min: f64,
|
||||
/// The larger end of the range within which the random number is generated.
|
||||
#[default(1.)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue