瀏覽代碼

Merge pull request #98402 from vaner-org/highlight-node-neighbours

Reduce opacity of nodes with no shared transition when selecting an AnimationNodeStateMachine node
Rémi Verschelde 8 月之前
父節點
當前提交
d6b20fd7e5

+ 144 - 15
editor/plugins/animation_state_machine_editor.cpp

@@ -71,6 +71,7 @@ void AnimationNodeStateMachineEditor::edit(const Ref<AnimationNode> &p_node) {
 		selected_transition_index = -1;
 		selected_node = StringName();
 		selected_nodes.clear();
+		connected_nodes.clear();
 		_update_mode();
 		_update_graph();
 	}
@@ -214,6 +215,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
 				}
 
 				selected_nodes.insert(selected_node);
+				_update_connected_nodes(selected_node);
 
 				Ref<AnimationNode> anode = state_machine->get_node(selected_node);
 				EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
@@ -277,6 +279,11 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
 			selected_transition_to = transition_lines[closest].to_node;
 			selected_transition_index = closest;
 
+			// Update connected_nodes for the selected transition.
+			connected_nodes.clear();
+			connected_nodes.insert(selected_transition_from);
+			connected_nodes.insert(selected_transition_to);
+
 			Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest);
 			if (!state_machine->is_transition_across_group(closest)) {
 				EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
@@ -377,6 +384,48 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
 		_update_mode();
 	}
 
+	if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+		StringName clicked_node;
+		for (int i = node_rects.size() - 1; i >= 0; i--) {
+			if (node_rects[i].node.has_point(mb->get_position())) {
+				clicked_node = node_rects[i].node_name;
+				break;
+			}
+		}
+
+		if (clicked_node != StringName()) {
+			if (selected_nodes.has(clicked_node) && mb->is_shift_pressed()) {
+				selected_nodes.erase(clicked_node);
+			} else {
+				if (!mb->is_shift_pressed()) {
+					selected_nodes.clear();
+				}
+				selected_nodes.insert(clicked_node);
+			}
+			selected_node = clicked_node;
+		} else {
+			// Clicked on empty space.
+			selected_nodes.clear();
+			selected_node = StringName();
+		}
+
+		_update_connected_nodes(selected_node);
+		state_machine_draw->queue_redraw();
+		_update_mode();
+
+		if (clicked_node != StringName()) {
+			Ref<AnimationNode> anode = state_machine->get_node(clicked_node);
+			EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
+			dragging_selected_attempt = true;
+			dragging_selected = false;
+			drag_from = mb->get_position();
+			snap_x = StringName();
+			snap_y = StringName();
+		}
+
+		return;
+	}
+
 	Ref<InputEventMouseMotion> mm = p_event;
 
 	// Pan window
@@ -860,11 +909,15 @@ void AnimationNodeStateMachineEditor::_add_transition(const bool p_nested_action
 	connecting = false;
 }
 
-void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group) {
+void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group, float p_opacity) {
 	Color line_color = p_enabled ? theme_cache.transition_color : theme_cache.transition_disabled_color;
 	Color icon_color = p_enabled ? theme_cache.transition_icon_color : theme_cache.transition_icon_disabled_color;
 	Color highlight_color = p_enabled ? theme_cache.highlight_color : theme_cache.highlight_disabled_color;
 
+	line_color.a *= p_opacity;
+	icon_color.a *= p_opacity;
+	highlight_color.a *= p_opacity;
+
 	if (p_travel) {
 		line_color = highlight_color;
 	}
@@ -877,6 +930,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co
 	if (p_fade_ratio > 0.0) {
 		Color fade_line_color = highlight_color;
 		fade_line_color.set_hsv(1.0, fade_line_color.get_s(), fade_line_color.get_v());
+		fade_line_color.a *= p_opacity;
 		state_machine_draw->draw_line(p_from, p_from.lerp(p_to, p_fade_ratio), fade_line_color, 2);
 	}
 
@@ -921,6 +975,25 @@ void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(const Vector2 &p_fr
 	}
 }
 
+Ref<StyleBox> AnimationNodeStateMachineEditor::_adjust_stylebox_opacity(Ref<StyleBox> p_style, float p_opacity) {
+	Ref<StyleBox> style = p_style->duplicate();
+	if (style->is_class("StyleBoxFlat")) {
+		Ref<StyleBoxFlat> flat_style = style;
+		Color bg_color = flat_style->get_bg_color();
+		Color border_color = flat_style->get_border_color();
+		Color shadow_color = flat_style->get_shadow_color();
+
+		bg_color.a *= p_opacity;
+		border_color.a *= p_opacity;
+		shadow_color.a *= p_opacity;
+
+		flat_style->set_bg_color(bg_color);
+		flat_style->set_border_color(border_color);
+		flat_style->set_shadow_color(shadow_color);
+	}
+	return style;
+}
+
 void AnimationNodeStateMachineEditor::_state_machine_draw() {
 	AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
 	if (!tree) {
@@ -1112,7 +1185,30 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
 	for (int i = 0; i < transition_lines.size(); i++) {
 		TransitionLine tl = transition_lines[i];
 		if (!tl.hidden) {
-			_connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, tl.is_across_group);
+			float opacity = 0.2; // Default to reduced opacity.
+
+			if (selected_transition_from != StringName() && selected_transition_to != StringName()) {
+				// A transition is selected.
+				if ((tl.from_node == selected_transition_from && tl.to_node == selected_transition_to) || (tl.from_node == selected_transition_to && tl.to_node == selected_transition_from)) {
+					opacity = 1.0; // Full opacity for the selected transition pair.
+				}
+			} else if (!connected_nodes.is_empty()) {
+				// A node is selected.
+				if (connected_nodes.has(selected_node)) {
+					// Only keep full opacity for transitions directly connected to the selected node.
+					if (tl.from_node == selected_node || tl.to_node == selected_node) {
+						opacity = 1.0;
+					}
+				} else {
+					// If no node is selected, all transitions are at full opacity.
+					opacity = 1.0;
+				}
+			} else {
+				// If nothing is selected, all transitions are at full opacity.
+				opacity = 1.0;
+			}
+
+			_connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, tl.is_across_group, opacity);
 		}
 	}
 
@@ -1129,31 +1225,45 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
 		Vector2 offset = nr.node.position;
 		int h = nr.node.size.height;
 
-		//prepre rect
+		float opacity = 1.0;
+		if (selected_transition_from != StringName() && selected_transition_to != StringName()) {
+			// A transition is selected.
+			if (name != selected_transition_from && name != selected_transition_to) {
+				opacity = 0.2;
+			}
+		} else if (!connected_nodes.is_empty() && !connected_nodes.has(name)) {
+			// A node is selected.
+			opacity = 0.2;
+		}
 
-		//now scroll it to draw
-		Ref<StyleBox> node_frame_style = is_selected ? theme_cache.node_frame_selected : theme_cache.node_frame;
-		state_machine_draw->draw_style_box(node_frame_style, nr.node);
+		Ref<StyleBox> original_style = is_selected ? theme_cache.node_frame_selected : theme_cache.node_frame;
+		Ref<StyleBox> node_style = _adjust_stylebox_opacity(original_style, opacity);
+
+		state_machine_draw->draw_style_box(node_style, nr.node);
 
 		if (!is_selected && AnimationNodeStateMachine::START_NODE == name) {
-			state_machine_draw->draw_style_box(theme_cache.node_frame_start, nr.node);
+			Ref<StyleBox> start_style = _adjust_stylebox_opacity(theme_cache.node_frame_start, opacity);
+			state_machine_draw->draw_style_box(start_style, nr.node);
 		}
 		if (!is_selected && AnimationNodeStateMachine::END_NODE == name) {
-			state_machine_draw->draw_style_box(theme_cache.node_frame_end, nr.node);
+			Ref<StyleBox> end_style = _adjust_stylebox_opacity(theme_cache.node_frame_end, opacity);
+			state_machine_draw->draw_style_box(end_style, nr.node);
 		}
 		if (playing && (blend_from == name || current == name || travel_path.has(name))) {
-			state_machine_draw->draw_style_box(theme_cache.node_frame_playing, nr.node);
+			Ref<StyleBox> playing_style = _adjust_stylebox_opacity(theme_cache.node_frame_playing, opacity);
+			state_machine_draw->draw_style_box(playing_style, nr.node);
 		}
 
-		offset.x += node_frame_style->get_offset().x;
+		offset.x += original_style->get_offset().x;
 
 		nr.play.position = offset + Vector2(0, (h - theme_cache.play_node->get_height()) / 2).floor();
 		nr.play.size = theme_cache.play_node->get_size();
 
+		Color color_mod = Color(1, 1, 1, opacity);
 		if (hovered_node_name == name && hovered_node_area == HOVER_NODE_PLAY) {
-			state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position, theme_cache.highlight_color);
+			state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position, theme_cache.highlight_color * color_mod);
 		} else {
-			state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position);
+			state_machine_draw->draw_texture(theme_cache.play_node, nr.play.position, color_mod);
 		}
 
 		offset.x += sep + theme_cache.play_node->get_width();
@@ -1161,7 +1271,9 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
 		nr.name.position = offset + Vector2(0, (h - theme_cache.node_title_font->get_height(theme_cache.node_title_font_size)) / 2).floor();
 		nr.name.size = Vector2(name_string_size, theme_cache.node_title_font->get_height(theme_cache.node_title_font_size));
 
-		state_machine_draw->draw_string(theme_cache.node_title_font, nr.name.position + Vector2(0, theme_cache.node_title_font->get_ascent(theme_cache.node_title_font_size)), name, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.node_title_font_size, theme_cache.node_title_font_color);
+		Color font_color = theme_cache.node_title_font_color;
+		font_color.a *= opacity;
+		state_machine_draw->draw_string(theme_cache.node_title_font, nr.name.position + Vector2(0, theme_cache.node_title_font->get_ascent(theme_cache.node_title_font_size)), name, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.node_title_font_size, font_color);
 		offset.x += name_string_size + sep;
 
 		nr.can_edit = needs_editor;
@@ -1170,9 +1282,9 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
 			nr.edit.size = theme_cache.edit_node->get_size();
 
 			if (hovered_node_name == name && hovered_node_area == HOVER_NODE_EDIT) {
-				state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position, theme_cache.highlight_color);
+				state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position, theme_cache.highlight_color * color_mod);
 			} else {
-				state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position);
+				state_machine_draw->draw_texture(theme_cache.edit_node, nr.edit.position, color_mod);
 			}
 		}
 	}
@@ -1201,6 +1313,23 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
 	state_machine_play_pos->queue_redraw();
 }
 
+void AnimationNodeStateMachineEditor::_update_connected_nodes(const StringName &p_node) {
+	connected_nodes.clear();
+	if (p_node != StringName()) {
+		connected_nodes.insert(p_node);
+
+		Vector<StringName> nodes_to = state_machine->get_nodes_with_transitions_to(p_node);
+		for (const StringName &node_to : nodes_to) {
+			connected_nodes.insert(node_to);
+		}
+
+		Vector<StringName> nodes_from = state_machine->get_nodes_with_transitions_from(p_node);
+		for (const StringName &node_from : nodes_from) {
+			connected_nodes.insert(node_from);
+		}
+	}
+}
+
 void AnimationNodeStateMachineEditor::_state_machine_pos_draw_individual(const String &p_name, float p_ratio) {
 	AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
 	if (!tree) {

+ 6 - 1
editor/plugins/animation_state_machine_editor.h

@@ -131,7 +131,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
 	static AnimationNodeStateMachineEditor *singleton;
 
 	void _state_machine_gui_input(const Ref<InputEvent> &p_event);
-	void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group);
+	void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_is_across_group, float p_opacity = 1.0);
 
 	void _state_machine_draw();
 
@@ -288,6 +288,11 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
 		MENU_LOAD_FILE_CONFIRM = 1002
 	};
 
+	HashSet<StringName> connected_nodes;
+	void _update_connected_nodes(const StringName &p_node);
+
+	Ref<StyleBox> _adjust_stylebox_opacity(Ref<StyleBox> p_style, float p_opacity);
+
 protected:
 	void _notification(int p_what);
 	static void _bind_methods();

+ 1 - 0
editor/themes/editor_theme_manager.cpp

@@ -2499,6 +2499,7 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
 			Ref<StyleBoxFlat> sm_node_playing_style = sm_node_selected_style->duplicate();
 			sm_node_playing_style->set_border_color(p_config.warning_color);
 			sm_node_playing_style->set_shadow_color(p_config.warning_color * Color(1, 1, 1, 0.2));
+			sm_node_playing_style->set_draw_center(false);
 
 			p_theme->set_stylebox("node_frame", "GraphStateMachine", sm_node_style);
 			p_theme->set_stylebox("node_frame_selected", "GraphStateMachine", sm_node_selected_style);

+ 20 - 0
scene/animation/animation_node_state_machine.cpp

@@ -1841,6 +1841,26 @@ void AnimationNodeStateMachine::_bind_methods() {
 	BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_GROUPED);
 }
 
+Vector<StringName> AnimationNodeStateMachine::get_nodes_with_transitions_from(const StringName &p_node) const {
+	Vector<StringName> result;
+	for (const Transition &transition : transitions) {
+		if (transition.from == p_node) {
+			result.push_back(transition.to);
+		}
+	}
+	return result;
+}
+
+Vector<StringName> AnimationNodeStateMachine::get_nodes_with_transitions_to(const StringName &p_node) const {
+	Vector<StringName> result;
+	for (const Transition &transition : transitions) {
+		if (transition.to == p_node) {
+			result.push_back(transition.from);
+		}
+	}
+	return result;
+}
+
 AnimationNodeStateMachine::AnimationNodeStateMachine() {
 	START_NODE = "Start";
 	END_NODE = "End";

+ 3 - 0
scene/animation/animation_node_state_machine.h

@@ -223,6 +223,9 @@ public:
 	virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
 #endif
 
+	Vector<StringName> get_nodes_with_transitions_from(const StringName &p_node) const;
+	Vector<StringName> get_nodes_with_transitions_to(const StringName &p_node) const;
+
 	AnimationNodeStateMachine();
 };