Add WidgetValueTrait::value_widget_connected

Depending on the usage of value widgets, we might wish to display an
alternative UI if the input is connected.

Motivating example: consider a node with an input representing some
amount of time in the context of digital signal processing. Multiple
valuable representations of time may be desired as inputs:
* number of seconds
* number of samples
* wavelength corresponding to some frequency in Hz

In such a case we would use value_widget to display a widget for
selecting both the number and the unit, and value_widget_connected for
only selecting the unit, because the numerical value would already be
supplied by another node.
This commit is contained in:
Kamil Koczurek 2023-04-20 14:15:31 +02:00
parent 78fe0265dd
commit 3b5198c050
2 changed files with 42 additions and 11 deletions

View File

@ -537,17 +537,26 @@ where
for (param_name, param_id) in inputs {
if self.graph[param_id].shown_inline {
let height_before = ui.min_rect().bottom();
// NOTE: We want to pass the `user_data` to
// `value_widget`, but we can't since that would require
// borrowing the graph twice. Here, we make the
// assumption that the value is cheaply replaced, and
// use `std::mem::take` to temporarily replace it with a
// dummy value. This requires `ValueType` to implement
// Default, but results in a totally safe alternative.
let mut value = std::mem::take(&mut self.graph[param_id].value);
if self.graph.connection(param_id).is_some() {
ui.label(param_name);
let node_responses = value.value_widget_connected(
&param_name,
self.node_id,
ui,
user_state,
&self.graph[self.node_id].user_data,
);
responses.extend(node_responses.into_iter().map(NodeResponse::User));
} else {
// NOTE: We want to pass the `user_data` to
// `value_widget`, but we can't since that would require
// borrowing the graph twice. Here, we make the
// assumption that the value is cheaply replaced, and
// use `std::mem::take` to temporarily replace it with a
// dummy value. This requires `ValueType` to implement
// Default, but results in a totally safe alternative.
let mut value = std::mem::take(&mut self.graph[param_id].value);
let node_responses = value.value_widget(
&param_name,
self.node_id,
@ -555,9 +564,12 @@ where
user_state,
&self.graph[self.node_id].user_data,
);
self.graph[param_id].value = value;
responses.extend(node_responses.into_iter().map(NodeResponse::User));
}
self.graph[param_id].value = value;
let height_after = ui.min_rect().bottom();
input_port_heights.push((height_before + height_after) / 2.0);
}

View File

@ -13,10 +13,11 @@ pub trait WidgetValueTrait: Default {
type Response;
type UserState;
type NodeData;
/// This method will be called for each input parameter with a widget. The
/// return value is a vector of custom response objects which can be used
/// to implement handling of side effects. If unsure, the response Vec can
/// be empty.
/// be empty. It isn't called if the input is connected.
fn value_widget(
&mut self,
param_name: &str,
@ -25,6 +26,24 @@ pub trait WidgetValueTrait: Default {
user_state: &mut Self::UserState,
node_data: &Self::NodeData,
) -> Vec<Self::Response>;
/// This method will be called for each input parameter with a widget. The
/// return value is a vector of custom response objects which can be used
/// to implement handling of side effects. If unsure, the response Vec can
/// be empty. This differs from [`WidgetValueTrait::value_widget`]
/// by being called if and only if the input has a connection.
fn value_widget_connected(
&mut self,
param_name: &str,
_node_id: NodeId,
ui: &mut egui::Ui,
_user_state: &mut Self::UserState,
_node_data: &Self::NodeData,
) -> Vec<Self::Response> {
ui.label(param_name);
Default::default()
}
}
/// This trait must be implemented by the `DataType` generic parameter of the