diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index fd06bf0533ff..40ea0a378b12 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -266,23 +266,11 @@ void AnimationBezierTrackEdit::_notification(int p_what) { RBMap> 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 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 &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(); diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index ec2b52221eb6..371c5632678b 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -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 @@ class AnimationBezierTrackEdit : public Control { 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(); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 6f1439a91f2d..7f0d4a124fc5 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -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 &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 &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")); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index d0da7b00623c..d2d8462dbc1f 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -182,6 +182,9 @@ class AnimationTimelineEdit : public Range { virtual void gui_input(const Ref &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 @@ class AnimationTimelineEdit : public Range { 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; diff --git a/editor/icons/AnimationAutoFit.svg b/editor/icons/AnimationAutoFit.svg new file mode 100644 index 000000000000..fdde20d4641d --- /dev/null +++ b/editor/icons/AnimationAutoFit.svg @@ -0,0 +1,2 @@ + + diff --git a/editor/icons/AnimationAutoFitBezier.svg b/editor/icons/AnimationAutoFitBezier.svg new file mode 100644 index 000000000000..1a79255c198d --- /dev/null +++ b/editor/icons/AnimationAutoFitBezier.svg @@ -0,0 +1,2 @@ + +