Skip to content

Commit

Permalink
Add auto focus timeline and bezier scale on animation editor
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Hilderin committed Mar 20, 2024
1 parent fe01776 commit b46d0a6
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 36 deletions.
129 changes: 94 additions & 35 deletions editor/animation_bezier_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions editor/animation_bezier_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down
103 changes: 102 additions & 1 deletion editor/animation_track_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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++) {
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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) {
Expand All @@ -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.
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"));
Expand Down
9 changes: 9 additions & 0 deletions editor/animation_track_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit b46d0a6

Please sign in to comment.