feat(node_graph,effect_stack): programmatic selection and card selection events

NodeGraphView::set_selection selects nodes programmatically (emitting
SelectionChanged); EffectStackDataSource::selected_effect highlights
the chosen card and EffectStackEvent::CardSelected reports card clicks
— the two-way clip/node selection sync between the timeline, node
editor and inspector.
This commit is contained in:
2026-08-20 17:08:25 +08:00
parent f7a00fdd91
commit 3870d6da06
4 changed files with 37 additions and 4 deletions
@@ -198,6 +198,7 @@ impl StackDemoRoot {
| EffectStackEvent::RemoveRequested(_)
| EffectStackEvent::AddRequested { .. }
| EffectStackEvent::ContextMenuRequested { .. }
| EffectStackEvent::CardSelected { .. }
| EffectStackEvent::ParameterChanged { .. } => {
todo!("apply {event:?} to the mock model (or engine, in a real app)")
}
+9
View File
@@ -162,6 +162,15 @@ pub trait EffectStackDataSource: 'static {
/// state instead of cards and suppresses all card interactions.
fn target_label(&self) -> Option<SharedString>;
/// The card the stack should highlight as selected — the effect whose
/// node is selected in the node graph, when the two views are linked.
/// `None` means no card is highlighted. Defaults to `None`.
///
/// This is a read-only selection mirror; the view never mutates it.
fn selected_effect(&self) -> Option<EffectId> {
None
}
/// Whether dropping the given effect at `new_index` (an index into the
/// list returned by [`effects`](EffectStackDataSource::effects)) would
/// be a valid reorder.
+17 -4
View File
@@ -67,6 +67,14 @@ pub enum EffectStackEvent {
/// The desired new expansion state.
expanded: bool,
},
/// The user clicked a card header to select it (the same gesture that
/// toggles expansion). Emitted alongside
/// [`ExpansionToggled`](Self::ExpansionToggled) so hosts can sync the
/// effect selection with the node graph (highlighting the effect's node).
CardSelected {
/// The clicked effect.
effect: EffectId,
},
/// The user clicked the remove button on a removable card.
RemoveRequested(EffectId),
/// The user invoked an "add effect" affordance at a stack position.
@@ -274,7 +282,9 @@ impl<D: EffectStackDataSource> EffectStackView<D> {
}
/// Toggles a card's expansion by emitting
/// [`EffectStackEvent::ExpansionToggled`].
/// [`EffectStackEvent::ExpansionToggled`], and reports the card click as
/// [`EffectStackEvent::CardSelected`] so hosts can sync the node-graph
/// selection with the stack.
fn toggle_expanded(&mut self, id: EffectId, cx: &mut Context<Self>) {
self.drag_state = DragState::default();
let expanded = self
@@ -289,6 +299,7 @@ impl<D: EffectStackDataSource> EffectStackView<D> {
effect: id,
expanded,
});
cx.emit(EffectStackEvent::CardSelected { effect: id });
cx.notify();
}
@@ -360,9 +371,9 @@ impl<D: EffectStackDataSource> Render for EffectStackView<D> {
self.drag_state = DragState::default();
}
let (label, effects) = {
let (label, effects, selected_effect) = {
let data = self.data.read(cx);
(data.target_label(), data.effects())
(data.target_label(), data.effects(), data.selected_effect())
};
let insertion_index = self.drag_state.insertion_index;
let dragged_id = self.drag_state.dragged;
@@ -532,7 +543,9 @@ impl<D: EffectStackDataSource> Render for EffectStackView<D> {
.w_full()
.rounded_md()
.border_1()
.border_color(if fixed {
.border_color(if selected_effect == Some(id) {
colors.selected
} else if fixed {
colors.border
} else {
colors.separator
+10
View File
@@ -318,6 +318,16 @@ impl<D: NodeGraphDataSource + 'static> NodeGraphView<D> {
&mut self.state
}
/// Programmatically replaces the selection (e.g. to sync the graph with
/// a timeline selection or an inspector card click). Emits
/// [`NodeGraphEvent::SelectionChanged`] when the set changed, so hosts
/// keep their engine-side selection mirror consistent. Callers should
/// not call `cx.notify()` on the view themselves — this method does.
pub fn set_selection(&mut self, nodes: BTreeSet<NodeId>, cx: &mut Context<Self>) {
self.set_selection_and_emit(nodes, cx);
cx.notify();
}
/// Returns the data-source entity this view renders.
pub fn data(&self) -> &Entity<D> {
&self.data