Skip to content

Commit 085b91f

Browse files
committed
Add new string processing nodes
1 parent 1d03372 commit 085b91f

File tree

8 files changed

+375
-5
lines changed

8 files changed

+375
-5
lines changed

Cargo.lock

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ log = "0.4"
104104
bitflags = { version = "2.4", features = ["serde"] }
105105
ctor = "0.2"
106106
convert_case = "0.8"
107+
titlecase = "3.6"
108+
unicode-segmentation = "1.13.2"
107109
indoc = "2.0.5"
108110
derivative = "2.2"
109111
thiserror = "2"

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,7 @@ fn static_node_properties() -> NodeProperties {
20352035
map.insert("selective_color_properties".to_string(), Box::new(node_properties::selective_color_properties));
20362036
map.insert("exposure_properties".to_string(), Box::new(node_properties::exposure_properties));
20372037
map.insert("math_properties".to_string(), Box::new(node_properties::math_properties));
2038+
map.insert("string_capitalization_properties".to_string(), Box::new(node_properties::string_capitalization_properties));
20382039
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
20392040
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
20402041
map.insert("spiral_properties".to_string(), Box::new(node_properties::spiral_properties));

editor/src/messages/portfolio/document/node_graph/node_properties.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use graph_craft::{Type, concrete};
1616
use graphene_std::NodeInputDecleration;
1717
use graphene_std::animation::RealTimeMode;
1818
use graphene_std::extract_xy::XY;
19+
use graphene_std::logic::StringCapitalization;
1920
use graphene_std::raster::curve::Curve;
2021
use graphene_std::raster::{
2122
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
@@ -244,6 +245,7 @@ pub(crate) fn property_from_type(
244245
Some(x) if x == TypeId::of::<RedGreenBlue>() => enum_choice::<RedGreenBlue>().for_socket(default_info).property_row(),
245246
Some(x) if x == TypeId::of::<RedGreenBlueAlpha>() => enum_choice::<RedGreenBlueAlpha>().for_socket(default_info).property_row(),
246247
Some(x) if x == TypeId::of::<XY>() => enum_choice::<XY>().for_socket(default_info).property_row(),
248+
Some(x) if x == TypeId::of::<StringCapitalization>() => enum_choice::<StringCapitalization>().for_socket(default_info).property_row(),
247249
Some(x) if x == TypeId::of::<NoiseType>() => enum_choice::<NoiseType>().for_socket(default_info).property_row(),
248250
Some(x) if x == TypeId::of::<FractalType>() => enum_choice::<FractalType>().for_socket(default_info).disabled(false).property_row(),
249251
Some(x) if x == TypeId::of::<CellularDistanceFunction>() => enum_choice::<CellularDistanceFunction>().for_socket(default_info).disabled(false).property_row(),
@@ -1588,6 +1590,101 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
15881590
vec![LayoutGroup::row(exposure), LayoutGroup::row(offset), LayoutGroup::row(gamma_correction)]
15891591
}
15901592

1593+
pub(crate) fn string_capitalization_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
1594+
use graphene_std::logic::string_capitalization::*;
1595+
1596+
// Read the current values before borrowing context mutably for widgets
1597+
let (is_simple_case, use_joiner_enabled, joiner_value) = match get_document_node(node_id, context) {
1598+
Ok(document_node) => {
1599+
let is_simple = matches!(
1600+
document_node.inputs.get(CapitalizationInput::INDEX).and_then(|input| input.as_value()),
1601+
Some(TaggedValue::StringCapitalization(StringCapitalization::LowerCase | StringCapitalization::UpperCase))
1602+
);
1603+
let use_joiner = match document_node.inputs.get(UseJoinerInput::INDEX).and_then(|input| input.as_value()) {
1604+
Some(&TaggedValue::Bool(x)) => x,
1605+
_ => true,
1606+
};
1607+
let joiner = match document_node.inputs.get(JoinerInput::INDEX).and_then(|input| input.as_non_exposed_value()) {
1608+
Some(TaggedValue::String(x)) => Some(x.clone()),
1609+
_ => None,
1610+
};
1611+
(is_simple, use_joiner, joiner)
1612+
}
1613+
Err(err) => {
1614+
log::error!("Could not get document node in string_capitalization_properties: {err}");
1615+
return Vec::new();
1616+
}
1617+
};
1618+
1619+
// The joiner controls are disabled when lowercase/UPPERCASE are selected (they don't use word boundaries)
1620+
let joiner_disabled = is_simple_case || !use_joiner_enabled;
1621+
1622+
let capitalization = enum_choice::<StringCapitalization>()
1623+
.for_socket(ParameterWidgetsInfo::new(node_id, CapitalizationInput::INDEX, true, context))
1624+
.property_row();
1625+
1626+
// Joiner row: the UseJoiner checkbox is drawn in the assist area, followed by the Joiner text input
1627+
let mut joiner_widgets = start_widgets(ParameterWidgetsInfo::new(node_id, JoinerInput::INDEX, false, context));
1628+
if let Some(joiner) = joiner_value {
1629+
let joiner_is_empty = joiner.is_empty();
1630+
joiner_widgets.extend_from_slice(&[
1631+
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
1632+
Separator::new(SeparatorStyle::Related).widget_instance(),
1633+
CheckboxInput::new(use_joiner_enabled)
1634+
.disabled(is_simple_case)
1635+
.on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, UseJoinerInput::INDEX))
1636+
.on_commit(commit_value)
1637+
.widget_instance(),
1638+
Separator::new(SeparatorStyle::Related).widget_instance(),
1639+
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
1640+
TextInput::new(joiner)
1641+
.placeholder(if joiner_is_empty { "Empty" } else { "" })
1642+
.disabled(joiner_disabled)
1643+
.on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, JoinerInput::INDEX))
1644+
.on_commit(commit_value)
1645+
.widget_instance(),
1646+
]);
1647+
}
1648+
1649+
// Preset buttons for common joiner values, indented to align with the input field
1650+
let mut joiner_preset_buttons = vec![TextLabel::new("").widget_instance()];
1651+
add_blank_assist(&mut joiner_preset_buttons);
1652+
joiner_preset_buttons.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
1653+
for (label, value, tooltip) in [
1654+
("Empty", "", "Join words without any separator."),
1655+
("Space", " ", "Join words with a space."),
1656+
("Kebab", "-", "Join words with a hyphen."),
1657+
("Snake", "_", "Join words with an underscore."),
1658+
] {
1659+
let value = value.to_string();
1660+
joiner_preset_buttons.push(
1661+
TextButton::new(label)
1662+
.tooltip_description(tooltip)
1663+
.disabled(is_simple_case)
1664+
.on_update(move |_: &TextButton| Message::Batched {
1665+
messages: Box::new([
1666+
NodeGraphMessage::SetInputValue {
1667+
node_id,
1668+
input_index: UseJoinerInput::INDEX,
1669+
value: TaggedValue::Bool(true),
1670+
}
1671+
.into(),
1672+
NodeGraphMessage::SetInputValue {
1673+
node_id,
1674+
input_index: JoinerInput::INDEX,
1675+
value: TaggedValue::String(value.clone()),
1676+
}
1677+
.into(),
1678+
]),
1679+
})
1680+
.on_commit(commit_value)
1681+
.widget_instance(),
1682+
);
1683+
}
1684+
1685+
vec![capitalization, LayoutGroup::row(joiner_widgets), LayoutGroup::row(joiner_preset_buttons)]
1686+
}
1687+
15911688
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
15921689
use graphene_std::vector::generator_nodes::rectangle::*;
15931690

node-graph/graph-craft/src/document/value.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ tagged_value! {
243243
LuminanceCalculation(raster_nodes::adjustments::LuminanceCalculation),
244244
QRCodeErrorCorrectionLevel(vector_nodes::generator_nodes::QRCodeErrorCorrectionLevel),
245245
XY(graphene_core::extract_xy::XY),
246+
StringCapitalization(graphene_core::logic::StringCapitalization),
246247
RedGreenBlue(raster_nodes::adjustments::RedGreenBlue),
247248
RedGreenBlueAlpha(raster_nodes::adjustments::RedGreenBlueAlpha),
248249
RealTimeMode(graphene_core::animation::RealTimeMode),

node-graph/interpreted-executor/src/node_registry.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
113113
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
114114
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::LuminanceCalculation]),
115115
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),
116+
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::logic::StringCapitalization]),
116117
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlue]),
117118
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlueAlpha]),
118119
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),
@@ -203,6 +204,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
203204
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),
204205
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]),
205206
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),
207+
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::logic::StringCapitalization]),
206208
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),
207209
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),
208210
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),

node-graph/nodes/gcore/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ dyn-any = { workspace = true }
2828
glam = { workspace = true }
2929
log = { workspace = true }
3030
serde_json = { workspace = true }
31+
convert_case = { workspace = true }
32+
titlecase = { workspace = true }
33+
unicode-segmentation = { workspace = true }
3134

3235
# Optional workspace dependencies
3336
serde = { workspace = true, optional = true }

0 commit comments

Comments
 (0)