Add auto focus timeline and bezier scale on animation editor
Add a button at the bottom of the animation editor that change the zoom based on the animation length and the bezier scale based on the values and handles of the displayed tracks. The icon and the tooltip of the button change depending if the bezier editor is displayed or not. Some refactor was made in animation_track_editor.cpp to remove code duplication with the visibility check of the tracks. This should help with the issue #85826
This commit is contained in:
parent
fe01776f05
commit
b46d0a6ea8
|
@ -266,23 +266,11 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
|
|||
RBMap<String, Vector<int>> track_indices;
|
||||
int track_count = animation->get_track_count();
|
||||
for (int i = 0; i < track_count; ++i) {
|
||||
if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) {
|
||||
if (!_is_track_displayed(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String base_path = animation->track_get_path(i);
|
||||
if (is_filtered) {
|
||||
if (root && root->has_node(base_path)) {
|
||||
Node *node = root->get_node(base_path);
|
||||
if (!node) {
|
||||
continue; // No node, no filter.
|
||||
}
|
||||
if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
|
||||
continue; // Skip track due to not selected.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int end = base_path.find(":");
|
||||
if (end != -1) {
|
||||
base_path = base_path.substr(0, end + 1);
|
||||
|
@ -520,28 +508,11 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
|
|||
float scale = timeline->get_zoom_scale();
|
||||
|
||||
for (int i = 0; i < track_count; ++i) {
|
||||
if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hidden_tracks.has(i) || locked_tracks.has(i)) {
|
||||
if (!_is_track_curves_displayed(i) || locked_tracks.has(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int key_count = animation->track_get_key_count(i);
|
||||
String path = animation->track_get_path(i);
|
||||
|
||||
if (is_filtered) {
|
||||
if (root && root->has_node(path)) {
|
||||
Node *node = root->get_node(path);
|
||||
if (!node) {
|
||||
continue; // No node, no filter.
|
||||
}
|
||||
if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
|
||||
continue; // Skip track due to not selected.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < key_count; ++j) {
|
||||
float offset = animation->track_get_key_time(i, j);
|
||||
|
@ -648,6 +619,43 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if a track is displayed in the bezier editor (track type = bezier and track not filtered).
|
||||
bool AnimationBezierTrackEdit::_is_track_displayed(int p_track_index) {
|
||||
if (animation->track_get_type(p_track_index) != Animation::TrackType::TYPE_BEZIER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_filtered) {
|
||||
String path = animation->track_get_path(p_track_index);
|
||||
if (root && root->has_node(path)) {
|
||||
Node *node = root->get_node(path);
|
||||
if (!node) {
|
||||
return false; // No node, no filter.
|
||||
}
|
||||
if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {
|
||||
return false; // Skip track due to not selected.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the curves for a track are displayed in the editor (not hidden). Includes the check on the track visibility.
|
||||
bool AnimationBezierTrackEdit::_is_track_curves_displayed(int p_track_index) {
|
||||
//Is the track is visible in the editor?
|
||||
if (!_is_track_displayed(p_track_index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//And curves visible?
|
||||
if (hidden_tracks.has(p_track_index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<Animation> AnimationBezierTrackEdit::get_animation() const {
|
||||
return animation;
|
||||
}
|
||||
|
@ -741,6 +749,60 @@ void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {
|
|||
queue_redraw();
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::auto_fit_vertically() {
|
||||
int track_count = animation->get_track_count();
|
||||
real_t minimum_value = INFINITY;
|
||||
real_t maximum_value = -INFINITY;
|
||||
|
||||
int nb_track_visible = 0;
|
||||
for (int i = 0; i < track_count; ++i) {
|
||||
if (!_is_track_curves_displayed(i) || locked_tracks.has(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int key_count = animation->track_get_key_count(i);
|
||||
|
||||
for (int j = 0; j < key_count; ++j) {
|
||||
real_t value = animation->bezier_track_get_key_value(i, j);
|
||||
|
||||
minimum_value = MIN(value, minimum_value);
|
||||
maximum_value = MAX(value, maximum_value);
|
||||
|
||||
// We also want to includes the handles...
|
||||
Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);
|
||||
Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);
|
||||
|
||||
minimum_value = MIN(value + in_vec.y, minimum_value);
|
||||
maximum_value = MAX(value + in_vec.y, maximum_value);
|
||||
minimum_value = MIN(value + out_vec.y, minimum_value);
|
||||
maximum_value = MAX(value + out_vec.y, maximum_value);
|
||||
}
|
||||
|
||||
nb_track_visible++;
|
||||
}
|
||||
|
||||
if (nb_track_visible == 0) {
|
||||
// No visible track... we will not adjust the vertical zoom
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {
|
||||
_zoom_vertically(minimum_value, maximum_value);
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::_zoom_vertically(real_t p_minimum_value, real_t p_maximum_value) {
|
||||
real_t target_height = p_maximum_value - p_minimum_value;
|
||||
if (target_height <= CMP_EPSILON) {
|
||||
timeline_v_scroll = p_maximum_value;
|
||||
return;
|
||||
}
|
||||
|
||||
timeline_v_scroll = (p_maximum_value + p_minimum_value) / 2.0;
|
||||
timeline_v_zoom = target_height / ((get_size().height - timeline->get_size().height) * 0.9);
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::_zoom_changed() {
|
||||
queue_redraw();
|
||||
play_position->queue_redraw();
|
||||
|
@ -931,10 +993,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
|||
}
|
||||
|
||||
if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {
|
||||
timeline_v_scroll = (maximum_value + minimum_value) / 2.0;
|
||||
if (maximum_value - minimum_value > CMP_EPSILON) {
|
||||
timeline_v_zoom = (maximum_value - minimum_value) / ((get_size().height - timeline->get_size().height) * 0.9);
|
||||
}
|
||||
_zoom_vertically(minimum_value, maximum_value);
|
||||
}
|
||||
|
||||
queue_redraw();
|
||||
|
|
|
@ -101,6 +101,8 @@ class AnimationBezierTrackEdit : public Control {
|
|||
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;
|
||||
|
||||
|
@ -188,6 +190,7 @@ class AnimationBezierTrackEdit : public Control {
|
|||
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();
|
||||
|
@ -208,6 +211,7 @@ public:
|
|||
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();
|
||||
|
|
|
@ -1305,7 +1305,11 @@ void AnimationTimelineEdit::_zoom_changed(double) {
|
|||
}
|
||||
|
||||
float AnimationTimelineEdit::get_zoom_scale() const {
|
||||
float zv = zoom->get_max() - zoom->get_value();
|
||||
return _get_zoom_scale(zoom->get_value());
|
||||
}
|
||||
|
||||
float AnimationTimelineEdit::_get_zoom_scale(double p_zoom_value) const {
|
||||
float zv = zoom->get_max() - p_zoom_value;
|
||||
if (zv < 1) {
|
||||
zv = 1.0 - zv;
|
||||
return Math::pow(1.0f + zv, 8.0f) * 100;
|
||||
|
@ -1633,6 +1637,68 @@ void AnimationTimelineEdit::set_zoom(Range *p_zoom) {
|
|||
zoom->connect("value_changed", callable_mp(this, &AnimationTimelineEdit::_zoom_changed));
|
||||
}
|
||||
|
||||
void AnimationTimelineEdit::auto_fit() {
|
||||
if (!animation.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float anim_end = animation->get_length();
|
||||
float anim_start = 0;
|
||||
|
||||
// Search for keyframe outside animation boundaries to include keyframes before animation start and after animation length.
|
||||
int track_count = animation->get_track_count();
|
||||
for (int track = 0; track < track_count; ++track) {
|
||||
for (int i = 0; i < animation->track_get_key_count(track); i++) {
|
||||
float key_time = animation->track_get_key_time(track, i);
|
||||
if (key_time > anim_end) {
|
||||
anim_end = key_time;
|
||||
}
|
||||
if (key_time < anim_start) {
|
||||
anim_start = key_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float anim_length = anim_end - anim_start;
|
||||
int timeline_width_pixels = get_size().width - get_buttons_width() - get_name_limit();
|
||||
|
||||
// I want a little buffer at the end... (5% looks nice and we should keep some space for the bezier handles)
|
||||
timeline_width_pixels *= 0.95;
|
||||
|
||||
// The technique is to reuse the _get_zoom_scale function directly to be sure that the auto_fit is always calculated
|
||||
// the same way as the zoom slider. It's a little bit more calculation then doing the inverse of get_zoom_scale but
|
||||
// it's really easier to understand and should always be accurate.
|
||||
float new_zoom = zoom->get_max();
|
||||
while (true) {
|
||||
double test_zoom_scale = _get_zoom_scale(new_zoom);
|
||||
|
||||
if (anim_length * test_zoom_scale <= timeline_width_pixels) {
|
||||
// It fits...
|
||||
break;
|
||||
}
|
||||
|
||||
new_zoom -= zoom->get_step();
|
||||
|
||||
if (new_zoom <= zoom->get_min()) {
|
||||
new_zoom = zoom->get_min();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal scroll to get_min which should include keyframes that are before the animation start.
|
||||
hscroll->set_value(hscroll->get_min());
|
||||
// Set the zoom value... the signal value_changed will be emitted and the timeline will be refreshed correctly!
|
||||
zoom->set_value(new_zoom);
|
||||
// The new zoom value must be applied correctly so the scrollbar are updated before we move the scrollbar to
|
||||
// the beginning of the animation, hence the call deferred.
|
||||
callable_mp(this, &AnimationTimelineEdit::_scroll_to_start).call_deferred();
|
||||
}
|
||||
|
||||
void AnimationTimelineEdit::_scroll_to_start() {
|
||||
// Horizontal scroll to get_min which should include keyframes that are before the animation start.
|
||||
hscroll->set_value(hscroll->get_min());
|
||||
}
|
||||
|
||||
void AnimationTimelineEdit::set_track_edit(AnimationTrackEdit *p_track_edit) {
|
||||
track_edit = p_track_edit;
|
||||
}
|
||||
|
@ -3446,6 +3512,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
|
|||
step->set_read_only(false);
|
||||
snap->set_disabled(false);
|
||||
snap_mode->set_disabled(false);
|
||||
auto_fit->set_disabled(false);
|
||||
auto_fit_bezier->set_disabled(false);
|
||||
|
||||
imported_anim_warning->hide();
|
||||
for (int i = 0; i < animation->get_track_count(); i++) {
|
||||
|
@ -3466,6 +3534,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
|
|||
snap->set_disabled(true);
|
||||
snap_mode->set_disabled(true);
|
||||
bezier_edit_icon->set_disabled(true);
|
||||
auto_fit->set_disabled(true);
|
||||
auto_fit_bezier->set_disabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4763,6 +4833,8 @@ void AnimationTrackEditor::_notification(int p_what) {
|
|||
inactive_player_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
|
||||
main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
|
||||
edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_editor_theme_icon(SNAME("Reload")));
|
||||
auto_fit->set_icon(get_editor_theme_icon(SNAME("AnimationAutoFit")));
|
||||
auto_fit_bezier->set_icon(get_editor_theme_icon(SNAME("AnimationAutoFitBezier")));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_READY: {
|
||||
|
@ -5617,6 +5689,8 @@ void AnimationTrackEditor::_cancel_bezier_edit() {
|
|||
bezier_edit->hide();
|
||||
scroll->show();
|
||||
bezier_edit_icon->set_pressed(false);
|
||||
auto_fit->show();
|
||||
auto_fit_bezier->hide();
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_bezier_edit(int p_for_track) {
|
||||
|
@ -5625,6 +5699,8 @@ void AnimationTrackEditor::_bezier_edit(int p_for_track) {
|
|||
bezier_edit->set_animation_and_track(animation, p_for_track, read_only);
|
||||
scroll->hide();
|
||||
bezier_edit->show();
|
||||
auto_fit->hide();
|
||||
auto_fit_bezier->show();
|
||||
// Search everything within the track and curve - edit it.
|
||||
}
|
||||
|
||||
|
@ -6865,6 +6941,18 @@ bool AnimationTrackEditor::is_grouping_tracks() {
|
|||
return !view_group->is_pressed();
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_auto_fit() {
|
||||
timeline->auto_fit();
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_auto_fit_bezier() {
|
||||
timeline->auto_fit();
|
||||
|
||||
if (bezier_edit->is_visible()) {
|
||||
bezier_edit->auto_fit_vertically();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_selection_changed() {
|
||||
if (selected_filter->is_pressed()) {
|
||||
_update_tracks(); // Needs updatin.
|
||||
|
@ -7179,6 +7267,19 @@ AnimationTrackEditor::AnimationTrackEditor() {
|
|||
bottom_hb->add_child(zoom);
|
||||
timeline->set_zoom(zoom);
|
||||
|
||||
auto_fit = memnew(Button);
|
||||
auto_fit->set_flat(true);
|
||||
auto_fit->connect("pressed", callable_mp(this, &AnimationTrackEditor::_auto_fit));
|
||||
auto_fit->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F));
|
||||
bottom_hb->add_child(auto_fit);
|
||||
|
||||
auto_fit_bezier = memnew(Button);
|
||||
auto_fit_bezier->set_flat(true);
|
||||
auto_fit_bezier->set_visible(false);
|
||||
auto_fit_bezier->connect("pressed", callable_mp(this, &AnimationTrackEditor::_auto_fit_bezier));
|
||||
auto_fit_bezier->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F));
|
||||
bottom_hb->add_child(auto_fit_bezier);
|
||||
|
||||
edit = memnew(MenuButton);
|
||||
edit->set_shortcut_context(this);
|
||||
edit->set_text(TTR("Edit"));
|
||||
|
|
|
@ -182,6 +182,9 @@ class AnimationTimelineEdit : public Range {
|
|||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
void _track_added(int p_track);
|
||||
|
||||
float _get_zoom_scale(double p_zoom_value) const;
|
||||
void _scroll_to_start();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
@ -197,6 +200,7 @@ public:
|
|||
void set_track_edit(AnimationTrackEdit *p_track_edit);
|
||||
void set_zoom(Range *p_zoom);
|
||||
Range *get_zoom() const { return zoom; }
|
||||
void auto_fit();
|
||||
|
||||
void set_play_position(float p_pos);
|
||||
float get_play_position() const;
|
||||
|
@ -404,6 +408,8 @@ class AnimationTrackEditor : public VBoxContainer {
|
|||
Button *snap = nullptr;
|
||||
Button *bezier_edit_icon = nullptr;
|
||||
OptionButton *snap_mode = nullptr;
|
||||
Button *auto_fit = nullptr;
|
||||
Button *auto_fit_bezier = nullptr;
|
||||
|
||||
Button *imported_anim_warning = nullptr;
|
||||
void _show_imported_anim_warning();
|
||||
|
@ -591,6 +597,9 @@ class AnimationTrackEditor : public VBoxContainer {
|
|||
Button *view_group = nullptr;
|
||||
Button *selected_filter = nullptr;
|
||||
|
||||
void _auto_fit();
|
||||
void _auto_fit_bezier();
|
||||
|
||||
void _selection_changed();
|
||||
|
||||
ConfirmationDialog *track_copy_dialog = nullptr;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m15.477 0.99862v14h-2.1017v-14zm-14.954 14.003v-14h2.1017v14zm11.946-6.823-3.5-3v6zm-8.9343 0.023985 3.5 3v-2h2.1464l0.00376-2h-2.1501v-2zm4.6005-7.0028c8.9077 15.09 8.9077 15.09 0 0zm-0.23085 14.003c-8.9077-15.09-8.9077-15.09 0 0z" fill="#e0e0e0" stroke-width="1.0251"/></svg>
|
After Width: | Height: | Size: 423 B |
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m12.469 8.1784-3.5-3v6zm-8.9343 0.023985 3.5 3v-2h2.1464l0.00376-2h-2.1501v-2zm9.4532 0.53338c-10.763 8.9077-10.763 8.9077 0 0zm0 7h-9.9859v-2h9.9859zm-9.9806-8.5564c10.763-8.9077 10.763-8.9077 0 0zm0-7h9.9859v2h-9.9859zm5.4684 2.8277c8.9077 10.763 8.9077 10.763 0 0zm7 0v9.9859h-2v-9.9859zm-7.8862 9.9859c-8.9077-10.763-8.9077-10.763 0 0zm-7 0v-9.9859h2v9.9859z" fill="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 532 B |
Loading…
Reference in New Issue