mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
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:
@@ -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, ());
|
||||
|
||||
@@ -7,6 +7,7 @@ use graphite_proc_macros::*;
|
||||
pub enum Message {
|
||||
NoOp,
|
||||
Init,
|
||||
Batched(Box<[Message]>),
|
||||
|
||||
#[child]
|
||||
Broadcast(BroadcastMessage),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user