Add corner rounding to the Rectangle node (#1648)

* add skeleton implementation

* add corner rounding

* fix crash when `border_radius` is zero

* rename `Border Radius` to `Corner Radius`

* add clamped property

* add `TaggedValue::F64Array4`

* add frontend support for individual corner rounding

* added individual corner rounding

* fix rebase

* change default values when switching rounding type

* fix crash caused by negative scale

* remove `Any` trait

* add `Message::Batched`

* fix stale property bug

* add smarter clamping for individual rounding

* Rearrange widgets in properties panel

* update individual clamping algorithm

* add better variable names

* make variable names clearer

* Final code cleanup

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Karthik Prakash
2024-04-06 10:39:55 +05:30
committed by GitHub
parent d09e7eaf86
commit 438c45eb80
8 changed files with 221 additions and 17 deletions

View File

@@ -102,7 +102,9 @@ impl Dispatcher {
let font = Font::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into());
queue.add(FrontendMessage::TriggerFontLoad { font, is_default: true });
}
Message::Batched(messages) => {
messages.iter().for_each(|message| self.handle_message(message.to_owned()));
}
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());

View File

@@ -7,6 +7,7 @@ use graphite_proc_macros::*;
pub enum Message {
NoOp,
Init,
Batched(Box<[Message]>),
#[child]
Broadcast(BroadcastMessage),

View File

@@ -2226,13 +2226,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
name: "Rectangle",
category: "Vector",
implementation: DocumentNodeImplementation::Network(NodeNetwork {
imports: vec![NodeId(0), NodeId(0), NodeId(0)],
imports: vec![NodeId(0), NodeId(0), NodeId(0), NodeId(0), NodeId(0), NodeId(0)],
exports: vec![NodeOutput::new(NodeId(1), 0)],
nodes: vec![
DocumentNode {
name: "Rectangle Generator".to_string(),
inputs: vec![NodeInput::Network(concrete!(())), NodeInput::Network(concrete!(f64)), NodeInput::Network(concrete!(f64))],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::generator_nodes::RectangleGenerator<_, _>")),
inputs: vec![
NodeInput::Network(concrete!(())),
NodeInput::Network(concrete!(f64)),
NodeInput::Network(concrete!(f64)),
NodeInput::Network(concrete!(bool)),
NodeInput::Network(generic!(T)),
NodeInput::Network(concrete!(bool)),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::generator_nodes::RectangleGenerator<_, _, _, _, _>")),
..Default::default()
},
DocumentNode {
@@ -2253,6 +2260,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentInputType::none(),
DocumentInputType::value("Size X", TaggedValue::F64(100.), false),
DocumentInputType::value("Size Y", TaggedValue::F64(100.), false),
DocumentInputType::value("Individual Corner Radii", TaggedValue::Bool(false), false),
DocumentInputType::value("Corner Radius", TaggedValue::F64(0.), false),
DocumentInputType::value("Clamped", TaggedValue::Bool(true), false),
],
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::rectangle_properties,

View File

@@ -1503,12 +1503,136 @@ pub fn ellipse_properties(document_node: &DocumentNode, node_id: NodeId, _contex
}
pub fn rectangle_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let operand = |name: &str, index| {
let widgets = number_widget(document_node, node_id, index, name, NumberInput::default(), true);
let size_x_index = 1;
let size_y_index = 2;
let corner_rounding_type_index = 3;
let corner_radius_index = 4;
let clamped_index = 5;
LayoutGroup::Row { widgets }
};
vec![operand("Size X", 1), operand("Size Y", 2)]
// Size X
let size_x = number_widget(document_node, node_id, size_x_index, "Size X", NumberInput::default(), true);
// Size Y
let size_y = number_widget(document_node, node_id, size_y_index, "Size Y", NumberInput::default(), true);
// Corner Radius
let mut corner_radius_row_1 = start_widgets(document_node, node_id, corner_radius_index, "Corner Radius", FrontendGraphDataType::Number, true);
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
corner_radius_row_2.push(TextLabel::new("").widget_holder());
add_blank_assist(&mut corner_radius_row_2);
if let &NodeInput::Value {
tagged_value: TaggedValue::Bool(is_individual),
exposed: false,
} = &document_node.inputs[corner_rounding_type_index]
{
// Values
let uniform_val = match document_node.inputs[corner_radius_index] {
NodeInput::Value {
tagged_value: TaggedValue::F64(x),
exposed: false,
} => x,
NodeInput::Value {
tagged_value: TaggedValue::F64Array4(x),
exposed: false,
} => x[0],
_ => 0.,
};
let individual_val = match document_node.inputs[corner_radius_index] {
NodeInput::Value {
tagged_value: TaggedValue::F64Array4(x),
exposed: false,
} => x,
NodeInput::Value {
tagged_value: TaggedValue::F64(x),
exposed: false,
} => [x; 4],
_ => [0.; 4],
};
// Uniform/individual radio input widget
let uniform = RadioEntryData::new("Uniform")
.label("Uniform")
.on_update(move |_| {
Message::Batched(Box::new([
NodeGraphMessage::SetInputValue {
node_id,
input_index: corner_rounding_type_index,
value: TaggedValue::Bool(false),
}
.into(),
NodeGraphMessage::SetInputValue {
node_id,
input_index: corner_radius_index,
value: TaggedValue::F64(uniform_val),
}
.into(),
]))
})
.on_commit(commit_value);
let individual = RadioEntryData::new("Individual")
.label("Individual")
.on_update(move |_| {
Message::Batched(Box::new([
NodeGraphMessage::SetInputValue {
node_id,
input_index: corner_rounding_type_index,
value: TaggedValue::Bool(true),
}
.into(),
NodeGraphMessage::SetInputValue {
node_id,
input_index: corner_radius_index,
value: TaggedValue::F64Array4(individual_val),
}
.into(),
]))
})
.on_commit(commit_value);
let radio_input = RadioInput::new(vec![uniform, individual]).selected_index(Some(is_individual as u32)).widget_holder();
corner_radius_row_1.push(radio_input);
// Radius value input widget
let input_widget = if is_individual {
let from_string = |string: &str| {
string
.split(&[',', ' '])
.filter(|x| !x.is_empty())
.map(str::parse::<f64>)
.collect::<Result<Vec<f64>, _>>()
.ok()
.map(|v| {
let arr: Box<[f64; 4]> = v.into_boxed_slice().try_into().unwrap_or_default();
*arr
})
.map(TaggedValue::F64Array4)
};
TextInput::default()
.value(individual_val.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", "))
.on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, corner_radius_index))
.widget_holder()
} else {
NumberInput::default()
.value(Some(uniform_val))
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, corner_radius_index))
.on_commit(commit_value)
.widget_holder()
};
corner_radius_row_2.push(input_widget);
}
// Clamped
let clamped = bool_widget(document_node, node_id, clamped_index, "Clamped", true);
vec![
LayoutGroup::Row { widgets: size_x },
LayoutGroup::Row { widgets: size_y },
LayoutGroup::Row { widgets: corner_radius_row_1 },
LayoutGroup::Row { widgets: corner_radius_row_2 },
LayoutGroup::Row { widgets: clamped },
]
}
pub fn regular_polygon_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {