diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 3f00623b5ed..6e4b532c345 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -71,6 +71,7 @@ void AnimationNodeStateMachineEditor::edit(const Ref &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 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 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 Refis_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 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 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 AnimationNodeStateMachineEditor::_adjust_stylebox_opacity(Ref p_style, float p_opacity) { + Ref style = p_style->duplicate(); + if (style->is_class("StyleBoxFlat")) { + Ref 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 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 original_style = is_selected ? theme_cache.node_frame_selected : theme_cache.node_frame; + Ref 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 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 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 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 nodes_to = state_machine->get_nodes_with_transitions_to(p_node); + for (const StringName &node_to : nodes_to) { + connected_nodes.insert(node_to); + } + + Vector 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) { diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h index 0b6320f0ce5..f6dd5850daf 100644 --- a/editor/plugins/animation_state_machine_editor.h +++ b/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 &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 connected_nodes; + void _update_connected_nodes(const StringName &p_node); + + Ref _adjust_stylebox_opacity(Ref p_style, float p_opacity); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index aaaf0e474b5..f348a1b4185 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -2499,6 +2499,7 @@ void EditorThemeManager::_populate_editor_styles(const Ref &p_theme Ref 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); diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 5cc204100c0..12b876b5fa6 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -1841,6 +1841,26 @@ void AnimationNodeStateMachine::_bind_methods() { BIND_ENUM_CONSTANT(STATE_MACHINE_TYPE_GROUPED); } +Vector AnimationNodeStateMachine::get_nodes_with_transitions_from(const StringName &p_node) const { + Vector result; + for (const Transition &transition : transitions) { + if (transition.from == p_node) { + result.push_back(transition.to); + } + } + return result; +} + +Vector AnimationNodeStateMachine::get_nodes_with_transitions_to(const StringName &p_node) const { + Vector 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"; diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 648e96b138b..286c77aa04d 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/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 *r_options) const override; #endif + Vector get_nodes_with_transitions_from(const StringName &p_node) const; + Vector get_nodes_with_transitions_to(const StringName &p_node) const; + AnimationNodeStateMachine(); };