Auto-generate enum type widget boilerplate for radio buttons and dropdown menus (#2589)

* First draft of factoring out the dropdown boilerplate

* Add proc macro for enum boilerplate

* Detect whether to say `crate` or the name

* Clean up the input and naming of the enum macro

* Rename a file

* Do the rename of code too

* Use the attribute-driven selection of radio vs dropdown

* Add a metadata struct and tooltips

* Move the new traits to a better place.

* Use ChoiceType, part 1

* Use ChoiceType, part 2

* Introduce a builder API for choice widgets

* Start using the new new API

* DomainWarpType should be a dropdown still

* Handle the case where a node property can never have a socket

* Rustfmt

* Code review

* Update stable node IDs in test

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
kythyria
2025-05-01 12:14:26 +01:00
committed by GitHub
parent 9303953cf8
commit 9ef9b205d9
18 changed files with 713 additions and 990 deletions

View File

@@ -1,3 +1,4 @@
use super::node_properties::choice::enum_choice;
use super::node_properties::{self, ParameterWidgetsInfo};
use super::utility_types::FrontendNodeType;
use crate::messages::layout::utility_types::widget_prelude::*;
@@ -3212,7 +3213,9 @@ fn static_input_properties() -> InputProperties {
"noise_properties_noise_type".to_string(),
Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let noise_type_row = node_properties::noise_type_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true));
let noise_type_row = enum_choice::<NoiseType>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
.property_row();
Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }])
}),
);
@@ -3221,7 +3224,10 @@ fn static_input_properties() -> InputProperties {
Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let domain_warp_type = node_properties::domain_warp_type_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), !coherent_noise_active);
let domain_warp_type = enum_choice::<DomainWarpType>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
.disabled(!coherent_noise_active)
.property_row();
Ok(vec![domain_warp_type])
}),
);
@@ -3242,7 +3248,10 @@ fn static_input_properties() -> InputProperties {
Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let fractal_type_row = node_properties::fractal_type_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), !coherent_noise_active);
let fractal_type_row = enum_choice::<FractalType>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
.disabled(!coherent_noise_active)
.property_row();
Ok(vec![fractal_type_row])
}),
);
@@ -3333,10 +3342,10 @@ fn static_input_properties() -> InputProperties {
Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let cellular_distance_function_row = node_properties::cellular_distance_function_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
!coherent_noise_active || !cellular_noise_active,
);
let cellular_distance_function_row = enum_choice::<CellularDistanceFunction>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
.disabled(!coherent_noise_active || !cellular_noise_active)
.property_row();
Ok(vec![cellular_distance_function_row])
}),
);
@@ -3345,10 +3354,10 @@ fn static_input_properties() -> InputProperties {
Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let cellular_return_type = node_properties::cellular_return_type_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
!coherent_noise_active || !cellular_noise_active,
);
let cellular_return_type = enum_choice::<CellularReturnType>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
.disabled(!coherent_noise_active || !cellular_noise_active)
.property_row();
Ok(vec![cellular_return_type])
}),
);

View File

@@ -414,16 +414,15 @@ impl LayoutHolder for MenuBarMessageHandler {
action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![{
let operations = BooleanOperation::list();
let icons = BooleanOperation::icons();
operations
.into_iter()
.zip(icons)
.map(move |(operation, icon)| MenuBarEntry {
label: operation.to_string(),
icon: Some(icon.into()),
let list = <BooleanOperation as graphene_core::registry::ChoiceTypeStatic>::list();
list.into_iter()
.map(|i| i.into_iter())
.flatten()
.map(move |(operation, info)| MenuBarEntry {
label: info.label.to_string(),
icon: info.icon.as_ref().map(|i| i.to_string()),
action: MenuBarEntry::create_action(move |_| {
let group_folder_type = GroupFolderType::BooleanOperation(operation);
let group_folder_type = GroupFolderType::BooleanOperation(*operation);
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
}),
disabled: no_active_document || !has_selected_layers,

View File

@@ -90,11 +90,11 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Gradien
impl LayoutHolder for GradientTool {
fn layout(&self) -> Layout {
let gradient_type = RadioInput::new(vec![
RadioEntryData::new("linear")
RadioEntryData::new("Linear")
.label("Linear")
.tooltip("Linear gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()),
RadioEntryData::new("radial")
RadioEntryData::new("Radial")
.label("Radial")
.tooltip("Radial gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()),
@@ -611,7 +611,7 @@ mod test_gradient {
let (gradient, transform) = get_gradient(&mut editor).await;
// Gradient goes from secondary colour to primary colour
// Gradient goes from secondary color to primary color
let stops = gradient.stops.iter().map(|stop| (stop.0, stop.1.to_rgba8_srgb())).collect::<Vec<_>>();
assert_eq!(stops, vec![(0., Color::BLUE.to_rgba8_srgb()), (1., Color::GREEN.to_rgba8_srgb())]);
assert!(transform.transform_point2(gradient.start).abs_diff_eq(DVec2::new(2., 3.), 1e-10));

View File

@@ -181,14 +181,18 @@ impl SelectTool {
}
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
let operations = BooleanOperation::list();
let icons = BooleanOperation::icons();
operations.into_iter().zip(icons).map(move |(operation, icon)| {
IconButton::new(icon, 24)
.tooltip(operation.to_string())
let list = <BooleanOperation as graphene_core::registry::ChoiceTypeStatic>::list();
list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| {
let mut tooltip = info.label.to_string();
if let Some(doc) = info.docstring.as_deref() {
tooltip.push_str("\n\n");
tooltip.push_str(doc);
}
IconButton::new(info.icon.as_deref().unwrap(), 24)
.tooltip(tooltip)
.disabled(selected_count == 0)
.on_update(move |_| {
let group_folder_type = GroupFolderType::BooleanOperation(operation);
let group_folder_type = GroupFolderType::BooleanOperation(*operation);
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
})
.widget_holder()