diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index ea0bfc0049..e98df2a22e 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -139,7 +139,8 @@ impl FrameTimeInfo { } pub fn advance_timestamp(&mut self, next_timestamp: Duration) { - debug_assert!(next_timestamp >= self.timestamp); + // Guard against non-monotonic timestamps from the browser (Keavon observed this once in Chrome) + let next_timestamp = next_timestamp.max(self.timestamp); self.prev_timestamp = Some(self.timestamp); self.timestamp = next_timestamp; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 3c031a39b3..a1fd08e4f0 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -4005,6 +4005,14 @@ impl NodeNetworkInterface { } } (_, NodeInput::Node { node_id: upstream_node_id, .. }) => { + // If the old input wasn't exposed but the new one is (`Node` inputs are always exposed), + // the node's port count changed, so its click targets need to be recomputed + if !old_input.is_exposed() + && let InputConnector::Node { node_id, .. } = input_connector + { + self.unload_node_click_targets(node_id, network_path); + } + // Load structure if the change is to the document network and to the first or second if network_path.is_empty() { if matches!(input_connector, InputConnector::Export(0)) { diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index dba65b4080..5f0ff7a1e1 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -212,16 +212,15 @@ impl SelectedEdges { let original_from_pivot = updated - pivot; // The original vector from the point to the pivot let mut scale_factor = new_from_pivot / original_from_pivot; - // Constrain should always scale by the same factor in x and y + // Constrain should always scale by the same factor in x and y. + // When one axis of `original_from_pivot` is near zero (e.g. for a line's degenerate bounding box), + // the scale factor for that axis is numerically unstable, so we copy from the more stable axis. if constrain { - // When the point is on the pivot, we simply copy the other axis. - if original_from_pivot.x.abs() < 1e-5 { + if original_from_pivot.x.abs() < original_from_pivot.y.abs() { scale_factor.x = scale_factor.y; - } else if original_from_pivot.y.abs() < 1e-5 { + } else { scale_factor.y = scale_factor.x; } - - debug_assert!((scale_factor.x - scale_factor.y).abs() < 1e-5); } if !(self.left || self.right || constrain) { diff --git a/frontend/src/components/widgets/inputs/ColorInput.svelte b/frontend/src/components/widgets/inputs/ColorInput.svelte index f0d2b2a699..3b8bac24de 100644 --- a/frontend/src/components/widgets/inputs/ColorInput.svelte +++ b/frontend/src/components/widgets/inputs/ColorInput.svelte @@ -116,7 +116,7 @@ background-repeat: var(--color-transparent-checkered-background-repeat); } - &:not(.disabled).none > button { + &.none > button { background: var(--color-none); background-repeat: var(--color-none-repeat); background-position: var(--color-none-position); @@ -132,6 +132,7 @@ left: 0; right: 0; background: var(--color-4-dimgray); + opacity: 0.5; } &:not(.disabled):hover > button .text-label, diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 91b6831809..7126db8a66 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -127,6 +127,9 @@ macro_rules! tagged_value { // Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned. Some(match concrete_type.id? { x if x == TypeId::of::<()>() => TaggedValue::None, + // Table-wrapped types need a single-row default with the element's default, not an empty table + x if x == TypeId::of::