diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs index 893b301cd8..089f147558 100644 --- a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -255,6 +255,15 @@ pub struct NumberInput { #[serde(rename = "rangeMax")] pub range_max: Option, + // TODO: Make this (and range_max) apply to both Range and Increment modes when dragging with the mouse + #[serde(rename = "hardMin")] + #[widget_builder(skip)] + pub hard_min: Option, + + #[serde(rename = "hardMax")] + #[widget_builder(skip)] + pub hard_max: Option, + // Styling #[serde(rename = "minWidth")] pub min_width: u32, @@ -295,6 +304,14 @@ impl NumberInput { self.range_max = Some(val); self } + pub fn hard_min(mut self, val: f64) -> Self { + self.hard_min = Some(val); + self + } + pub fn hard_max(mut self, val: f64) -> Self { + self.hard_max = Some(val); + self + } pub fn mode_range(mut self) -> Self { self.mode = NumberInputMode::Range; self diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 89e0aaac92..682053ec9f 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -2653,6 +2653,8 @@ impl DocumentMessageHandler { .disabled(disabled) .min(0.) .max(100.) + .hard_min(0.) + .hard_max(100.) .range_min(Some(0.)) .range_max(Some(100.)) .mode_range() @@ -2675,6 +2677,8 @@ impl DocumentMessageHandler { .disabled(disabled) .min(0.) .max(100.) + .hard_min(0.) + .hard_max(100.) .range_min(Some(0.)) .range_max(Some(100.)) .mode_range() diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 18a7f82909..807c6f9024 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -118,19 +118,25 @@ pub(crate) fn property_from_type( node_id: NodeId, index: usize, ty: &Type, - number_options: (Option, Option, Option<(f64, f64)>), + number_options: (Option, Option, Option, Option, Option<(f64, f64)>), unit: Option<&str>, display_decimal_places: Option, step: Option, context: &mut NodePropertiesContext, ) -> Result, Vec> { - let (mut number_min, mut number_max, range) = number_options; + let (mut number_min, mut number_max, number_hard_min, number_hard_max, range) = number_options; let mut number_input = NumberInput::default(); if let Some((range_start, range_end)) = range { number_min = Some(range_start); number_max = Some(range_end); number_input = number_input.mode_range().min(range_start).max(range_end); } + if let Some(hard_min) = number_hard_min { + number_input = number_input.hard_min(hard_min) + } + if let Some(hard_max) = number_hard_max { + number_input = number_input.hard_max(hard_max) + } if let Some(unit) = unit { number_input = number_input.unit(unit); } @@ -1427,7 +1433,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper return Vec::new(); }; - let mut number_options = (None, None, None); + let mut number_options = (None, None, None, None, None); let mut display_decimal_places = None; let mut step = None; let mut unit_suffix = None; @@ -1439,7 +1445,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper .get(&proto_node_identifier) .and_then(|metadata| metadata.fields.get(input_index)) { - number_options = (field.number_min, field.number_max, field.number_mode_range); + number_options = (field.number_min, field.number_max, field.number_hard_min, field.number_hard_max, field.number_mode_range); display_decimal_places = field.number_display_decimal_places; unit_suffix = field.unit; step = field.number_step; diff --git a/frontend/src/components/widgets/inputs/NumberInput.svelte b/frontend/src/components/widgets/inputs/NumberInput.svelte index 955a749ad0..7d275fd8be 100644 --- a/frontend/src/components/widgets/inputs/NumberInput.svelte +++ b/frontend/src/components/widgets/inputs/NumberInput.svelte @@ -25,8 +25,12 @@ // Value // When `value` is not provided (i.e. it's `undefined`), a dash is displayed. export let value: number | undefined = undefined; // NOTE: Do not update this directly, do so by calling `updateValue()` instead. + export let min: number | undefined = undefined; export let max: number | undefined = undefined; + export let hardMin: number | undefined = undefined; + export let hardMax: number | undefined = undefined; + export let isInteger = false; // Number presentation @@ -130,6 +134,9 @@ if (mode == "Increment") { if (typeof min === "number") sanitized = Math.max(sanitized, min); if (typeof max === "number") sanitized = Math.min(sanitized, max); + } else { + if (typeof hardMin === "number") sanitized = Math.max(sanitized, hardMin); + if (typeof hardMax === "number") sanitized = Math.min(sanitized, hardMax); } text = displayText(sanitized, unit); } @@ -145,6 +152,9 @@ if (mode == "Increment") { if (typeof min === "number" && !Number.isNaN(min)) newValueValidated = Math.max(newValueValidated, min); if (typeof max === "number" && !Number.isNaN(max)) newValueValidated = Math.min(newValueValidated, max); + } else { + if (typeof hardMin === "number" && !Number.isNaN(hardMin)) newValueValidated = Math.max(newValueValidated, hardMin); + if (typeof hardMax === "number" && !Number.isNaN(hardMax)) newValueValidated = Math.min(newValueValidated, hardMax); } if (isInteger) newValueValidated = Math.round(newValueValidated); diff --git a/node-graph/gcore/src/blending_nodes.rs b/node-graph/gcore/src/blending_nodes.rs index fcf2bd0c00..0f776c323c 100644 --- a/node-graph/gcore/src/blending_nodes.rs +++ b/node-graph/gcore/src/blending_nodes.rs @@ -162,8 +162,14 @@ fn blending( )] mut value: T, blend_mode: BlendMode, - #[default(100.)] opacity: Percentage, - #[default(100.)] fill: Percentage, + #[hard_min(0.)] + #[hard_max(100.)] + #[default(100.)] + opacity: Percentage, + #[hard_min(0.)] + #[hard_max(100.)] + #[default(100.)] + fill: Percentage, #[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 Instance) rather than applying to each row in its own table, which produces the undesired result diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 5d405df093..686b1e07a8 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -55,6 +55,8 @@ pub struct FieldMetadata { pub default_type: Option, pub number_min: Option, pub number_max: Option, + pub number_hard_min: Option, + pub number_hard_max: Option, pub number_mode_range: Option<(f64, f64)>, pub number_display_decimal_places: Option, pub number_step: Option, diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 456f354530..da5fe424f9 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -154,6 +154,23 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result quote!(None), }) .collect(); + + let number_hard_min_values: Vec<_> = fields + .iter() + .map(|field| match field { + ParsedField::Regular { number_hard_min: Some(hard_min), .. } => quote!(Some(#hard_min)), + _ => quote!(None), + }) + .collect(); + + let number_hard_max_values: Vec<_> = fields + .iter() + .map(|field| match field { + ParsedField::Regular { number_hard_max: Some(hard_max), .. } => quote!(Some(#hard_max)), + _ => quote!(None), + }) + .collect(); + let number_mode_range_values: Vec<_> = fields .iter() .map(|field| match field { @@ -419,6 +436,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result