initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
editor/animation/SCsub
Normal file
6
editor/animation/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
2464
editor/animation/animation_bezier_editor.cpp
Normal file
2464
editor/animation/animation_bezier_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
237
editor/animation/animation_bezier_editor.h
Normal file
237
editor/animation/animation_bezier_editor.h
Normal file
@@ -0,0 +1,237 @@
|
||||
/**************************************************************************/
|
||||
/* animation_bezier_editor.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "animation_track_editor.h"
|
||||
#include "core/templates/hashfuncs.h"
|
||||
|
||||
class ViewPanner;
|
||||
|
||||
class AnimationBezierTrackEdit : public Control {
|
||||
GDCLASS(AnimationBezierTrackEdit, Control);
|
||||
|
||||
enum {
|
||||
MENU_KEY_INSERT,
|
||||
MENU_KEY_DUPLICATE,
|
||||
MENU_KEY_CUT,
|
||||
MENU_KEY_COPY,
|
||||
MENU_KEY_PASTE,
|
||||
MENU_KEY_DELETE,
|
||||
MENU_KEY_SET_HANDLE_FREE,
|
||||
MENU_KEY_SET_HANDLE_LINEAR,
|
||||
MENU_KEY_SET_HANDLE_BALANCED,
|
||||
MENU_KEY_SET_HANDLE_MIRRORED,
|
||||
MENU_KEY_SET_HANDLE_AUTO_BALANCED,
|
||||
MENU_KEY_SET_HANDLE_AUTO_MIRRORED,
|
||||
};
|
||||
|
||||
AnimationTimelineEdit *timeline = nullptr;
|
||||
Node *root = nullptr;
|
||||
Control *play_position = nullptr; //separate control used to draw so updates for only position changed are much faster
|
||||
real_t play_position_pos = 0;
|
||||
|
||||
Ref<Animation> animation;
|
||||
bool read_only = false;
|
||||
int selected_track = 0;
|
||||
|
||||
Vector<Rect2> view_rects;
|
||||
|
||||
Ref<Texture2D> bezier_icon;
|
||||
Ref<Texture2D> bezier_handle_icon;
|
||||
Ref<Texture2D> selected_icon;
|
||||
|
||||
RBMap<int, Rect2> subtracks;
|
||||
|
||||
enum {
|
||||
REMOVE_ICON,
|
||||
LOCK_ICON,
|
||||
SOLO_ICON,
|
||||
VISIBILITY_ICON
|
||||
};
|
||||
|
||||
RBMap<int, RBMap<int, Rect2>> subtrack_icons;
|
||||
HashSet<int> locked_tracks;
|
||||
HashSet<int> hidden_tracks;
|
||||
int solo_track = -1;
|
||||
bool is_filtered = false;
|
||||
|
||||
float track_v_scroll = 0;
|
||||
float track_v_scroll_max = 0;
|
||||
|
||||
float timeline_v_scroll = 0;
|
||||
float timeline_v_zoom = 1;
|
||||
|
||||
PopupMenu *menu = nullptr;
|
||||
|
||||
void _zoom_changed();
|
||||
|
||||
void _update_locked_tracks_after(int p_track);
|
||||
void _update_hidden_tracks_after(int p_track);
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
void _menu_selected(int p_index);
|
||||
|
||||
void _play_position_draw();
|
||||
bool _is_track_displayed(int p_track_index);
|
||||
bool _is_track_curves_displayed(int p_track_index);
|
||||
|
||||
Vector2 insert_at_pos;
|
||||
|
||||
typedef Pair<int, int> IntPair;
|
||||
|
||||
bool moving_selection_attempt = false;
|
||||
bool moving_inserted_key = false;
|
||||
Point2 moving_selection_mouse_begin;
|
||||
IntPair select_single_attempt;
|
||||
bool moving_selection = false;
|
||||
int moving_selection_from_key = 0;
|
||||
int moving_selection_from_track = 0;
|
||||
|
||||
Vector2 moving_selection_offset;
|
||||
|
||||
bool box_selecting_attempt = false;
|
||||
bool box_selecting = false;
|
||||
bool box_selecting_add = false;
|
||||
Vector2 box_selection_from;
|
||||
Vector2 box_selection_to;
|
||||
|
||||
Rect2 selection_rect;
|
||||
Rect2 selection_handles_rect;
|
||||
|
||||
bool scaling_selection = false;
|
||||
Vector2i scaling_selection_handles;
|
||||
Vector2 scaling_selection_scale = Vector2(1, 1);
|
||||
Vector2 scaling_selection_offset;
|
||||
Point2 scaling_selection_pivot;
|
||||
|
||||
int moving_handle = 0; //0 no move -1 or +1 out, 2 both (drawing only)
|
||||
int moving_handle_key = 0;
|
||||
int moving_handle_track = 0;
|
||||
Vector2 moving_handle_left;
|
||||
Vector2 moving_handle_right;
|
||||
int moving_handle_mode = 0; // value from Animation::HandleMode
|
||||
|
||||
struct PairHasher {
|
||||
static _FORCE_INLINE_ uint32_t hash(const Pair<int, int> &p_value) {
|
||||
int32_t hash = 23;
|
||||
hash = hash * 31 * hash_one_uint64(p_value.first);
|
||||
hash = hash * 31 * hash_one_uint64(p_value.second);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<Pair<int, int>, Vector2, PairHasher> additional_moving_handle_lefts;
|
||||
HashMap<Pair<int, int>, Vector2, PairHasher> additional_moving_handle_rights;
|
||||
|
||||
void _clear_selection();
|
||||
void _clear_selection_for_anim(const Ref<Animation> &p_anim);
|
||||
void _select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos, bool p_single);
|
||||
bool _try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable);
|
||||
void _change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto = false);
|
||||
|
||||
Vector2 menu_insert_key;
|
||||
|
||||
struct AnimMoveRestore {
|
||||
int track = 0;
|
||||
double time = 0;
|
||||
Variant key;
|
||||
real_t transition = 0;
|
||||
};
|
||||
|
||||
AnimationTrackEditor *editor = nullptr;
|
||||
|
||||
struct EditPoint {
|
||||
Rect2 point_rect;
|
||||
Rect2 in_rect;
|
||||
Rect2 out_rect;
|
||||
int track = 0;
|
||||
int key = 0;
|
||||
};
|
||||
|
||||
Vector<EditPoint> edit_points;
|
||||
|
||||
struct PairCompare {
|
||||
bool operator()(const IntPair &lh, const IntPair &rh) {
|
||||
if (lh.first == rh.first) {
|
||||
return lh.second < rh.second;
|
||||
} else {
|
||||
return lh.first < rh.first;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef RBSet<IntPair, PairCompare> SelectionSet;
|
||||
|
||||
SelectionSet selection;
|
||||
|
||||
Ref<ViewPanner> panner;
|
||||
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
|
||||
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
|
||||
|
||||
void _draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right);
|
||||
void _draw_track(int p_track, const Color &p_color);
|
||||
|
||||
float _bezier_h_to_pixel(float p_h);
|
||||
void _zoom_vertically(real_t p_minimum_value, real_t p_maximum_value);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static float get_bezier_key_value(Array p_bezier_key_array);
|
||||
|
||||
virtual String get_tooltip(const Point2 &p_pos) const override;
|
||||
|
||||
Ref<Animation> get_animation() const;
|
||||
|
||||
void set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only);
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
|
||||
|
||||
void set_timeline(AnimationTimelineEdit *p_timeline);
|
||||
void set_editor(AnimationTrackEditor *p_editor);
|
||||
void set_root(Node *p_root);
|
||||
void set_filtered(bool p_filtered);
|
||||
void auto_fit_vertically();
|
||||
|
||||
void set_play_position(real_t p_pos);
|
||||
void update_play_position();
|
||||
|
||||
void duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid);
|
||||
void copy_selected_keys(bool p_cut);
|
||||
void paste_keys(real_t p_ofs, bool p_ofs_valid);
|
||||
void delete_selection();
|
||||
|
||||
void _bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode, Animation::HandleSetMode p_handle_set_mode = Animation::HANDLE_SET_MODE_NONE);
|
||||
|
||||
AnimationBezierTrackEdit();
|
||||
};
|
||||
816
editor/animation/animation_blend_space_1d_editor.cpp
Normal file
816
editor/animation/animation_blend_space_1d_editor.cpp
Normal file
@@ -0,0 +1,816 @@
|
||||
/**************************************************************************/
|
||||
/* animation_blend_space_1d_editor.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "animation_blend_space_1d_editor.h"
|
||||
|
||||
#include "core/os/keyboard.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/animation/animation_blend_tree.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
|
||||
StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const {
|
||||
StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + "blend_position";
|
||||
return path;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) {
|
||||
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventKey> k = p_event;
|
||||
|
||||
if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) {
|
||||
if (selected_point != -1) {
|
||||
if (!read_only) {
|
||||
_erase_selected();
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
|
||||
if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (mb->get_button_index() == MouseButton::LEFT && tool_create->is_pressed()))) {
|
||||
if (!read_only) {
|
||||
menu->clear(false);
|
||||
animations_menu->clear();
|
||||
animations_to_add.clear();
|
||||
|
||||
LocalVector<StringName> classes;
|
||||
ClassDB::get_inheriters_from_class("AnimationRootNode", classes);
|
||||
classes.sort_custom<StringName::AlphCompare>();
|
||||
|
||||
menu->add_submenu_node_item(TTR("Add Animation"), animations_menu);
|
||||
|
||||
List<StringName> names;
|
||||
tree->get_animation_list(&names);
|
||||
|
||||
for (const StringName &E : names) {
|
||||
animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E);
|
||||
animations_to_add.push_back(E);
|
||||
}
|
||||
|
||||
for (const StringName &E : classes) {
|
||||
String name = String(E).replace_first("AnimationNode", "");
|
||||
if (name == "Animation" || name == "StartState" || name == "EndState") {
|
||||
continue;
|
||||
}
|
||||
|
||||
int idx = menu->get_item_count();
|
||||
menu->add_item(vformat(TTR("Add %s"), name), idx);
|
||||
menu->set_item_metadata(idx, E);
|
||||
}
|
||||
|
||||
Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard();
|
||||
if (clipb.is_valid()) {
|
||||
menu->add_separator();
|
||||
menu->add_item(TTR("Paste"), MENU_PASTE);
|
||||
}
|
||||
menu->add_separator();
|
||||
menu->add_item(TTR("Load..."), MENU_LOAD_FILE);
|
||||
|
||||
menu->set_position(blend_space_draw->get_screen_position() + mb->get_position());
|
||||
menu->reset_size();
|
||||
menu->popup();
|
||||
|
||||
add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x;
|
||||
add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
|
||||
add_point_pos += blend_space->get_min_space();
|
||||
|
||||
if (snap->is_pressed()) {
|
||||
add_point_pos = Math::snapped(add_point_pos, blend_space->get_snap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
|
||||
blend_space_draw->queue_redraw(); // why not
|
||||
|
||||
// try to see if a point can be selected
|
||||
selected_point = -1;
|
||||
_update_tool_erase();
|
||||
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
if (Math::abs(float(points[i] - mb->get_position().x)) < 10 * EDSCALE) {
|
||||
selected_point = i;
|
||||
|
||||
Ref<AnimationNode> node = blend_space->get_blend_point_node(i);
|
||||
EditorNode::get_singleton()->push_item(node.ptr(), "", true);
|
||||
dragging_selected_attempt = true;
|
||||
drag_from = mb->get_position();
|
||||
_update_tool_erase();
|
||||
_update_edited_point_pos();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT) {
|
||||
if (!read_only) {
|
||||
if (dragging_selected) {
|
||||
// move
|
||||
float point = blend_space->get_blend_point_position(selected_point);
|
||||
point += drag_ofs.x;
|
||||
|
||||
if (snap->is_pressed()) {
|
||||
point = Math::snapped(point, blend_space->get_snap());
|
||||
}
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Move Node Point"));
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point);
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point));
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
undo_redo->add_undo_method(this, "_update_space");
|
||||
undo_redo->add_do_method(this, "_update_edited_point_pos");
|
||||
undo_redo->add_undo_method(this, "_update_edited_point_pos");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
}
|
||||
|
||||
dragging_selected_attempt = false;
|
||||
dragging_selected = false;
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
// *set* the blend
|
||||
if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
|
||||
float blend_pos = mb->get_position().x / blend_space_draw->get_size().x;
|
||||
blend_pos *= blend_space->get_max_space() - blend_space->get_min_space();
|
||||
blend_pos += blend_space->get_min_space();
|
||||
|
||||
tree->set(get_blend_position_path(), blend_pos);
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
Ref<InputEventMouseMotion> mm = p_event;
|
||||
|
||||
if (mm.is_valid() && dragging_selected_attempt) {
|
||||
dragging_selected = true;
|
||||
drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * ((blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, 0));
|
||||
blend_space_draw->queue_redraw();
|
||||
_update_edited_point_pos();
|
||||
}
|
||||
|
||||
if (mm.is_valid() && tool_blend->is_pressed() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
|
||||
float blend_pos = mm->get_position().x / blend_space_draw->get_size().x;
|
||||
blend_pos *= blend_space->get_max_space() - blend_space->get_min_space();
|
||||
blend_pos += blend_space->get_min_space();
|
||||
|
||||
tree->set(get_blend_position_path(), blend_pos);
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_blend_space_draw() {
|
||||
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
Color linecolor = get_theme_color(SceneStringName(font_color), SNAME("Label"));
|
||||
Color linecolor_soft = linecolor;
|
||||
linecolor_soft.a *= 0.5;
|
||||
|
||||
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
|
||||
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("KeyValue"));
|
||||
Ref<Texture2D> icon_selected = get_editor_theme_icon(SNAME("KeySelected"));
|
||||
|
||||
Size2 s = blend_space_draw->get_size();
|
||||
|
||||
if (blend_space_draw->has_focus()) {
|
||||
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
|
||||
blend_space_draw->draw_rect(Rect2(Point2(), s), color, false);
|
||||
}
|
||||
|
||||
blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor, Math::round(EDSCALE));
|
||||
|
||||
if (blend_space->get_min_space() < 0) {
|
||||
float point = 0.0;
|
||||
point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
|
||||
point *= s.width;
|
||||
|
||||
float x = point;
|
||||
|
||||
blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor, Math::round(EDSCALE));
|
||||
blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height(font_size) + font->get_ascent(font_size)), "0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, linecolor);
|
||||
blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft, Math::round(EDSCALE));
|
||||
}
|
||||
|
||||
if (snap->is_pressed()) {
|
||||
linecolor_soft.a = linecolor.a * 0.1;
|
||||
|
||||
if (blend_space->get_snap() > 0) {
|
||||
int prev_idx = -1;
|
||||
|
||||
for (int i = 0; i < s.x; i++) {
|
||||
float v = blend_space->get_min_space() + i * (blend_space->get_max_space() - blend_space->get_min_space()) / s.x;
|
||||
int idx = int(v / blend_space->get_snap());
|
||||
|
||||
if (i > 0 && prev_idx != idx) {
|
||||
blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft, Math::round(EDSCALE));
|
||||
}
|
||||
|
||||
prev_idx = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
points.clear();
|
||||
|
||||
for (int i = 0; i < blend_space->get_blend_point_count(); i++) {
|
||||
float point = blend_space->get_blend_point_position(i);
|
||||
|
||||
if (!read_only) {
|
||||
if (dragging_selected && selected_point == i) {
|
||||
point += drag_ofs.x;
|
||||
if (snap->is_pressed()) {
|
||||
point = Math::snapped(point, blend_space->get_snap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
|
||||
point *= s.width;
|
||||
|
||||
points.push_back(point);
|
||||
|
||||
Vector2 gui_point = Vector2(point, s.height / 2.0);
|
||||
|
||||
gui_point -= (icon->get_size() / 2.0);
|
||||
|
||||
gui_point = gui_point.floor();
|
||||
|
||||
if (i == selected_point) {
|
||||
blend_space_draw->draw_texture(icon_selected, gui_point);
|
||||
} else {
|
||||
blend_space_draw->draw_texture(icon, gui_point);
|
||||
}
|
||||
}
|
||||
|
||||
// blend position
|
||||
{
|
||||
Color color;
|
||||
if (tool_blend->is_pressed()) {
|
||||
color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
|
||||
} else {
|
||||
color = linecolor;
|
||||
color.a *= 0.5;
|
||||
}
|
||||
|
||||
float point = tree->get(get_blend_position_path());
|
||||
|
||||
point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
|
||||
point *= s.width;
|
||||
|
||||
Vector2 gui_point = Vector2(point, s.height / 2.0);
|
||||
|
||||
float mind = 5 * EDSCALE;
|
||||
float maxd = 15 * EDSCALE;
|
||||
blend_space_draw->draw_line(gui_point + Vector2(mind, 0), gui_point + Vector2(maxd, 0), color, Math::round(2 * EDSCALE));
|
||||
blend_space_draw->draw_line(gui_point + Vector2(-mind, 0), gui_point + Vector2(-maxd, 0), color, Math::round(2 * EDSCALE));
|
||||
blend_space_draw->draw_line(gui_point + Vector2(0, mind), gui_point + Vector2(0, maxd), color, Math::round(2 * EDSCALE));
|
||||
blend_space_draw->draw_line(gui_point + Vector2(0, -mind), gui_point + Vector2(0, -maxd), color, Math::round(2 * EDSCALE));
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_update_space() {
|
||||
if (updating) {
|
||||
return;
|
||||
}
|
||||
|
||||
updating = true;
|
||||
|
||||
max_value->set_value(blend_space->get_max_space());
|
||||
min_value->set_value(blend_space->get_min_space());
|
||||
|
||||
sync->set_pressed(blend_space->is_using_sync());
|
||||
interpolation->select(blend_space->get_blend_mode());
|
||||
|
||||
label_value->set_text(blend_space->get_value_label());
|
||||
|
||||
snap_value->set_value(blend_space->get_snap());
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
|
||||
updating = false;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_config_changed(double) {
|
||||
if (updating) {
|
||||
return;
|
||||
}
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Change BlendSpace1D Config"));
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_max_space", max_value->get_value());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_min_space", min_value->get_value());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_snap", snap_value->get_value());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_use_sync", sync->is_pressed());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_use_sync", blend_space->is_using_sync());
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_blend_mode", interpolation->get_selected());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_blend_mode", blend_space->get_blend_mode());
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
undo_redo->add_undo_method(this, "_update_space");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_labels_changed(String) {
|
||||
if (updating) {
|
||||
return;
|
||||
}
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Change BlendSpace1D Labels"), UndoRedo::MERGE_ENDS);
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_value_label", label_value->get_text());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_value_label", blend_space->get_value_label());
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
undo_redo->add_undo_method(this, "_update_space");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_snap_toggled() {
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_file_opened(const String &p_file) {
|
||||
file_loaded = ResourceLoader::load(p_file);
|
||||
if (file_loaded.is_valid()) {
|
||||
_add_menu_type(MENU_LOAD_FILE_CONFIRM);
|
||||
} else {
|
||||
EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only animation nodes are allowed."));
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) {
|
||||
Ref<AnimationRootNode> node;
|
||||
if (p_index == MENU_LOAD_FILE) {
|
||||
open_file->clear_filters();
|
||||
List<String> filters;
|
||||
ResourceLoader::get_recognized_extensions_for_type("AnimationRootNode", &filters);
|
||||
for (const String &E : filters) {
|
||||
open_file->add_filter("*." + E);
|
||||
}
|
||||
open_file->popup_file_dialog();
|
||||
return;
|
||||
} else if (p_index == MENU_LOAD_FILE_CONFIRM) {
|
||||
node = file_loaded;
|
||||
file_loaded.unref();
|
||||
} else if (p_index == MENU_PASTE) {
|
||||
node = EditorSettings::get_singleton()->get_resource_clipboard();
|
||||
} else {
|
||||
String type = menu->get_item_metadata(p_index);
|
||||
|
||||
Object *obj = ClassDB::instantiate(type);
|
||||
ERR_FAIL_NULL(obj);
|
||||
AnimationNode *an = Object::cast_to<AnimationNode>(obj);
|
||||
ERR_FAIL_NULL(an);
|
||||
|
||||
node = Ref<AnimationNode>(an);
|
||||
}
|
||||
|
||||
if (node.is_null()) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("This type of node can't be used. Only root nodes are allowed."));
|
||||
return;
|
||||
}
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Add Node Point"));
|
||||
undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos);
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count());
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
undo_redo->add_undo_method(this, "_update_space");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) {
|
||||
Ref<AnimationNodeAnimation> anim;
|
||||
anim.instantiate();
|
||||
|
||||
anim->set_animation(animations_to_add[p_index]);
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Add Animation Point"));
|
||||
undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos);
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count());
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
undo_redo->add_undo_method(this, "_update_space");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_tool_switch(int p_tool) {
|
||||
if (p_tool == 0) {
|
||||
tool_erase->show();
|
||||
tool_erase_sep->show();
|
||||
} else {
|
||||
tool_erase->hide();
|
||||
tool_erase_sep->hide();
|
||||
}
|
||||
|
||||
_update_tool_erase();
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() {
|
||||
if (updating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
|
||||
float pos = blend_space->get_blend_point_position(selected_point);
|
||||
|
||||
if (dragging_selected) {
|
||||
pos += drag_ofs.x;
|
||||
|
||||
if (snap->is_pressed()) {
|
||||
pos = Math::snapped(pos, blend_space->get_snap());
|
||||
}
|
||||
}
|
||||
|
||||
updating = true;
|
||||
edit_value->set_value(pos);
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_update_tool_erase() {
|
||||
bool point_valid = selected_point >= 0 && selected_point < blend_space->get_blend_point_count();
|
||||
tool_erase->set_disabled(!point_valid || read_only);
|
||||
|
||||
if (point_valid) {
|
||||
Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
|
||||
|
||||
if (AnimationTreeEditor::get_singleton()->can_edit(an)) {
|
||||
open_editor->show();
|
||||
} else {
|
||||
open_editor->hide();
|
||||
}
|
||||
|
||||
if (!read_only) {
|
||||
edit_hb->show();
|
||||
} else {
|
||||
edit_hb->hide();
|
||||
}
|
||||
} else {
|
||||
edit_hb->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_erase_selected() {
|
||||
if (selected_point != -1) {
|
||||
updating = true;
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Remove BlendSpace1D Point"));
|
||||
undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point);
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point);
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
undo_redo->add_undo_method(this, "_update_space");
|
||||
undo_redo->commit_action();
|
||||
|
||||
updating = false;
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_edit_point_pos(double) {
|
||||
if (updating) {
|
||||
return;
|
||||
}
|
||||
|
||||
updating = true;
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Move BlendSpace1D Node Point"));
|
||||
undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, edit_value->get_value());
|
||||
undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point));
|
||||
undo_redo->add_do_method(this, "_update_space");
|
||||
undo_redo->add_undo_method(this, "_update_space");
|
||||
undo_redo->add_do_method(this, "_update_edited_point_pos");
|
||||
undo_redo->add_undo_method(this, "_update_edited_point_pos");
|
||||
undo_redo->commit_action();
|
||||
updating = false;
|
||||
|
||||
blend_space_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_open_editor() {
|
||||
if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
|
||||
Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
|
||||
ERR_FAIL_COND(an.is_null());
|
||||
AnimationTreeEditor::get_singleton()->enter_editor(itos(selected_point));
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
error_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
|
||||
error_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
|
||||
tool_blend->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));
|
||||
tool_select->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
|
||||
tool_create->set_button_icon(get_editor_theme_icon(SNAME("EditKey")));
|
||||
tool_erase->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
|
||||
snap->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
|
||||
open_editor->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
|
||||
interpolation->clear();
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackContinuous")), TTR("Continuous"), 0);
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackDiscrete")), TTR("Discrete"), 1);
|
||||
interpolation->add_icon_item(get_editor_theme_icon(SNAME("TrackCapture")), TTR("Capture"), 2);
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_PROCESS: {
|
||||
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
String error;
|
||||
|
||||
if (!tree->is_active()) {
|
||||
error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
|
||||
} else if (tree->is_state_invalid()) {
|
||||
error = tree->get_invalid_state_reason();
|
||||
}
|
||||
|
||||
if (error != error_label->get_text()) {
|
||||
error_label->set_text(error);
|
||||
if (!error.is_empty()) {
|
||||
error_panel->show();
|
||||
} else {
|
||||
error_panel->hide();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
set_process(is_visible_in_tree());
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::_bind_methods() {
|
||||
ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace1DEditor::_update_space);
|
||||
ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace1DEditor::_update_tool_erase);
|
||||
|
||||
ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace1DEditor::_update_edited_point_pos);
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace1DEditor::can_edit(const Ref<AnimationNode> &p_node) {
|
||||
Ref<AnimationNodeBlendSpace1D> b1d = p_node;
|
||||
return b1d.is_valid();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1DEditor::edit(const Ref<AnimationNode> &p_node) {
|
||||
blend_space = p_node;
|
||||
read_only = false;
|
||||
|
||||
if (blend_space.is_valid()) {
|
||||
read_only = EditorNode::get_singleton()->is_resource_read_only(blend_space);
|
||||
|
||||
_update_space();
|
||||
}
|
||||
|
||||
tool_create->set_disabled(read_only);
|
||||
edit_value->set_editable(!read_only);
|
||||
label_value->set_editable(!read_only);
|
||||
min_value->set_editable(!read_only);
|
||||
max_value->set_editable(!read_only);
|
||||
sync->set_disabled(read_only);
|
||||
interpolation->set_disabled(read_only);
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nullptr;
|
||||
|
||||
AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
|
||||
singleton = this;
|
||||
|
||||
HBoxContainer *top_hb = memnew(HBoxContainer);
|
||||
add_child(top_hb);
|
||||
|
||||
Ref<ButtonGroup> bg;
|
||||
bg.instantiate();
|
||||
|
||||
tool_blend = memnew(Button);
|
||||
tool_blend->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
tool_blend->set_toggle_mode(true);
|
||||
tool_blend->set_button_group(bg);
|
||||
top_hb->add_child(tool_blend);
|
||||
tool_blend->set_pressed(true);
|
||||
tool_blend->set_tooltip_text(TTR("Set the blending position within the space"));
|
||||
tool_blend->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(3));
|
||||
|
||||
tool_select = memnew(Button);
|
||||
tool_select->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
tool_select->set_toggle_mode(true);
|
||||
tool_select->set_button_group(bg);
|
||||
top_hb->add_child(tool_select);
|
||||
tool_select->set_tooltip_text(TTR("Select and move points, create points with RMB."));
|
||||
tool_select->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(0));
|
||||
|
||||
tool_create = memnew(Button);
|
||||
tool_create->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
tool_create->set_toggle_mode(true);
|
||||
tool_create->set_button_group(bg);
|
||||
top_hb->add_child(tool_create);
|
||||
tool_create->set_tooltip_text(TTR("Create points."));
|
||||
tool_create->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_tool_switch).bind(1));
|
||||
|
||||
tool_erase_sep = memnew(VSeparator);
|
||||
top_hb->add_child(tool_erase_sep);
|
||||
tool_erase = memnew(Button);
|
||||
tool_erase->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
top_hb->add_child(tool_erase);
|
||||
tool_erase->set_tooltip_text(TTR("Erase points."));
|
||||
tool_erase->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_erase_selected));
|
||||
|
||||
top_hb->add_child(memnew(VSeparator));
|
||||
|
||||
snap = memnew(Button);
|
||||
snap->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
snap->set_toggle_mode(true);
|
||||
top_hb->add_child(snap);
|
||||
snap->set_pressed(true);
|
||||
snap->set_tooltip_text(TTR("Enable snap and show grid."));
|
||||
snap->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_snap_toggled));
|
||||
|
||||
snap_value = memnew(SpinBox);
|
||||
top_hb->add_child(snap_value);
|
||||
snap_value->set_min(0.01);
|
||||
snap_value->set_step(0.01);
|
||||
snap_value->set_max(1000);
|
||||
snap_value->set_accessibility_name(TTRC("Grid Step"));
|
||||
|
||||
top_hb->add_child(memnew(VSeparator));
|
||||
top_hb->add_child(memnew(Label(TTR("Sync:"))));
|
||||
sync = memnew(CheckBox);
|
||||
top_hb->add_child(sync);
|
||||
sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
|
||||
top_hb->add_child(memnew(VSeparator));
|
||||
|
||||
top_hb->add_child(memnew(Label(TTR("Blend:"))));
|
||||
interpolation = memnew(OptionButton);
|
||||
top_hb->add_child(interpolation);
|
||||
interpolation->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
|
||||
edit_hb = memnew(HBoxContainer);
|
||||
top_hb->add_child(edit_hb);
|
||||
edit_hb->add_child(memnew(VSeparator));
|
||||
edit_hb->add_child(memnew(Label(TTR("Point"))));
|
||||
|
||||
edit_value = memnew(SpinBox);
|
||||
edit_hb->add_child(edit_value);
|
||||
edit_value->set_min(-1000);
|
||||
edit_value->set_max(1000);
|
||||
edit_value->set_step(0.01);
|
||||
edit_value->set_accessibility_name(TTRC("Blend Value"));
|
||||
edit_value->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_edit_point_pos));
|
||||
|
||||
open_editor = memnew(Button);
|
||||
edit_hb->add_child(open_editor);
|
||||
open_editor->set_text(TTR("Open Editor"));
|
||||
open_editor->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_open_editor), CONNECT_DEFERRED);
|
||||
|
||||
edit_hb->hide();
|
||||
open_editor->hide();
|
||||
|
||||
VBoxContainer *main_vb = memnew(VBoxContainer);
|
||||
add_child(main_vb);
|
||||
main_vb->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
panel = memnew(PanelContainer);
|
||||
panel->set_clip_contents(true);
|
||||
main_vb->add_child(panel);
|
||||
panel->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
panel->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
blend_space_draw = memnew(Control);
|
||||
blend_space_draw->connect(SceneStringName(gui_input), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_blend_space_gui_input));
|
||||
blend_space_draw->connect(SceneStringName(draw), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_blend_space_draw));
|
||||
blend_space_draw->set_focus_mode(FOCUS_ALL);
|
||||
|
||||
panel->add_child(blend_space_draw);
|
||||
|
||||
{
|
||||
HBoxContainer *bottom_hb = memnew(HBoxContainer);
|
||||
main_vb->add_child(bottom_hb);
|
||||
bottom_hb->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
min_value = memnew(SpinBox);
|
||||
min_value->set_min(-10000);
|
||||
min_value->set_max(0);
|
||||
min_value->set_step(0.01);
|
||||
min_value->set_accessibility_name(TTRC("Min"));
|
||||
|
||||
max_value = memnew(SpinBox);
|
||||
max_value->set_min(0.01);
|
||||
max_value->set_max(10000);
|
||||
max_value->set_step(0.01);
|
||||
max_value->set_accessibility_name(TTRC("Max"));
|
||||
|
||||
label_value = memnew(LineEdit);
|
||||
label_value->set_expand_to_text_length_enabled(true);
|
||||
label_value->set_accessibility_name(TTRC("Value"));
|
||||
|
||||
// now add
|
||||
|
||||
bottom_hb->add_child(min_value);
|
||||
bottom_hb->add_spacer();
|
||||
bottom_hb->add_child(label_value);
|
||||
bottom_hb->add_spacer();
|
||||
bottom_hb->add_child(max_value);
|
||||
}
|
||||
|
||||
snap_value->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
min_value->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
max_value->connect(SceneStringName(value_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
|
||||
label_value->connect(SceneStringName(text_changed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_labels_changed));
|
||||
|
||||
error_panel = memnew(PanelContainer);
|
||||
add_child(error_panel);
|
||||
|
||||
error_label = memnew(Label);
|
||||
error_label->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
error_panel->add_child(error_label);
|
||||
error_panel->hide();
|
||||
|
||||
menu = memnew(PopupMenu);
|
||||
add_child(menu);
|
||||
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_add_menu_type));
|
||||
|
||||
animations_menu = memnew(PopupMenu);
|
||||
animations_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
menu->add_child(animations_menu);
|
||||
animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_add_animation_type));
|
||||
|
||||
open_file = memnew(EditorFileDialog);
|
||||
add_child(open_file);
|
||||
open_file->set_title(TTR("Open Animation Node"));
|
||||
open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
|
||||
open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_file_opened));
|
||||
|
||||
set_custom_minimum_size(Size2(0, 150 * EDSCALE));
|
||||
}
|
||||
137
editor/animation/animation_blend_space_1d_editor.h
Normal file
137
editor/animation/animation_blend_space_1d_editor.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/**************************************************************************/
|
||||
/* animation_blend_space_1d_editor.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/animation/animation_tree_editor_plugin.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/animation/animation_blend_space_1d.h"
|
||||
#include "scene/gui/graph_edit.h"
|
||||
|
||||
class Button;
|
||||
class CheckBox;
|
||||
class LineEdit;
|
||||
class OptionButton;
|
||||
class PanelContainer;
|
||||
class SpinBox;
|
||||
class VSeparator;
|
||||
|
||||
class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin {
|
||||
GDCLASS(AnimationNodeBlendSpace1DEditor, AnimationTreeNodeEditorPlugin);
|
||||
|
||||
Ref<AnimationNodeBlendSpace1D> blend_space;
|
||||
bool read_only = false;
|
||||
|
||||
HBoxContainer *goto_parent_hb = nullptr;
|
||||
Button *goto_parent = nullptr;
|
||||
|
||||
PanelContainer *panel = nullptr;
|
||||
Button *tool_blend = nullptr;
|
||||
Button *tool_select = nullptr;
|
||||
Button *tool_create = nullptr;
|
||||
VSeparator *tool_erase_sep = nullptr;
|
||||
Button *tool_erase = nullptr;
|
||||
Button *snap = nullptr;
|
||||
SpinBox *snap_value = nullptr;
|
||||
|
||||
LineEdit *label_value = nullptr;
|
||||
SpinBox *max_value = nullptr;
|
||||
SpinBox *min_value = nullptr;
|
||||
|
||||
CheckBox *sync = nullptr;
|
||||
OptionButton *interpolation = nullptr;
|
||||
|
||||
HBoxContainer *edit_hb = nullptr;
|
||||
SpinBox *edit_value = nullptr;
|
||||
Button *open_editor = nullptr;
|
||||
|
||||
int selected_point = -1;
|
||||
|
||||
Control *blend_space_draw = nullptr;
|
||||
|
||||
PanelContainer *error_panel = nullptr;
|
||||
Label *error_label = nullptr;
|
||||
|
||||
bool updating = false;
|
||||
|
||||
static AnimationNodeBlendSpace1DEditor *singleton;
|
||||
|
||||
void _blend_space_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _blend_space_draw();
|
||||
|
||||
void _update_space();
|
||||
|
||||
void _config_changed(double);
|
||||
void _labels_changed(String);
|
||||
void _snap_toggled();
|
||||
|
||||
PopupMenu *menu = nullptr;
|
||||
PopupMenu *animations_menu = nullptr;
|
||||
Vector<String> animations_to_add;
|
||||
float add_point_pos = 0.0f;
|
||||
Vector<real_t> points;
|
||||
|
||||
bool dragging_selected_attempt = false;
|
||||
bool dragging_selected = false;
|
||||
Vector2 drag_from;
|
||||
Vector2 drag_ofs;
|
||||
|
||||
void _add_menu_type(int p_index);
|
||||
void _add_animation_type(int p_index);
|
||||
|
||||
void _tool_switch(int p_tool);
|
||||
void _update_edited_point_pos();
|
||||
void _update_tool_erase();
|
||||
void _erase_selected();
|
||||
void _edit_point_pos(double);
|
||||
void _open_editor();
|
||||
|
||||
EditorFileDialog *open_file = nullptr;
|
||||
Ref<AnimationNode> file_loaded;
|
||||
void _file_opened(const String &p_file);
|
||||
|
||||
enum {
|
||||
MENU_LOAD_FILE = 1000,
|
||||
MENU_PASTE = 1001,
|
||||
MENU_LOAD_FILE_CONFIRM = 1002
|
||||
};
|
||||
|
||||
StringName get_blend_position_path() const;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static AnimationNodeBlendSpace1DEditor *get_singleton() { return singleton; }
|
||||
virtual bool can_edit(const Ref<AnimationNode> &p_node) override;
|
||||
virtual void edit(const Ref<AnimationNode> &p_node) override;
|
||||
AnimationNodeBlendSpace1DEditor();
|
||||
};
|
||||
1103
editor/animation/animation_blend_space_2d_editor.cpp
Normal file
1103
editor/animation/animation_blend_space_2d_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
148
editor/animation/animation_blend_space_2d_editor.h
Normal file
148
editor/animation/animation_blend_space_2d_editor.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/**************************************************************************/
|
||||
/* animation_blend_space_2d_editor.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/animation/animation_tree_editor_plugin.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/animation/animation_blend_space_2d.h"
|
||||
#include "scene/gui/graph_edit.h"
|
||||
|
||||
class Button;
|
||||
class CheckBox;
|
||||
class LineEdit;
|
||||
class OptionButton;
|
||||
class PanelContainer;
|
||||
class SpinBox;
|
||||
class VSeparator;
|
||||
|
||||
class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin {
|
||||
GDCLASS(AnimationNodeBlendSpace2DEditor, AnimationTreeNodeEditorPlugin);
|
||||
|
||||
Ref<AnimationNodeBlendSpace2D> blend_space;
|
||||
bool read_only = false;
|
||||
|
||||
PanelContainer *panel = nullptr;
|
||||
Button *tool_blend = nullptr;
|
||||
Button *tool_select = nullptr;
|
||||
Button *tool_create = nullptr;
|
||||
Button *tool_triangle = nullptr;
|
||||
VSeparator *tool_erase_sep = nullptr;
|
||||
Button *tool_erase = nullptr;
|
||||
Button *snap = nullptr;
|
||||
SpinBox *snap_x = nullptr;
|
||||
SpinBox *snap_y = nullptr;
|
||||
CheckBox *sync = nullptr;
|
||||
OptionButton *interpolation = nullptr;
|
||||
|
||||
Button *auto_triangles = nullptr;
|
||||
|
||||
LineEdit *label_x = nullptr;
|
||||
LineEdit *label_y = nullptr;
|
||||
SpinBox *max_x_value = nullptr;
|
||||
SpinBox *min_x_value = nullptr;
|
||||
SpinBox *max_y_value = nullptr;
|
||||
SpinBox *min_y_value = nullptr;
|
||||
|
||||
HBoxContainer *edit_hb = nullptr;
|
||||
SpinBox *edit_x = nullptr;
|
||||
SpinBox *edit_y = nullptr;
|
||||
Button *open_editor = nullptr;
|
||||
|
||||
int selected_point;
|
||||
int selected_triangle;
|
||||
|
||||
Control *blend_space_draw = nullptr;
|
||||
|
||||
PanelContainer *error_panel = nullptr;
|
||||
Label *error_label = nullptr;
|
||||
|
||||
bool updating;
|
||||
|
||||
static AnimationNodeBlendSpace2DEditor *singleton;
|
||||
|
||||
void _blend_space_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _blend_space_draw();
|
||||
|
||||
void _update_space();
|
||||
|
||||
void _config_changed(double);
|
||||
void _labels_changed(String);
|
||||
void _snap_toggled();
|
||||
|
||||
PopupMenu *menu = nullptr;
|
||||
PopupMenu *animations_menu = nullptr;
|
||||
Vector<String> animations_to_add;
|
||||
Vector2 add_point_pos;
|
||||
Vector<Vector2> points;
|
||||
|
||||
bool dragging_selected_attempt;
|
||||
bool dragging_selected;
|
||||
Vector2 drag_from;
|
||||
Vector2 drag_ofs;
|
||||
|
||||
Vector<int> making_triangle;
|
||||
|
||||
void _add_menu_type(int p_index);
|
||||
void _add_animation_type(int p_index);
|
||||
|
||||
void _tool_switch(int p_tool);
|
||||
void _update_edited_point_pos();
|
||||
void _update_tool_erase();
|
||||
void _erase_selected();
|
||||
void _edit_point_pos(double);
|
||||
void _open_editor();
|
||||
|
||||
void _auto_triangles_toggled();
|
||||
|
||||
StringName get_blend_position_path() const;
|
||||
|
||||
EditorFileDialog *open_file = nullptr;
|
||||
Ref<AnimationNode> file_loaded;
|
||||
void _file_opened(const String &p_file);
|
||||
|
||||
enum {
|
||||
MENU_LOAD_FILE = 1000,
|
||||
MENU_PASTE = 1001,
|
||||
MENU_LOAD_FILE_CONFIRM = 1002
|
||||
};
|
||||
|
||||
void _blend_space_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static AnimationNodeBlendSpace2DEditor *get_singleton() { return singleton; }
|
||||
virtual bool can_edit(const Ref<AnimationNode> &p_node) override;
|
||||
virtual void edit(const Ref<AnimationNode> &p_node) override;
|
||||
AnimationNodeBlendSpace2DEditor();
|
||||
};
|
||||
1438
editor/animation/animation_blend_tree_editor_plugin.cpp
Normal file
1438
editor/animation/animation_blend_tree_editor_plugin.cpp
Normal file
File diff suppressed because it is too large
Load Diff
210
editor/animation/animation_blend_tree_editor_plugin.h
Normal file
210
editor/animation/animation_blend_tree_editor_plugin.h
Normal file
@@ -0,0 +1,210 @@
|
||||
/**************************************************************************/
|
||||
/* animation_blend_tree_editor_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
#include "editor/animation/animation_tree_editor_plugin.h"
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "scene/animation/animation_blend_tree.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/graph_edit.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class AcceptDialog;
|
||||
class CheckBox;
|
||||
class ProgressBar;
|
||||
class EditorFileDialog;
|
||||
class EditorProperty;
|
||||
class MenuButton;
|
||||
class PanelContainer;
|
||||
class EditorInspectorPluginAnimationNodeAnimation;
|
||||
|
||||
class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
|
||||
GDCLASS(AnimationNodeBlendTreeEditor, AnimationTreeNodeEditorPlugin);
|
||||
|
||||
Ref<AnimationNodeBlendTree> blend_tree;
|
||||
|
||||
bool read_only = false;
|
||||
|
||||
GraphEdit *graph = nullptr;
|
||||
MenuButton *add_node = nullptr;
|
||||
Vector2 position_from_popup_menu;
|
||||
bool use_position_from_popup_menu;
|
||||
|
||||
PanelContainer *error_panel = nullptr;
|
||||
Label *error_label = nullptr;
|
||||
|
||||
AcceptDialog *filter_dialog = nullptr;
|
||||
Tree *filters = nullptr;
|
||||
CheckBox *filter_enabled = nullptr;
|
||||
Button *filter_fill_selection = nullptr;
|
||||
Button *filter_invert_selection = nullptr;
|
||||
Button *filter_clear_selection = nullptr;
|
||||
|
||||
HashMap<StringName, ProgressBar *> animations;
|
||||
Vector<EditorProperty *> visible_properties;
|
||||
|
||||
String to_node = "";
|
||||
int to_slot = -1;
|
||||
String from_node = "";
|
||||
|
||||
struct AddOption {
|
||||
String name;
|
||||
String type;
|
||||
Ref<Script> script;
|
||||
int input_port_count;
|
||||
AddOption(const String &p_name = String(), const String &p_type = String(), int p_input_port_count = 0) :
|
||||
name(p_name),
|
||||
type(p_type),
|
||||
input_port_count(p_input_port_count) {
|
||||
}
|
||||
};
|
||||
|
||||
Vector<AddOption> add_options;
|
||||
|
||||
void _add_node(int p_idx);
|
||||
void _update_options_menu(bool p_has_input_ports = false);
|
||||
|
||||
static AnimationNodeBlendTreeEditor *singleton;
|
||||
|
||||
void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, const StringName &p_which);
|
||||
void _node_renamed(const String &p_text, Ref<AnimationNode> p_node);
|
||||
void _node_renamed_focus_out(Ref<AnimationNode> p_node);
|
||||
void _node_rename_lineedit_changed(const String &p_text);
|
||||
void _node_changed(const StringName &p_node_name);
|
||||
|
||||
String current_node_rename_text;
|
||||
bool updating;
|
||||
|
||||
void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index);
|
||||
void _disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index);
|
||||
|
||||
void _scroll_changed(const Vector2 &p_scroll);
|
||||
void _node_selected(Object *p_node);
|
||||
void _open_in_editor(const String &p_which);
|
||||
void _anim_selected(int p_index, const Array &p_options, const String &p_node);
|
||||
void _delete_node_request(const String &p_which);
|
||||
void _delete_nodes_request(const TypedArray<StringName> &p_nodes);
|
||||
|
||||
bool _update_filters(const Ref<AnimationNode> &anode);
|
||||
void _inspect_filters(const String &p_which);
|
||||
void _filter_edited();
|
||||
void _filter_toggled();
|
||||
void _filter_fill_selection();
|
||||
void _filter_invert_selection();
|
||||
void _filter_clear_selection();
|
||||
void _filter_fill_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item, bool p_parent_filtered);
|
||||
void _filter_invert_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item);
|
||||
void _filter_clear_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item);
|
||||
Ref<AnimationNode> _filter_edit;
|
||||
|
||||
void _popup(bool p_has_input_ports, const Vector2 &p_node_position);
|
||||
void _popup_request(const Vector2 &p_position);
|
||||
void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position);
|
||||
void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position);
|
||||
void _popup_hide();
|
||||
|
||||
void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing);
|
||||
|
||||
void _update_editor_settings();
|
||||
|
||||
EditorFileDialog *open_file = nullptr;
|
||||
Ref<AnimationNode> file_loaded;
|
||||
void _file_opened(const String &p_file);
|
||||
|
||||
enum {
|
||||
MENU_LOAD_FILE = 1000,
|
||||
MENU_PASTE = 1001,
|
||||
MENU_LOAD_FILE_CONFIRM = 1002
|
||||
};
|
||||
|
||||
Ref<EditorInspectorPluginAnimationNodeAnimation> animation_node_inspector_plugin;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static AnimationNodeBlendTreeEditor *get_singleton() { return singleton; }
|
||||
|
||||
void add_custom_type(const String &p_name, const Ref<Script> &p_script);
|
||||
void remove_custom_type(const Ref<Script> &p_script);
|
||||
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
|
||||
virtual bool can_edit(const Ref<AnimationNode> &p_node) override;
|
||||
virtual void edit(const Ref<AnimationNode> &p_node) override;
|
||||
|
||||
void update_graph();
|
||||
|
||||
AnimationNodeBlendTreeEditor();
|
||||
};
|
||||
|
||||
// EditorPluginAnimationNodeAnimation
|
||||
|
||||
class EditorInspectorPluginAnimationNodeAnimation : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginAnimationNodeAnimation, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) override;
|
||||
};
|
||||
|
||||
class AnimationNodeAnimationEditorDialog : public ConfirmationDialog {
|
||||
GDCLASS(AnimationNodeAnimationEditorDialog, ConfirmationDialog);
|
||||
|
||||
friend class AnimationNodeAnimationEditor;
|
||||
|
||||
OptionButton *select_start = nullptr;
|
||||
OptionButton *select_end = nullptr;
|
||||
|
||||
public:
|
||||
AnimationNodeAnimationEditorDialog();
|
||||
};
|
||||
|
||||
class AnimationNodeAnimationEditor : public VBoxContainer {
|
||||
GDCLASS(AnimationNodeAnimationEditor, VBoxContainer);
|
||||
|
||||
Ref<AnimationNodeAnimation> animation_node_animation;
|
||||
Button *button = nullptr;
|
||||
AnimationNodeAnimationEditorDialog *dialog = nullptr;
|
||||
void _open_set_custom_timeline_from_marker_dialog();
|
||||
void _validate_markers(int p_id);
|
||||
void _confirm_set_custom_timeline_from_marker_dialog();
|
||||
|
||||
public:
|
||||
AnimationNodeAnimationEditor(Ref<AnimationNodeAnimation> p_animation_node_animation);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
};
|
||||
1081
editor/animation/animation_library_editor.cpp
Normal file
1081
editor/animation/animation_library_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
129
editor/animation/animation_library_editor.h
Normal file
129
editor/animation/animation_library_editor.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/**************************************************************************/
|
||||
/* animation_library_editor.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/animation/animation_mixer.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class AnimationMixer;
|
||||
class EditorFileDialog;
|
||||
|
||||
class AnimationLibraryEditor : public AcceptDialog {
|
||||
GDCLASS(AnimationLibraryEditor, AcceptDialog)
|
||||
|
||||
enum {
|
||||
LIB_BUTTON_ADD,
|
||||
LIB_BUTTON_LOAD,
|
||||
LIB_BUTTON_PASTE,
|
||||
LIB_BUTTON_FILE,
|
||||
LIB_BUTTON_DELETE,
|
||||
};
|
||||
enum {
|
||||
ANIM_BUTTON_COPY,
|
||||
ANIM_BUTTON_FILE,
|
||||
ANIM_BUTTON_DELETE,
|
||||
};
|
||||
|
||||
enum FileMenuAction {
|
||||
FILE_MENU_SAVE_LIBRARY,
|
||||
FILE_MENU_SAVE_AS_LIBRARY,
|
||||
FILE_MENU_MAKE_LIBRARY_UNIQUE,
|
||||
FILE_MENU_EDIT_LIBRARY,
|
||||
|
||||
FILE_MENU_SAVE_ANIMATION,
|
||||
FILE_MENU_SAVE_AS_ANIMATION,
|
||||
FILE_MENU_MAKE_ANIMATION_UNIQUE,
|
||||
FILE_MENU_EDIT_ANIMATION,
|
||||
};
|
||||
|
||||
enum FileDialogAction {
|
||||
FILE_DIALOG_ACTION_OPEN_LIBRARY,
|
||||
FILE_DIALOG_ACTION_SAVE_LIBRARY,
|
||||
FILE_DIALOG_ACTION_OPEN_ANIMATION,
|
||||
FILE_DIALOG_ACTION_SAVE_ANIMATION,
|
||||
};
|
||||
|
||||
FileDialogAction file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION;
|
||||
|
||||
StringName file_dialog_animation;
|
||||
StringName file_dialog_library;
|
||||
|
||||
Button *new_library_button = nullptr;
|
||||
Button *load_library_button = nullptr;
|
||||
|
||||
AcceptDialog *error_dialog = nullptr;
|
||||
bool adding_animation = false;
|
||||
StringName adding_animation_to_library;
|
||||
EditorFileDialog *file_dialog = nullptr;
|
||||
ConfirmationDialog *add_library_dialog = nullptr;
|
||||
LineEdit *add_library_name = nullptr;
|
||||
Label *add_library_validate = nullptr;
|
||||
PopupMenu *file_popup = nullptr;
|
||||
|
||||
Tree *tree = nullptr;
|
||||
|
||||
AnimationMixer *mixer = nullptr;
|
||||
|
||||
void _add_library();
|
||||
void _add_library_validate(const String &p_name);
|
||||
void _add_library_confirm();
|
||||
void _load_library();
|
||||
void _load_file(const String &p_path);
|
||||
void _load_files(const PackedStringArray &p_paths);
|
||||
|
||||
void _save_mixer_lib_folding(TreeItem *p_item);
|
||||
Vector<uint64_t> _load_mixer_libs_folding();
|
||||
void _load_config_libs_folding(Vector<uint64_t> &p_lib_ids, ConfigFile *p_config, String p_section);
|
||||
String _get_mixer_signature() const;
|
||||
|
||||
void _item_renamed();
|
||||
void _button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button);
|
||||
|
||||
void _file_popup_selected(int p_id);
|
||||
|
||||
bool updating = false;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
void _update_editor(Object *p_mixer);
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_animation_mixer(Object *p_mixer);
|
||||
void show_dialog();
|
||||
void update_tree();
|
||||
AnimationLibraryEditor();
|
||||
};
|
||||
2480
editor/animation/animation_player_editor_plugin.cpp
Normal file
2480
editor/animation/animation_player_editor_plugin.cpp
Normal file
File diff suppressed because it is too large
Load Diff
368
editor/animation/animation_player_editor_plugin.h
Normal file
368
editor/animation/animation_player_editor_plugin.h
Normal file
@@ -0,0 +1,368 @@
|
||||
/**************************************************************************/
|
||||
/* animation_player_editor_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/animation/animation_library_editor.h"
|
||||
#include "editor/animation/animation_track_editor.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/slider.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
#include "scene/gui/texture_button.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class AnimationPlayerEditorPlugin;
|
||||
class ImageTexture;
|
||||
|
||||
class AnimationPlayerEditor : public VBoxContainer {
|
||||
GDCLASS(AnimationPlayerEditor, VBoxContainer);
|
||||
|
||||
friend AnimationPlayerEditorPlugin;
|
||||
|
||||
AnimationPlayerEditorPlugin *plugin = nullptr;
|
||||
AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree.
|
||||
AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy.
|
||||
ObjectID cached_root_node_id;
|
||||
bool is_dummy = false;
|
||||
|
||||
enum {
|
||||
TOOL_NEW_ANIM,
|
||||
TOOL_ANIM_LIBRARY,
|
||||
TOOL_DUPLICATE_ANIM,
|
||||
TOOL_RENAME_ANIM,
|
||||
TOOL_EDIT_TRANSITIONS,
|
||||
TOOL_REMOVE_ANIM,
|
||||
TOOL_EDIT_RESOURCE
|
||||
};
|
||||
|
||||
enum {
|
||||
ONION_SKINNING_ENABLE,
|
||||
ONION_SKINNING_PAST,
|
||||
ONION_SKINNING_FUTURE,
|
||||
ONION_SKINNING_1_STEP,
|
||||
ONION_SKINNING_2_STEPS,
|
||||
ONION_SKINNING_3_STEPS,
|
||||
ONION_SKINNING_LAST_STEPS_OPTION = ONION_SKINNING_3_STEPS,
|
||||
ONION_SKINNING_DIFFERENCES_ONLY,
|
||||
ONION_SKINNING_FORCE_WHITE_MODULATE,
|
||||
ONION_SKINNING_INCLUDE_GIZMOS,
|
||||
};
|
||||
|
||||
enum {
|
||||
ANIM_OPEN,
|
||||
ANIM_SAVE,
|
||||
ANIM_SAVE_AS
|
||||
};
|
||||
|
||||
enum {
|
||||
RESOURCE_LOAD,
|
||||
RESOURCE_SAVE
|
||||
};
|
||||
|
||||
OptionButton *animation = nullptr;
|
||||
Button *stop = nullptr;
|
||||
Button *play = nullptr;
|
||||
Button *play_from = nullptr;
|
||||
Button *play_bw = nullptr;
|
||||
Button *play_bw_from = nullptr;
|
||||
Button *autoplay = nullptr;
|
||||
|
||||
MenuButton *tool_anim = nullptr;
|
||||
Button *onion_toggle = nullptr;
|
||||
MenuButton *onion_skinning = nullptr;
|
||||
Button *pin = nullptr;
|
||||
SpinBox *frame = nullptr;
|
||||
LineEdit *scale = nullptr;
|
||||
LineEdit *name = nullptr;
|
||||
OptionButton *library = nullptr;
|
||||
Label *name_title = nullptr;
|
||||
|
||||
Ref<Texture2D> stop_icon;
|
||||
Ref<Texture2D> pause_icon;
|
||||
Ref<Texture2D> autoplay_icon;
|
||||
Ref<Texture2D> reset_icon;
|
||||
Ref<ImageTexture> autoplay_reset_icon;
|
||||
|
||||
bool finishing = false;
|
||||
bool last_active = false;
|
||||
float timeline_position = 0;
|
||||
|
||||
EditorFileDialog *file = nullptr;
|
||||
ConfirmationDialog *delete_dialog = nullptr;
|
||||
|
||||
AnimationLibraryEditor *library_editor = nullptr;
|
||||
|
||||
struct BlendEditor {
|
||||
AcceptDialog *dialog = nullptr;
|
||||
Tree *tree = nullptr;
|
||||
OptionButton *next = nullptr;
|
||||
|
||||
} blend_editor;
|
||||
|
||||
ConfirmationDialog *name_dialog = nullptr;
|
||||
AcceptDialog *error_dialog = nullptr;
|
||||
int name_dialog_op = TOOL_NEW_ANIM;
|
||||
|
||||
bool updating = false;
|
||||
bool updating_blends = false;
|
||||
|
||||
AnimationTrackEditor *track_editor = nullptr;
|
||||
static AnimationPlayerEditor *singleton;
|
||||
|
||||
// Onion skinning.
|
||||
struct {
|
||||
// Settings.
|
||||
bool enabled = false;
|
||||
bool past = true;
|
||||
bool future = false;
|
||||
uint32_t steps = 1;
|
||||
bool differences_only = false;
|
||||
bool force_white_modulate = false;
|
||||
bool include_gizmos = false;
|
||||
|
||||
uint32_t get_capture_count() const {
|
||||
// 'Differences only' needs a capture of the present.
|
||||
return (past && future ? 2 * steps : steps) + (differences_only ? 1 : 0);
|
||||
}
|
||||
|
||||
// Rendering.
|
||||
int64_t last_frame = 0;
|
||||
int can_overlay = 0;
|
||||
Size2 capture_size;
|
||||
LocalVector<RID> captures;
|
||||
LocalVector<bool> captures_valid;
|
||||
struct {
|
||||
RID canvas;
|
||||
RID canvas_item;
|
||||
Ref<ShaderMaterial> material;
|
||||
Ref<Shader> shader;
|
||||
} capture;
|
||||
|
||||
// Cross-call state.
|
||||
struct {
|
||||
double anim_player_position = 0.0;
|
||||
Ref<AnimatedValuesBackup> anim_values_backup;
|
||||
Rect2 screen_rect;
|
||||
Dictionary canvas_edit_state;
|
||||
Dictionary spatial_edit_state;
|
||||
} temp;
|
||||
} onion;
|
||||
|
||||
void _select_anim_by_name(const String &p_anim);
|
||||
float _get_editor_step() const;
|
||||
void _go_to_nearest_keyframe(bool p_backward);
|
||||
void _play_pressed();
|
||||
void _play_from_pressed();
|
||||
void _play_bw_pressed();
|
||||
void _play_bw_from_pressed();
|
||||
void _autoplay_pressed();
|
||||
void _stop_pressed();
|
||||
void _animation_selected(int p_which);
|
||||
void _animation_new();
|
||||
void _animation_rename();
|
||||
void _animation_name_edited();
|
||||
|
||||
void _animation_remove();
|
||||
void _animation_remove_confirmed();
|
||||
void _animation_edit();
|
||||
void _animation_duplicate();
|
||||
Ref<Animation> _animation_clone(const Ref<Animation> p_anim);
|
||||
void _animation_resource_edit();
|
||||
void _scale_changed(const String &p_scale);
|
||||
void _seek_value_changed(float p_value, bool p_timeline_only = false);
|
||||
void _blend_editor_next_changed(const int p_idx);
|
||||
|
||||
void _edit_animation_blend();
|
||||
void _update_animation_blend();
|
||||
|
||||
void _list_changed();
|
||||
void _animation_finished(const String &p_name);
|
||||
void _current_animation_changed(const String &p_name);
|
||||
void _update_animation();
|
||||
void _update_player();
|
||||
void _set_controls_disabled(bool p_disabled);
|
||||
void _update_animation_list_icons();
|
||||
void _update_name_dialog_library_dropdown();
|
||||
void _blend_edited();
|
||||
|
||||
void _animation_player_changed(Object *p_pl);
|
||||
void _animation_libraries_updated();
|
||||
|
||||
void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false, bool p_update_position_only = false);
|
||||
void _animation_key_editor_anim_len_changed(float p_len);
|
||||
void _animation_update_key_frame();
|
||||
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_ev) override;
|
||||
void _animation_tool_menu(int p_option);
|
||||
void _onion_skinning_menu(int p_option);
|
||||
|
||||
void _editor_visibility_changed();
|
||||
bool _are_onion_layers_valid();
|
||||
void _allocate_onion_layers();
|
||||
void _free_onion_layers();
|
||||
void _prepare_onion_layers_1();
|
||||
void _prepare_onion_layers_2_prolog();
|
||||
void _prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx);
|
||||
void _prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx);
|
||||
void _prepare_onion_layers_2_epilog();
|
||||
void _start_onion_skinning();
|
||||
void _stop_onion_skinning();
|
||||
|
||||
bool _validate_tracks(const Ref<Animation> p_anim);
|
||||
|
||||
void _pin_pressed();
|
||||
String _get_current() const;
|
||||
|
||||
void _ensure_dummy_player();
|
||||
|
||||
~AnimationPlayerEditor();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
void _node_removed(Node *p_node);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
AnimationMixer *get_editing_node() const;
|
||||
AnimationPlayer *get_player() const;
|
||||
AnimationMixer *fetch_mixer_for_library() const;
|
||||
Node *get_cached_root_node() const;
|
||||
|
||||
static AnimationPlayerEditor *get_singleton() { return singleton; }
|
||||
|
||||
bool is_pinned() const { return pin->is_pressed(); }
|
||||
void unpin() {
|
||||
pin->set_pressed(false);
|
||||
_pin_pressed();
|
||||
}
|
||||
AnimationTrackEditor *get_track_editor() { return track_editor; }
|
||||
Dictionary get_state() const;
|
||||
void set_state(const Dictionary &p_state);
|
||||
void clear();
|
||||
|
||||
void ensure_visibility();
|
||||
|
||||
void edit(AnimationMixer *p_node, AnimationPlayer *p_player, bool p_is_dummy);
|
||||
void forward_force_draw_over_viewport(Control *p_overlay);
|
||||
|
||||
AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin);
|
||||
};
|
||||
|
||||
class AnimationPlayerEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(AnimationPlayerEditorPlugin, EditorPlugin);
|
||||
|
||||
friend AnimationPlayerEditor;
|
||||
|
||||
AnimationPlayerEditor *anim_editor = nullptr;
|
||||
AnimationPlayer *player = nullptr;
|
||||
AnimationPlayer *dummy_player = nullptr;
|
||||
ObjectID last_mixer;
|
||||
|
||||
void _update_dummy_player(AnimationMixer *p_mixer);
|
||||
void _clear_dummy_player();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
void _property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance);
|
||||
void _transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key);
|
||||
void _update_keying();
|
||||
|
||||
public:
|
||||
virtual Dictionary get_state() const override { return anim_editor->get_state(); }
|
||||
virtual void set_state(const Dictionary &p_state) override { anim_editor->set_state(p_state); }
|
||||
virtual void clear() override { anim_editor->clear(); }
|
||||
|
||||
virtual String get_plugin_name() const override { return "Anim"; }
|
||||
bool has_main_screen() const override { return false; }
|
||||
virtual void edit(Object *p_object) override;
|
||||
virtual bool handles(Object *p_object) const override;
|
||||
virtual void make_visible(bool p_visible) override;
|
||||
|
||||
virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); }
|
||||
virtual void forward_3d_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); }
|
||||
|
||||
AnimationPlayerEditorPlugin();
|
||||
~AnimationPlayerEditorPlugin();
|
||||
};
|
||||
|
||||
// AnimationTrackKeyEditEditorPlugin
|
||||
|
||||
class EditorInspectorPluginAnimationTrackKeyEdit : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginAnimationTrackKeyEdit, EditorInspectorPlugin);
|
||||
|
||||
AnimationTrackKeyEditEditor *atk_editor = nullptr;
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class AnimationTrackKeyEditEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(AnimationTrackKeyEditEditorPlugin, EditorPlugin);
|
||||
|
||||
EditorInspectorPluginAnimationTrackKeyEdit *atk_plugin = nullptr;
|
||||
|
||||
public:
|
||||
bool has_main_screen() const override { return false; }
|
||||
virtual bool handles(Object *p_object) const override;
|
||||
|
||||
virtual String get_plugin_name() const override { return "AnimationTrackKeyEdit"; }
|
||||
|
||||
AnimationTrackKeyEditEditorPlugin();
|
||||
};
|
||||
|
||||
// AnimationMarkerKeyEditEditorPlugin
|
||||
|
||||
class EditorInspectorPluginAnimationMarkerKeyEdit : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginAnimationMarkerKeyEdit, EditorInspectorPlugin);
|
||||
|
||||
AnimationMarkerKeyEditEditor *amk_editor = nullptr;
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class AnimationMarkerKeyEditEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(AnimationMarkerKeyEditEditorPlugin, EditorPlugin);
|
||||
|
||||
EditorInspectorPluginAnimationMarkerKeyEdit *amk_plugin = nullptr;
|
||||
|
||||
public:
|
||||
bool has_main_screen() const override { return false; }
|
||||
virtual bool handles(Object *p_object) const override;
|
||||
|
||||
virtual String get_plugin_name() const override { return "AnimationMarkerKeyEdit"; }
|
||||
|
||||
AnimationMarkerKeyEditEditorPlugin();
|
||||
};
|
||||
1968
editor/animation/animation_state_machine_editor.cpp
Normal file
1968
editor/animation/animation_state_machine_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
322
editor/animation/animation_state_machine_editor.h
Normal file
322
editor/animation/animation_state_machine_editor.h
Normal file
@@ -0,0 +1,322 @@
|
||||
/**************************************************************************/
|
||||
/* animation_state_machine_editor.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/animation/animation_tree_editor_plugin.h"
|
||||
#include "scene/animation/animation_node_state_machine.h"
|
||||
#include "scene/gui/graph_edit.h"
|
||||
#include "scene/gui/popup.h"
|
||||
|
||||
class ConfirmationDialog;
|
||||
class EditorFileDialog;
|
||||
class LineEdit;
|
||||
class OptionButton;
|
||||
class PanelContainer;
|
||||
|
||||
class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
|
||||
GDCLASS(AnimationNodeStateMachineEditor, AnimationTreeNodeEditorPlugin);
|
||||
|
||||
Ref<AnimationNodeStateMachine> state_machine;
|
||||
|
||||
bool read_only = false;
|
||||
|
||||
Button *tool_select = nullptr;
|
||||
Button *tool_create = nullptr;
|
||||
Button *tool_connect = nullptr;
|
||||
Popup *name_edit_popup = nullptr;
|
||||
LineEdit *name_edit = nullptr;
|
||||
|
||||
HBoxContainer *selection_tools_hb = nullptr;
|
||||
Button *tool_erase = nullptr;
|
||||
|
||||
HBoxContainer *transition_tools_hb = nullptr;
|
||||
OptionButton *switch_mode = nullptr;
|
||||
Button *auto_advance = nullptr;
|
||||
|
||||
OptionButton *play_mode = nullptr;
|
||||
|
||||
PanelContainer *panel = nullptr;
|
||||
|
||||
StringName selected_node;
|
||||
HashSet<StringName> selected_nodes;
|
||||
|
||||
HScrollBar *h_scroll = nullptr;
|
||||
VScrollBar *v_scroll = nullptr;
|
||||
|
||||
Control *state_machine_draw = nullptr;
|
||||
Control *state_machine_play_pos = nullptr;
|
||||
|
||||
PanelContainer *error_panel = nullptr;
|
||||
Label *error_label = nullptr;
|
||||
|
||||
struct ThemeCache {
|
||||
Ref<StyleBox> panel_style;
|
||||
Ref<StyleBox> error_panel_style;
|
||||
Color error_color;
|
||||
|
||||
Ref<Texture2D> tool_icon_select;
|
||||
Ref<Texture2D> tool_icon_create;
|
||||
Ref<Texture2D> tool_icon_connect;
|
||||
Ref<Texture2D> tool_icon_erase;
|
||||
|
||||
Ref<Texture2D> transition_icon_immediate;
|
||||
Ref<Texture2D> transition_icon_sync;
|
||||
Ref<Texture2D> transition_icon_end;
|
||||
|
||||
Ref<Texture2D> play_icon_start;
|
||||
Ref<Texture2D> play_icon_travel;
|
||||
Ref<Texture2D> play_icon_auto;
|
||||
|
||||
Ref<Texture2D> animation_icon;
|
||||
|
||||
Ref<StyleBox> node_frame;
|
||||
Ref<StyleBox> node_frame_selected;
|
||||
Ref<StyleBox> node_frame_playing;
|
||||
Ref<StyleBox> node_frame_start;
|
||||
Ref<StyleBox> node_frame_end;
|
||||
|
||||
Ref<Font> node_title_font;
|
||||
int node_title_font_size = 0;
|
||||
Color node_title_font_color;
|
||||
|
||||
Ref<Texture2D> play_node;
|
||||
Ref<Texture2D> edit_node;
|
||||
|
||||
Color transition_color;
|
||||
Color transition_disabled_color;
|
||||
Color transition_icon_color;
|
||||
Color transition_icon_disabled_color;
|
||||
Color highlight_color;
|
||||
Color highlight_disabled_color;
|
||||
Color focus_color;
|
||||
Color guideline_color;
|
||||
|
||||
Ref<Texture2D> transition_icons[6]{};
|
||||
|
||||
Color playback_color;
|
||||
Color playback_background_color;
|
||||
} theme_cache;
|
||||
|
||||
bool updating = false;
|
||||
|
||||
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, float p_opacity = 1.0);
|
||||
|
||||
void _state_machine_draw();
|
||||
|
||||
void _state_machine_pos_draw_individual(const String &p_name, float p_ratio);
|
||||
void _state_machine_pos_draw_all();
|
||||
|
||||
void _update_graph();
|
||||
|
||||
PopupMenu *menu = nullptr;
|
||||
PopupMenu *connect_menu = nullptr;
|
||||
PopupMenu *state_machine_menu = nullptr;
|
||||
PopupMenu *end_menu = nullptr;
|
||||
PopupMenu *animations_menu = nullptr;
|
||||
Vector<String> animations_to_add;
|
||||
Vector<String> nodes_to_connect;
|
||||
|
||||
Vector2 add_node_pos;
|
||||
|
||||
bool box_selecting = false;
|
||||
Point2 box_selecting_from;
|
||||
Point2 box_selecting_to;
|
||||
Rect2 box_selecting_rect;
|
||||
HashSet<StringName> previous_selected;
|
||||
|
||||
bool dragging_selected_attempt = false;
|
||||
bool dragging_selected = false;
|
||||
Vector2 drag_from;
|
||||
Vector2 drag_ofs;
|
||||
StringName snap_x;
|
||||
StringName snap_y;
|
||||
|
||||
bool connecting = false;
|
||||
bool connection_follows_cursor = false;
|
||||
StringName connecting_from;
|
||||
Vector2 connecting_to;
|
||||
StringName connecting_to_node;
|
||||
|
||||
void _add_menu_type(int p_index);
|
||||
void _add_animation_type(int p_index);
|
||||
void _connect_to(int p_index);
|
||||
|
||||
struct NodeRect {
|
||||
StringName node_name;
|
||||
Rect2 node;
|
||||
Rect2 play;
|
||||
Rect2 name;
|
||||
Rect2 edit;
|
||||
bool can_edit;
|
||||
};
|
||||
|
||||
Vector<NodeRect> node_rects;
|
||||
|
||||
struct TransitionLine {
|
||||
StringName from_node;
|
||||
StringName to_node;
|
||||
Vector2 from;
|
||||
Vector2 to;
|
||||
AnimationNodeStateMachineTransition::SwitchMode mode;
|
||||
StringName advance_condition_name;
|
||||
bool advance_condition_state = false;
|
||||
bool disabled = false;
|
||||
bool auto_advance = false;
|
||||
float width = 0;
|
||||
bool selected;
|
||||
bool travel;
|
||||
float fade_ratio;
|
||||
bool hidden;
|
||||
int transition_index;
|
||||
bool is_across_group = false;
|
||||
};
|
||||
|
||||
Vector<TransitionLine> transition_lines;
|
||||
|
||||
struct NodeUR {
|
||||
StringName name;
|
||||
Ref<AnimationNode> node;
|
||||
Vector2 position;
|
||||
};
|
||||
|
||||
struct TransitionUR {
|
||||
StringName new_from;
|
||||
StringName new_to;
|
||||
StringName old_from;
|
||||
StringName old_to;
|
||||
Ref<AnimationNodeStateMachineTransition> transition;
|
||||
};
|
||||
|
||||
StringName selected_transition_from;
|
||||
StringName selected_transition_to;
|
||||
int selected_transition_index = -1;
|
||||
void _add_transition(const bool p_nested_action = false);
|
||||
|
||||
enum HoveredNodeArea {
|
||||
HOVER_NODE_NONE = -1,
|
||||
HOVER_NODE_PLAY = 0,
|
||||
HOVER_NODE_EDIT = 1,
|
||||
};
|
||||
|
||||
StringName hovered_node_name;
|
||||
HoveredNodeArea hovered_node_area = HOVER_NODE_NONE;
|
||||
|
||||
String prev_name;
|
||||
void _name_edited(const String &p_text);
|
||||
void _name_edited_focus_out();
|
||||
void _open_editor(const String &p_name);
|
||||
void _scroll_changed(double);
|
||||
|
||||
String _get_root_playback_path(String &r_node_directory);
|
||||
|
||||
void _clip_src_line_to_rect(Vector2 &r_from, const Vector2 &p_to, const Rect2 &p_rect);
|
||||
void _clip_dst_line_to_rect(const Vector2 &p_from, Vector2 &r_to, const Rect2 &p_rect);
|
||||
|
||||
void _erase_selected(const bool p_nested_action = false);
|
||||
void _update_mode();
|
||||
void _open_menu(const Vector2 &p_position);
|
||||
bool _create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path);
|
||||
void _stop_connecting();
|
||||
|
||||
bool last_active = false;
|
||||
StringName last_fading_from_node;
|
||||
StringName last_current_node;
|
||||
Vector<StringName> last_travel_path;
|
||||
|
||||
float fade_from_last_play_pos = 0.0f;
|
||||
float fade_from_current_play_pos = 0.0f;
|
||||
float fade_from_length = 0.0f;
|
||||
|
||||
float last_play_pos = 0.0f;
|
||||
float current_play_pos = 0.0f;
|
||||
float current_length = 0.0f;
|
||||
|
||||
float last_fading_time = 0.0f;
|
||||
float last_fading_pos = 0.0f;
|
||||
float fading_time = 0.0f;
|
||||
float fading_pos = 0.0f;
|
||||
|
||||
float error_time = 0.0f;
|
||||
String error_text;
|
||||
|
||||
EditorFileDialog *open_file = nullptr;
|
||||
Ref<AnimationNode> file_loaded;
|
||||
void _file_opened(const String &p_file);
|
||||
|
||||
enum {
|
||||
MENU_LOAD_FILE = 1000,
|
||||
MENU_PASTE = 1001,
|
||||
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();
|
||||
|
||||
public:
|
||||
static AnimationNodeStateMachineEditor *get_singleton() { return singleton; }
|
||||
|
||||
virtual bool can_edit(const Ref<AnimationNode> &p_node) override;
|
||||
virtual void edit(const Ref<AnimationNode> &p_node) override;
|
||||
|
||||
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
|
||||
virtual String get_tooltip(const Point2 &p_pos) const override;
|
||||
|
||||
AnimationNodeStateMachineEditor();
|
||||
};
|
||||
|
||||
class EditorAnimationMultiTransitionEdit : public RefCounted {
|
||||
GDCLASS(EditorAnimationMultiTransitionEdit, RefCounted);
|
||||
|
||||
struct Transition {
|
||||
StringName from;
|
||||
StringName to;
|
||||
Ref<AnimationNodeStateMachineTransition> transition;
|
||||
};
|
||||
|
||||
Vector<Transition> transitions;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_property);
|
||||
bool _get(const StringName &p_name, Variant &r_property) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
void add_transition(const StringName &p_from, const StringName &p_to, Ref<AnimationNodeStateMachineTransition> p_transition);
|
||||
};
|
||||
9569
editor/animation/animation_track_editor.cpp
Normal file
9569
editor/animation/animation_track_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1023
editor/animation/animation_track_editor.h
Normal file
1023
editor/animation/animation_track_editor.h
Normal file
File diff suppressed because it is too large
Load Diff
1375
editor/animation/animation_track_editor_plugins.cpp
Normal file
1375
editor/animation/animation_track_editor_plugins.cpp
Normal file
File diff suppressed because it is too large
Load Diff
165
editor/animation/animation_track_editor_plugins.h
Normal file
165
editor/animation/animation_track_editor_plugins.h
Normal file
@@ -0,0 +1,165 @@
|
||||
/**************************************************************************/
|
||||
/* animation_track_editor_plugins.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/animation/animation_track_editor.h"
|
||||
|
||||
class AnimationTrackEditBool : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditBool, AnimationTrackEdit);
|
||||
Ref<Texture2D> icon_checked;
|
||||
Ref<Texture2D> icon_unchecked;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
};
|
||||
|
||||
class AnimationTrackEditColor : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditColor, AnimationTrackEdit);
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) override;
|
||||
};
|
||||
|
||||
class AnimationTrackEditAudio : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditAudio, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
|
||||
void _preview_changed(ObjectID p_which);
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
|
||||
AnimationTrackEditAudio();
|
||||
};
|
||||
|
||||
class AnimationTrackEditSpriteFrame : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditSpriteFrame, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
bool is_coords = false;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
void set_as_coords();
|
||||
};
|
||||
|
||||
class AnimationTrackEditSubAnim : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditSubAnim, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
};
|
||||
|
||||
class AnimationTrackEditTypeAudio : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditTypeAudio, AnimationTrackEdit);
|
||||
|
||||
void _preview_changed(ObjectID p_which);
|
||||
|
||||
bool len_resizing = false;
|
||||
bool len_resizing_start = false;
|
||||
int len_resizing_index = 0;
|
||||
float len_resizing_from_px = 0.0f;
|
||||
float len_resizing_rel = 0.0f;
|
||||
bool over_drag_position = false;
|
||||
|
||||
public:
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
|
||||
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override;
|
||||
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
|
||||
|
||||
AnimationTrackEditTypeAudio();
|
||||
};
|
||||
|
||||
class AnimationTrackEditTypeAnimation : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditTypeAnimation, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
};
|
||||
|
||||
class AnimationTrackEditVolumeDB : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditVolumeDB, AnimationTrackEdit);
|
||||
|
||||
public:
|
||||
virtual void draw_bg(int p_clip_left, int p_clip_right) override;
|
||||
virtual void draw_fg(int p_clip_left, int p_clip_right) override;
|
||||
virtual int get_key_height() const override;
|
||||
virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) override;
|
||||
};
|
||||
|
||||
class AnimationTrackEditDefaultPlugin : public AnimationTrackEditPlugin {
|
||||
GDCLASS(AnimationTrackEditDefaultPlugin, AnimationTrackEditPlugin);
|
||||
|
||||
public:
|
||||
virtual AnimationTrackEdit *create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage) override;
|
||||
virtual AnimationTrackEdit *create_audio_track_edit() override;
|
||||
virtual AnimationTrackEdit *create_animation_track_edit(Object *p_object) override;
|
||||
};
|
||||
310
editor/animation/animation_tree_editor_plugin.cpp
Normal file
310
editor/animation/animation_tree_editor_plugin.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
/**************************************************************************/
|
||||
/* animation_tree_editor_plugin.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "animation_tree_editor_plugin.h"
|
||||
|
||||
#include "animation_blend_space_1d_editor.h"
|
||||
#include "animation_blend_space_2d_editor.h"
|
||||
#include "animation_blend_tree_editor_plugin.h"
|
||||
#include "animation_state_machine_editor.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/gui/editor_bottom_panel.h"
|
||||
#include "editor/settings/editor_command_palette.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/animation/animation_blend_tree.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
#include "scene/gui/scroll_container.h"
|
||||
#include "scene/gui/separator.h"
|
||||
|
||||
void AnimationTreeEditor::edit(AnimationTree *p_tree) {
|
||||
if (p_tree && !p_tree->is_connected("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) {
|
||||
p_tree->connect("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed), CONNECT_DEFERRED);
|
||||
}
|
||||
|
||||
if (tree == p_tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tree && tree->is_connected("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) {
|
||||
tree->disconnect("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed));
|
||||
}
|
||||
|
||||
tree = p_tree;
|
||||
|
||||
Vector<String> path;
|
||||
if (tree) {
|
||||
edit_path(path);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::_node_removed(Node *p_node) {
|
||||
if (p_node == tree) {
|
||||
tree = nullptr;
|
||||
_clear_editors();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::_path_button_pressed(int p_path) {
|
||||
edited_path.clear();
|
||||
for (int i = 0; i <= p_path; i++) {
|
||||
edited_path.push_back(button_path[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::_animation_list_changed() {
|
||||
AnimationNodeBlendTreeEditor *bte = AnimationNodeBlendTreeEditor::get_singleton();
|
||||
if (bte) {
|
||||
bte->update_graph();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::_update_path() {
|
||||
while (path_hb->get_child_count() > 1) {
|
||||
memdelete(path_hb->get_child(1));
|
||||
}
|
||||
|
||||
Ref<ButtonGroup> group;
|
||||
group.instantiate();
|
||||
|
||||
Button *b = memnew(Button);
|
||||
b->set_text(TTR("Root"));
|
||||
b->set_toggle_mode(true);
|
||||
b->set_button_group(group);
|
||||
b->set_pressed(true);
|
||||
b->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
b->connect(SceneStringName(pressed), callable_mp(this, &AnimationTreeEditor::_path_button_pressed).bind(-1));
|
||||
path_hb->add_child(b);
|
||||
for (int i = 0; i < button_path.size(); i++) {
|
||||
b = memnew(Button);
|
||||
b->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
b->set_text(button_path[i]);
|
||||
b->set_toggle_mode(true);
|
||||
b->set_button_group(group);
|
||||
path_hb->add_child(b);
|
||||
b->set_pressed(true);
|
||||
b->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
b->connect(SceneStringName(pressed), callable_mp(this, &AnimationTreeEditor::_path_button_pressed).bind(i));
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::edit_path(const Vector<String> &p_path) {
|
||||
button_path.clear();
|
||||
|
||||
Ref<AnimationNode> node = tree->get_root_animation_node();
|
||||
|
||||
if (node.is_valid()) {
|
||||
current_root = node->get_instance_id();
|
||||
|
||||
for (int i = 0; i < p_path.size(); i++) {
|
||||
Ref<AnimationNode> child = node->get_child_by_name(p_path[i]);
|
||||
ERR_BREAK(child.is_null());
|
||||
node = child;
|
||||
button_path.push_back(p_path[i]);
|
||||
}
|
||||
|
||||
edited_path = button_path;
|
||||
|
||||
for (int i = 0; i < editors.size(); i++) {
|
||||
if (editors[i]->can_edit(node)) {
|
||||
editors[i]->edit(node);
|
||||
editors[i]->show();
|
||||
} else {
|
||||
editors[i]->edit(Ref<AnimationNode>());
|
||||
editors[i]->hide();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_root = ObjectID();
|
||||
edited_path = button_path;
|
||||
for (int i = 0; i < editors.size(); i++) {
|
||||
editors[i]->edit(Ref<AnimationNode>());
|
||||
editors[i]->hide();
|
||||
}
|
||||
}
|
||||
|
||||
_update_path();
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::_clear_editors() {
|
||||
button_path.clear();
|
||||
current_root = ObjectID();
|
||||
edited_path = button_path;
|
||||
for (int i = 0; i < editors.size(); i++) {
|
||||
editors[i]->edit(Ref<AnimationNode>());
|
||||
editors[i]->hide();
|
||||
}
|
||||
_update_path();
|
||||
}
|
||||
|
||||
Vector<String> AnimationTreeEditor::get_edited_path() const {
|
||||
return button_path;
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::enter_editor(const String &p_path) {
|
||||
Vector<String> path = edited_path;
|
||||
path.push_back(p_path);
|
||||
edit_path(path);
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
get_tree()->connect("node_removed", callable_mp(this, &AnimationTreeEditor::_node_removed));
|
||||
} break;
|
||||
case NOTIFICATION_PROCESS: {
|
||||
ObjectID root;
|
||||
if (tree && tree->get_root_animation_node().is_valid()) {
|
||||
root = tree->get_root_animation_node()->get_instance_id();
|
||||
}
|
||||
|
||||
if (root != current_root) {
|
||||
edit_path(Vector<String>());
|
||||
}
|
||||
|
||||
if (button_path.size() != edited_path.size()) {
|
||||
edit_path(edited_path);
|
||||
}
|
||||
} break;
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
get_tree()->disconnect("node_removed", callable_mp(this, &AnimationTreeEditor::_node_removed));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
AnimationTreeEditor *AnimationTreeEditor::singleton = nullptr;
|
||||
|
||||
void AnimationTreeEditor::add_plugin(AnimationTreeNodeEditorPlugin *p_editor) {
|
||||
ERR_FAIL_COND(p_editor->get_parent());
|
||||
editor_base->add_child(p_editor);
|
||||
editors.push_back(p_editor);
|
||||
p_editor->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
p_editor->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
p_editor->hide();
|
||||
}
|
||||
|
||||
void AnimationTreeEditor::remove_plugin(AnimationTreeNodeEditorPlugin *p_editor) {
|
||||
ERR_FAIL_COND(p_editor->get_parent() != editor_base);
|
||||
editor_base->remove_child(p_editor);
|
||||
editors.erase(p_editor);
|
||||
}
|
||||
|
||||
String AnimationTreeEditor::get_base_path() {
|
||||
String path = Animation::PARAMETERS_BASE_PATH;
|
||||
for (int i = 0; i < edited_path.size(); i++) {
|
||||
path += edited_path[i] + "/";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool AnimationTreeEditor::can_edit(const Ref<AnimationNode> &p_node) const {
|
||||
for (int i = 0; i < editors.size(); i++) {
|
||||
if (editors[i]->can_edit(p_node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<String> AnimationTreeEditor::get_animation_list() {
|
||||
// This can be called off the main thread due to resource preview generation. Quit early in that case.
|
||||
if (!singleton->tree || !Thread::is_main_thread() || !singleton->is_visible()) {
|
||||
// When tree is empty, singleton not in the main thread.
|
||||
return Vector<String>();
|
||||
}
|
||||
|
||||
AnimationTree *tree = singleton->tree;
|
||||
if (!tree) {
|
||||
return Vector<String>();
|
||||
}
|
||||
|
||||
List<StringName> anims;
|
||||
tree->get_animation_list(&anims);
|
||||
Vector<String> ret;
|
||||
for (const StringName &E : anims) {
|
||||
ret.push_back(E);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AnimationTreeEditor::AnimationTreeEditor() {
|
||||
AnimationNodeAnimation::get_editable_animation_list = get_animation_list;
|
||||
path_edit = memnew(ScrollContainer);
|
||||
add_child(path_edit);
|
||||
path_edit->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
|
||||
path_hb = memnew(HBoxContainer);
|
||||
path_edit->add_child(path_hb);
|
||||
path_hb->add_child(memnew(Label(TTR("Path:"))));
|
||||
|
||||
add_child(memnew(HSeparator));
|
||||
|
||||
singleton = this;
|
||||
editor_base = memnew(MarginContainer);
|
||||
editor_base->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
add_child(editor_base);
|
||||
|
||||
add_plugin(memnew(AnimationNodeBlendTreeEditor));
|
||||
add_plugin(memnew(AnimationNodeBlendSpace1DEditor));
|
||||
add_plugin(memnew(AnimationNodeBlendSpace2DEditor));
|
||||
add_plugin(memnew(AnimationNodeStateMachineEditor));
|
||||
}
|
||||
|
||||
void AnimationTreeEditorPlugin::edit(Object *p_object) {
|
||||
anim_tree_editor->edit(Object::cast_to<AnimationTree>(p_object));
|
||||
}
|
||||
|
||||
bool AnimationTreeEditorPlugin::handles(Object *p_object) const {
|
||||
return p_object->is_class("AnimationTree");
|
||||
}
|
||||
|
||||
void AnimationTreeEditorPlugin::make_visible(bool p_visible) {
|
||||
if (p_visible) {
|
||||
//editor->hide_animation_player_editors();
|
||||
//editor->animation_panel_make_visible(true);
|
||||
button->show();
|
||||
EditorNode::get_bottom_panel()->make_item_visible(anim_tree_editor);
|
||||
anim_tree_editor->set_process(true);
|
||||
} else {
|
||||
if (anim_tree_editor->is_visible_in_tree()) {
|
||||
EditorNode::get_bottom_panel()->hide_bottom_panel();
|
||||
}
|
||||
button->hide();
|
||||
anim_tree_editor->set_process(false);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationTreeEditorPlugin::AnimationTreeEditorPlugin() {
|
||||
anim_tree_editor = memnew(AnimationTreeEditor);
|
||||
anim_tree_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
|
||||
|
||||
button = EditorNode::get_bottom_panel()->add_item(TTRC("AnimationTree"), anim_tree_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_animation_tree_bottom_panel", TTRC("Toggle AnimationTree Bottom Panel")));
|
||||
button->hide();
|
||||
}
|
||||
109
editor/animation/animation_tree_editor_plugin.h
Normal file
109
editor/animation/animation_tree_editor_plugin.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/**************************************************************************/
|
||||
/* animation_tree_editor_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/animation/animation_tree.h"
|
||||
#include "scene/gui/graph_edit.h"
|
||||
|
||||
class Button;
|
||||
class EditorFileDialog;
|
||||
class ScrollContainer;
|
||||
|
||||
class AnimationTreeNodeEditorPlugin : public VBoxContainer {
|
||||
GDCLASS(AnimationTreeNodeEditorPlugin, VBoxContainer);
|
||||
|
||||
public:
|
||||
virtual bool can_edit(const Ref<AnimationNode> &p_node) = 0;
|
||||
virtual void edit(const Ref<AnimationNode> &p_node) = 0;
|
||||
};
|
||||
|
||||
class AnimationTreeEditor : public VBoxContainer {
|
||||
GDCLASS(AnimationTreeEditor, VBoxContainer);
|
||||
|
||||
ScrollContainer *path_edit = nullptr;
|
||||
HBoxContainer *path_hb = nullptr;
|
||||
|
||||
AnimationTree *tree = nullptr;
|
||||
MarginContainer *editor_base = nullptr;
|
||||
|
||||
Vector<String> button_path;
|
||||
Vector<String> edited_path;
|
||||
Vector<AnimationTreeNodeEditorPlugin *> editors;
|
||||
|
||||
void _update_path();
|
||||
void _clear_editors();
|
||||
ObjectID current_root;
|
||||
|
||||
void _path_button_pressed(int p_path);
|
||||
void _animation_list_changed();
|
||||
|
||||
static Vector<String> get_animation_list();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
void _node_removed(Node *p_node);
|
||||
|
||||
static AnimationTreeEditor *singleton;
|
||||
|
||||
public:
|
||||
AnimationTree *get_animation_tree() { return tree; }
|
||||
void add_plugin(AnimationTreeNodeEditorPlugin *p_editor);
|
||||
void remove_plugin(AnimationTreeNodeEditorPlugin *p_editor);
|
||||
|
||||
String get_base_path();
|
||||
|
||||
bool can_edit(const Ref<AnimationNode> &p_node) const;
|
||||
|
||||
void edit_path(const Vector<String> &p_path);
|
||||
Vector<String> get_edited_path() const;
|
||||
|
||||
void enter_editor(const String &p_path = "");
|
||||
static AnimationTreeEditor *get_singleton() { return singleton; }
|
||||
void edit(AnimationTree *p_tree);
|
||||
AnimationTreeEditor();
|
||||
};
|
||||
|
||||
class AnimationTreeEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(AnimationTreeEditorPlugin, EditorPlugin);
|
||||
|
||||
AnimationTreeEditor *anim_tree_editor = nullptr;
|
||||
Button *button = nullptr;
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "AnimationTree"; }
|
||||
bool has_main_screen() const override { return false; }
|
||||
virtual void edit(Object *p_object) override;
|
||||
virtual bool handles(Object *p_object) const override;
|
||||
virtual void make_visible(bool p_visible) override;
|
||||
|
||||
AnimationTreeEditorPlugin();
|
||||
};
|
||||
Reference in New Issue
Block a user