Path: blob/master/editor/animation/animation_bezier_editor.cpp
21102 views
/**************************************************************************/1/* animation_bezier_editor.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "animation_bezier_editor.h"3132#include "core/string/translation_server.h"33#include "editor/animation/animation_player_editor_plugin.h"34#include "editor/editor_node.h"35#include "editor/editor_string_names.h"36#include "editor/editor_undo_redo_manager.h"37#include "editor/gui/editor_spin_slider.h"38#include "editor/settings/editor_settings.h"39#include "editor/themes/editor_scale.h"40#include "scene/gui/option_button.h"41#include "scene/gui/view_panner.h"42#include "scene/resources/text_line.h"4344#include <climits>4546float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) {47float h = p_h;48h = (h - timeline_v_scroll) / timeline_v_zoom;49h = (get_size().height / 2.0) - h;50return h;51}5253void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {54float scale = timeline->get_zoom_scale();5556int limit = timeline->get_name_limit();57int right_limit = get_size().width;5859// Selection may have altered the order of keys.60RBMap<real_t, int> key_order;6162for (int i = 0; i < animation->track_get_key_count(p_track); i++) {63real_t ofs = animation->track_get_key_time(p_track, i);64if (selection.has(IntPair(p_track, i))) {65if (moving_selection) {66ofs += moving_selection_offset.x;67} else if (scaling_selection) {68ofs += -scaling_selection_offset.x + (ofs - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);69}70}7172key_order[ofs] = i;73}7475for (RBMap<real_t, int>::Element *E = key_order.front(); E; E = E->next()) {76int i = E->get();7778if (!E->next()) {79break;80}8182int i_n = E->next()->get();8384float offset = animation->track_get_key_time(p_track, i);85float height = animation->bezier_track_get_key_value(p_track, i);86Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i);87if (p_track == moving_handle_track && (moving_handle == -1 || moving_handle == 1) && moving_handle_key == i) {88out_handle = moving_handle_right;89}9091if (selection.has(IntPair(p_track, i))) {92if (moving_selection) {93offset += moving_selection_offset.x;94height += moving_selection_offset.y;95} else if (scaling_selection) {96offset += -scaling_selection_offset.x + (offset - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);97height += -scaling_selection_offset.y + (height - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);98}99}100101float offset_n = animation->track_get_key_time(p_track, i_n);102float height_n = animation->bezier_track_get_key_value(p_track, i_n);103Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n);104if (p_track == moving_handle_track && (moving_handle == -1 || moving_handle == 1) && moving_handle_key == i_n) {105in_handle = moving_handle_left;106}107108if (selection.has(IntPair(p_track, i_n))) {109if (moving_selection) {110offset_n += moving_selection_offset.x;111height_n += moving_selection_offset.y;112} else if (scaling_selection) {113offset_n += -scaling_selection_offset.x + (offset_n - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);114height_n += -scaling_selection_offset.y + (height_n - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);115}116}117118if (moving_inserted_key && moving_selection_from_track == p_track) {119if (moving_selection_from_key == i) {120Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(p_track, i);121if (handle_mode != Animation::HANDLE_MODE_FREE) {122float offset_p = offset;123float height_p = height;124if (E->prev()) {125int i_p = E->prev()->get();126offset_p = animation->track_get_key_time(p_track, i_p);127height_p = animation->bezier_track_get_key_value(p_track, i_p);128}129130animation->bezier_track_calculate_handles(offset, offset_p, height_p, offset_n, height_n, handle_mode, Animation::HANDLE_SET_MODE_AUTO, nullptr, &out_handle);131}132} else if (moving_selection_from_key == i_n) {133Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(p_track, i_n);134if (handle_mode != Animation::HANDLE_MODE_FREE) {135float offset_nn = offset_n;136float height_nn = height_n;137if (E->next()->next()) {138int i_nn = E->next()->next()->get();139offset_nn = animation->track_get_key_time(p_track, i_nn);140height_nn = animation->bezier_track_get_key_value(p_track, i_nn);141}142143animation->bezier_track_calculate_handles(offset_n, offset, height, offset_nn, height_nn, handle_mode, Animation::HANDLE_SET_MODE_AUTO, &in_handle, nullptr);144}145}146}147148out_handle += Vector2(offset, height);149in_handle += Vector2(offset_n, height_n);150151Vector2 start(offset, height);152Vector2 end(offset_n, height_n);153154int from_x = (offset - timeline->get_value()) * scale + limit;155int point_start = from_x;156int to_x = (offset_n - timeline->get_value()) * scale + limit;157int point_end = to_x;158159if (from_x > right_limit) { // Not visible.160continue;161}162163if (to_x < limit) { // Not visible.164continue;165}166167from_x = MAX(from_x, limit);168to_x = MIN(to_x, right_limit);169170Vector<Vector2> lines;171172Vector2 prev_pos;173174for (int j = from_x; j <= to_x; j++) {175float t = (j - limit) / scale + timeline->get_value();176177float h;178179if (j == point_end) {180h = end.y; // Make sure it always connects.181} else if (j == point_start) {182h = start.y; // Make sure it always connects.183} else { // Custom interpolation, used because it needs to show paths affected by moving the selection or handles.184int iterations = 10;185float low = 0;186float high = 1;187188// Narrow high and low as much as possible.189for (int k = 0; k < iterations; k++) {190float middle = (low + high) / 2.0;191192Vector2 interp = start.bezier_interpolate(out_handle, in_handle, end, middle);193194if (interp.x < t) {195low = middle;196} else {197high = middle;198}199}200201// Interpolate the result.202Vector2 low_pos = start.bezier_interpolate(out_handle, in_handle, end, low);203Vector2 high_pos = start.bezier_interpolate(out_handle, in_handle, end, high);204205float c = (t - low_pos.x) / (high_pos.x - low_pos.x);206207h = low_pos.lerp(high_pos, c).y;208}209210h = _bezier_h_to_pixel(h);211212Vector2 pos(j, h);213214if (j > from_x) {215lines.push_back(prev_pos);216lines.push_back(pos);217}218prev_pos = pos;219}220221if (lines.size() >= 2) {222draw_multiline(lines, p_color, Math::round(EDSCALE), true);223}224}225}226227void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right) {228Vector2 from = p_from;229Vector2 to = p_to;230231if (from.x == to.x && from.y == to.y) {232return;233}234if (to.x < from.x) {235SWAP(to, from);236}237238if (to.x < p_clip_left) {239return;240}241242if (from.x > p_clip_right) {243return;244}245246if (to.x > p_clip_right) {247float c = (p_clip_right - from.x) / (to.x - from.x);248to = from.lerp(to, c);249}250251if (from.x < p_clip_left) {252float c = (p_clip_left - from.x) / (to.x - from.x);253from = from.lerp(to, c);254}255256draw_line(from, to, p_color, Math::round(EDSCALE), true);257}258259void AnimationBezierTrackEdit::_notification(int p_what) {260switch (p_what) {261case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {262if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {263panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));264panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));265}266} break;267268case NOTIFICATION_READY: {269panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));270panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));271} break;272case NOTIFICATION_THEME_CHANGED: {273bezier_icon = get_editor_theme_icon(SNAME("KeyBezierPoint"));274bezier_handle_icon = get_editor_theme_icon(SNAME("KeyBezierHandle"));275selected_icon = get_editor_theme_icon(SNAME("KeyBezierSelected"));276} break;277278case NOTIFICATION_DRAW: {279if (animation.is_null()) {280return;281}282283int limit = timeline->get_name_limit();284285const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));286const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));287const Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));288289const Color h_line_color = get_theme_color(SNAME("h_line_color"), SNAME("AnimationBezierTrackEdit"));290const Color v_line_color = get_theme_color(SNAME("v_line_color"), SNAME("AnimationBezierTrackEdit"));291const Color focus_color = get_theme_color(SNAME("focus_color"), SNAME("AnimationBezierTrackEdit"));292const Color track_focus_color = get_theme_color(SNAME("track_focus_color"), SNAME("AnimationBezierTrackEdit"));293294const int h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit"));295const int v_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit"));296297const String &lang = _get_locale();298299if (has_focus(true)) {300draw_rect(Rect2(Point2(1, 1), get_size() - Point2(1, 1)), focus_color, false, Math::round(EDSCALE));301}302303int right_limit = get_size().width;304305// Unavailable timeline.306{307int px = (editor->get_current_animation()->get_length() - timeline->get_value()) * timeline->get_zoom_scale() + timeline->get_name_limit();308px = MAX(px, timeline->get_name_limit());309Rect2 rect = Rect2(px, 0, right_limit - px, get_size().height);310if (rect.size.width > 0) {311draw_rect(rect, Color(0, 0, 0, 0.2));312}313}314315draw_line(Point2(limit, 0), Point2(limit, get_size().height), v_line_color, Math::round(EDSCALE));316317track_v_scroll_max = v_separation;318319int vofs = v_separation + track_v_scroll;320int margin = 0;321322RBMap<int, Color> subtrack_colors;323Color selected_track_color;324subtracks.clear();325node_icons.clear();326subtrack_icons.clear();327328// Marker sections.329{330float scale = timeline->get_zoom_scale();331int limit_end = get_size().width - timeline->get_buttons_width();332333PackedStringArray section = editor->get_selected_section();334if (section.size() == 2) {335StringName start_marker = section[0];336StringName end_marker = section[1];337double start_time = animation->get_marker_time(start_marker);338double end_time = animation->get_marker_time(end_marker);339340// When AnimationPlayer is playing, don't move the preview rect, so it still indicates the playback section.341AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();342if (editor->is_marker_moving_selection() && !(player && player->is_playing())) {343start_time += editor->get_marker_moving_selection_offset();344end_time += editor->get_marker_moving_selection_offset();345}346347if (start_time < animation->get_length() && end_time >= 0) {348float start_ofs = MAX(0, start_time) - timeline->get_value();349float end_ofs = MIN(animation->get_length(), end_time) - timeline->get_value();350start_ofs = start_ofs * scale + limit;351end_ofs = end_ofs * scale + limit;352start_ofs = MAX(start_ofs, limit);353end_ofs = MIN(end_ofs, limit_end);354Rect2 rect;355rect.set_position(Vector2(start_ofs, 0));356rect.set_size(Vector2(end_ofs - start_ofs, get_size().height));357358draw_rect(rect, Color(1, 0.1, 0.1, 0.2));359}360}361}362363// Marker overlays.364{365float scale = timeline->get_zoom_scale();366PackedStringArray markers = animation->get_marker_names();367for (const StringName marker : markers) {368double time = animation->get_marker_time(marker);369if (editor->is_marker_selected(marker) && editor->is_marker_moving_selection()) {370time += editor->get_marker_moving_selection_offset();371}372if (time >= 0) {373float offset = time - timeline->get_value();374offset = offset * scale + limit;375Color marker_color = animation->get_marker_color(marker);376marker_color.a = 0.2;377draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE));378}379}380}381382RBMap<String, Vector<int>> track_indices;383int track_count = animation->get_track_count();384for (int i = 0; i < track_count; ++i) {385if (!_is_track_displayed(i)) {386continue;387}388389String base_path = String(animation->track_get_path(i));390int end = base_path.find_char(':');391if (end != -1) {392base_path = base_path.substr(0, end + 1);393}394Vector<int> indices = track_indices.has(base_path) ? track_indices[base_path] : Vector<int>();395indices.push_back(i);396track_indices[base_path] = indices;397}398399const Color dc = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor));400401Ref<Texture2D> remove = get_editor_theme_icon(SNAME("Remove"));402Ref<Texture2D> visibility_visible = get_editor_theme_icon(SNAME("GuiVisibilityVisible"));403Ref<Texture2D> visibility_hidden = get_editor_theme_icon(SNAME("GuiVisibilityHidden"));404Ref<Texture2D> lock = get_editor_theme_icon(SNAME("Lock"));405Ref<Texture2D> unlock = get_editor_theme_icon(SNAME("Unlock"));406Ref<Texture2D> solo = get_editor_theme_icon(SNAME("AudioBusSolo"));407408for (const KeyValue<String, Vector<int>> &E : track_indices) {409String base_path = E.key;410411Vector<int> tracks = E.value;412413// Names and icon.414{415NodePath path = animation->track_get_path(tracks[0]);416417Node *node = nullptr;418419if (root && root->has_node(path)) {420node = root->get_node(path);421}422423if (node) {424int ofs = h_separation;425426Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node);427428TextLine text_buf = TextLine(node->get_name(), font, font_size);429int total_icon_width = icon->get_width() + solo->get_width() + visibility_visible->get_width() + lock->get_width() + remove->get_width();430int total_icon_separation = h_separation * 5;431text_buf.set_width(limit - ofs - total_icon_width - total_icon_separation);432433int h = MAX(text_buf.get_size().y, icon->get_height());434435draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2.0));436ofs += icon->get_width() + h_separation;437438margin = icon->get_width();439440Vector2 string_pos = Point2(ofs, vofs);441string_pos = string_pos.floor();442text_buf.draw(get_canvas_item(), string_pos, color);443444Rect2 remove_rect(limit - h_separation - remove->get_width(), vofs, remove->get_width(), remove->get_height());445if (read_only) {446draw_texture(remove, remove_rect.position, dc);447} else {448draw_texture(remove, remove_rect.position);449}450451bool all_tracks_locked = true;452for (int track : tracks) {453if (!locked_tracks.has(track)) {454all_tracks_locked = false;455break;456}457}458459Rect2 lock_rect(remove_rect.position.x - h_separation - lock->get_width(), vofs, lock->get_width(), lock->get_height());460461if (all_tracks_locked) {462draw_texture(lock, lock_rect.position);463} else {464draw_texture(unlock, lock_rect.position);465}466467bool all_tracks_hidden = true;468for (int track : tracks) {469if (!hidden_tracks.has(track)) {470all_tracks_hidden = false;471break;472}473}474475Rect2 visibility_rect(lock_rect.position.x - h_separation - visibility_visible->get_width(), vofs, visibility_visible->get_width(), visibility_visible->get_height());476477if (all_tracks_hidden) {478draw_texture(visibility_hidden, visibility_rect.position);479} else {480draw_texture(visibility_visible, visibility_rect.position);481}482483Rect2 solo_rect(visibility_rect.position.x - h_separation - solo->get_width(), vofs, solo->get_width(), solo->get_height());484draw_texture(solo, solo_rect.position);485486RBMap<int, Rect2> icon_rects;487icon_rects[REMOVE_ICON] = remove_rect;488icon_rects[LOCK_ICON] = lock_rect;489icon_rects[VISIBILITY_ICON] = visibility_rect;490icon_rects[SOLO_ICON] = solo_rect;491492node_icons[base_path.trim_suffix(":")] = icon_rects;493494vofs += h + v_separation;495track_v_scroll_max += h + v_separation;496}497}498499float remove_hpos = limit - h_separation - remove->get_width();500float lock_hpos = remove_hpos - h_separation - lock->get_width();501float visibility_hpos = lock_hpos - h_separation - visibility_visible->get_width();502float solo_hpos = visibility_hpos - h_separation - solo->get_width();503504float buttons_width = remove->get_width() + lock->get_width() + visibility_visible->get_width() + solo->get_width() + h_separation * 3;505506for (int i = 0; i < tracks.size(); ++i) {507// Related track titles.508509int current_track = tracks[i];510511String path = String(animation->track_get_path(current_track));512path = path.replace_first(base_path, "");513514Color cc = color;515TextLine text_buf = TextLine(path, font, font_size);516text_buf.set_width(limit - margin - buttons_width - h_separation * 2);517518Rect2 rect = Rect2(margin, vofs, solo_hpos - h_separation - solo->get_width(), text_buf.get_size().y + v_separation);519520cc.a *= 0.7;521float h;522if (path.ends_with(":x")) {523h = 0;524} else if (path.ends_with(":y")) {525h = 0.33f;526} else if (path.ends_with(":z")) {527h = 0.66f;528} else {529uint32_t hash = path.hash();530hash = ((hash >> 16) ^ hash) * 0x45d9f3b;531hash = ((hash >> 16) ^ hash) * 0x45d9f3b;532hash = (hash >> 16) ^ hash;533h = (hash % 65535) / 65536.0;534}535536if (current_track != selected_track) {537Color track_color;538if (locked_tracks.has(current_track)) {539track_color.set_hsv(h, 0, 0.4);540} else {541track_color.set_hsv(h, 0.2, 0.8);542}543track_color.a = 0.5;544draw_rect(Rect2(0, vofs, margin - h_separation, text_buf.get_size().y * 0.8), track_color);545subtrack_colors[current_track] = track_color;546547subtracks[current_track] = rect;548} else {549draw_rect(rect, track_focus_color);550if (locked_tracks.has(selected_track)) {551selected_track_color.set_hsv(h, 0.0, 0.4);552} else {553selected_track_color.set_hsv(h, 0.8, 0.8);554}555}556557Vector2 string_pos = Point2(margin + h_separation, vofs);558text_buf.draw(get_canvas_item(), string_pos, cc);559560Rect2 remove_rect = Rect2(remove_hpos, vofs, remove->get_width(), remove->get_height());561static const Color texture_modulate = Color(1, 1, 1, .75);562563if (read_only) {564draw_texture(remove, remove_rect.position, dc);565} else {566draw_texture(remove, remove_rect.position, texture_modulate);567}568569Rect2 lock_rect = Rect2(lock_hpos, vofs, lock->get_width(), lock->get_height());570if (locked_tracks.has(current_track)) {571draw_texture(lock, lock_rect.position, texture_modulate);572} else {573draw_texture(unlock, lock_rect.position, texture_modulate);574}575576Rect2 visible_rect = Rect2(visibility_hpos, vofs, visibility_visible->get_width(), visibility_visible->get_height());577if (hidden_tracks.has(current_track)) {578draw_texture(visibility_hidden, visible_rect.position, texture_modulate);579} else {580draw_texture(visibility_visible, visible_rect.position, texture_modulate);581}582583Rect2 solo_rect = Rect2(solo_hpos, vofs, solo->get_width(), solo->get_height());584draw_texture(solo, solo_rect.position, texture_modulate);585586RBMap<int, Rect2> track_icons;587track_icons[REMOVE_ICON] = remove_rect;588track_icons[LOCK_ICON] = lock_rect;589track_icons[VISIBILITY_ICON] = visible_rect;590track_icons[SOLO_ICON] = solo_rect;591592subtrack_icons[current_track] = track_icons;593594vofs += text_buf.get_size().y + v_separation;595track_v_scroll_max += text_buf.get_size().y + v_separation;596}597}598599const Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));600601// Guides.602{603float min_left_scale = font->get_height(font_size) + v_separation;604605float scale = (min_left_scale * 2) * timeline_v_zoom;606float step = Math::pow(10.0, Math::round(Math::log(scale / 5.0) / Math::log(10.0))) * 5.0;607scale = Math::snapped(scale, step);608609while (scale / timeline_v_zoom < min_left_scale * 2) {610scale += step;611}612613bool first = true;614int prev_iv = 0;615for (int i = font->get_height(font_size); i < get_size().height; i++) {616float ofs = get_size().height / 2.0 - i;617ofs *= timeline_v_zoom;618ofs += timeline_v_scroll;619620int iv = int(ofs / scale);621if (ofs < 0) {622iv -= 1;623}624if (!first && iv != prev_iv) {625Color lc = h_line_color;626lc.a *= 0.5;627draw_line(Point2(limit, i), Point2(right_limit, i), lc, Math::round(EDSCALE));628Color c = color;629c.a *= 0.5;630631const String &formatted = TranslationServer::get_singleton()->format_number(rtos(Math::snapped((iv + 1) * scale, step)), lang);632draw_string(font, Point2(limit + 8, i - 2), formatted, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, c);633}634635first = false;636prev_iv = iv;637}638}639640// Draw other curves.641{642float scale = timeline->get_zoom_scale();643Ref<Texture2D> point = get_editor_theme_icon(SNAME("KeyValue"));644for (const KeyValue<int, Color> &E : subtrack_colors) {645if (hidden_tracks.has(E.key)) {646continue;647}648_draw_track(E.key, E.value);649650for (int i = 0; i < animation->track_get_key_count(E.key); i++) {651float offset = animation->track_get_key_time(E.key, i);652float value = animation->bezier_track_get_key_value(E.key, i);653654Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));655656if (pos.x >= limit && pos.x <= right_limit) {657draw_texture(point, pos - point->get_size() / 2.0, E.value);658}659}660}661662if (selected_track >= 0 && track_count > 0 && !hidden_tracks.has(selected_track)) {663// Draw edited curve.664_draw_track(selected_track, selected_track_color);665}666}667668const bool draw_selection_handles = selection.size() > 1;669LocalVector<Point2> selected_pos;670671// Draw editor handles.672{673edit_points.clear();674float scale = timeline->get_zoom_scale();675676for (int i = 0; i < track_count; ++i) {677bool draw_track = _is_track_curves_displayed(i) && !locked_tracks.has(i);678if (!draw_selection_handles && !draw_track) {679continue;680}681682int key_count = animation->track_get_key_count(i);683for (int j = 0; j < key_count; ++j) {684float offset = animation->track_get_key_time(i, j);685float value = animation->bezier_track_get_key_value(i, j);686bool is_selected = selection.has(IntPair(i, j));687688if (is_selected) {689if (moving_selection) {690offset += moving_selection_offset.x;691value += moving_selection_offset.y;692} else if (scaling_selection) {693offset += -scaling_selection_offset.x + (offset - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);694value += -scaling_selection_offset.y + (value - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);695}696}697698Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));699700if (draw_selection_handles && is_selected) {701selected_pos.push_back(pos);702}703704if (!draw_track) {705continue;706}707708Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);709Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);710711if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {712in_vec = moving_handle_left;713}714715if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {716out_vec = moving_handle_right;717}718719if (moving_inserted_key && moving_selection_from_key == j) {720Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(i, j);721if (handle_mode != Animation::HANDLE_MODE_FREE) {722int key_prev = 0;723int key_next = moving_selection_from_key;724for (int k = 0; k < key_count; k++) {725if (k == moving_selection_from_key) {726continue;727}728729if (animation->track_get_key_time(i, k) < offset) {730key_prev = k;731} else {732key_next = k;733break;734}735}736737float prev_time = offset;738float prev_value = value;739if (key_prev != moving_selection_from_key) {740prev_time = animation->track_get_key_time(i, key_prev);741prev_value = animation->bezier_track_get_key_value(i, key_prev);742}743744float next_time = offset;745float next_value = value;746if (key_next != moving_selection_from_key) {747next_time = animation->track_get_key_time(i, key_next);748next_value = animation->bezier_track_get_key_value(i, key_next);749}750751animation->bezier_track_calculate_handles(offset, prev_time, prev_value, next_time, next_value, handle_mode, Animation::HANDLE_SET_MODE_AUTO, &in_vec, &out_vec);752}753}754755Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y));756Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y));757758if (i == selected_track || is_selected) {759_draw_line_clipped(pos, pos_in, accent, limit, right_limit);760_draw_line_clipped(pos, pos_out, accent, limit, right_limit);761}762763EditPoint ep;764ep.track = i;765ep.key = j;766if (pos.x >= limit && pos.x <= right_limit) {767ep.point_rect.position = (pos - bezier_icon->get_size() / 2.0).floor();768ep.point_rect.size = bezier_icon->get_size();769if (is_selected) {770draw_texture(selected_icon, ep.point_rect.position);771772const String &formatted_offset = TranslationServer::get_singleton()->format_number(rtos(Math::snapped(offset, 0.0001)), lang);773draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + formatted_offset, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);774775const String &formatted_value = TranslationServer::get_singleton()->format_number(rtos(Math::snapped(value, 0.001)), lang);776draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + formatted_value, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);777} else {778Color track_color = Color(1, 1, 1, 1);779if (i != selected_track) {780track_color = subtrack_colors[i];781}782draw_texture(bezier_icon, ep.point_rect.position, track_color);783}784ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5);785}786ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5);787788if (i == selected_track || is_selected) {789if (animation->bezier_track_get_key_handle_mode(i, j) != Animation::HANDLE_MODE_LINEAR) {790if (pos_in.x >= limit && pos_in.x <= right_limit) {791ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2.0).floor();792ep.in_rect.size = bezier_handle_icon->get_size();793draw_texture(bezier_handle_icon, ep.in_rect.position);794ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5);795}796if (pos_out.x >= limit && pos_out.x <= right_limit) {797ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2.0).floor();798ep.out_rect.size = bezier_handle_icon->get_size();799draw_texture(bezier_handle_icon, ep.out_rect.position);800ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5);801}802}803}804if (!locked_tracks.has(i)) {805edit_points.push_back(ep);806}807}808}809810for (int i = 0; i < edit_points.size(); ++i) {811if (edit_points[i].track == selected_track) {812EditPoint ep = edit_points[i];813edit_points.remove_at(i);814edit_points.insert(0, ep);815}816}817}818819selection_rect = Rect2();820selection_handles_rect = Rect2();821// Draw scale handles.822if (draw_selection_handles) {823selection_rect.position = selected_pos[0];824selected_pos.remove_at(0);825for (const Point2 &pos : selected_pos) {826selection_rect = selection_rect.expand(pos);827}828829const int outer_ofs = Math::round(12 * EDSCALE);830const int inner_ofs = Math::round(outer_ofs / 2.0);831832// Draw horizontal handles.833if (selection_rect.size.height > CMP_EPSILON) {834_draw_line_clipped(selection_rect.position - Vector2(inner_ofs, inner_ofs), selection_rect.position + Vector2(selection_rect.size.width + inner_ofs, -inner_ofs), accent, limit, right_limit);835_draw_line_clipped(selection_rect.position + Vector2(-inner_ofs, selection_rect.size.height + inner_ofs), selection_rect.position + selection_rect.size + Vector2(inner_ofs, inner_ofs), accent, limit, right_limit);836}837// Draw vertical handles.838if (selection_rect.size.width > CMP_EPSILON) {839_draw_line_clipped(selection_rect.position - Vector2(inner_ofs, inner_ofs), selection_rect.position + Vector2(-inner_ofs, selection_rect.size.height + inner_ofs), accent, limit, right_limit);840_draw_line_clipped(selection_rect.position + Vector2(selection_rect.size.width + inner_ofs, -inner_ofs), selection_rect.position + selection_rect.size + Vector2(inner_ofs, inner_ofs), accent, limit, right_limit);841}842843selection_handles_rect.position = selection_rect.position - Vector2(outer_ofs, outer_ofs);844selection_handles_rect.size = selection_rect.size + Vector2(outer_ofs, outer_ofs) * 2;845}846847if (box_selecting) {848Vector2 bs_from = box_selection_from;849Vector2 bs_to = box_selection_to;850if (bs_from.x > bs_to.x) {851SWAP(bs_from.x, bs_to.x);852}853if (bs_from.y > bs_to.y) {854SWAP(bs_from.y, bs_to.y);855}856draw_rect(857Rect2(bs_from, bs_to - bs_from),858get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));859draw_rect(860Rect2(bs_from, bs_to - bs_from),861get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),862false,863Math::round(EDSCALE));864}865} break;866}867}868869// Check if a track is displayed in the bezier editor (track type = bezier and track not filtered).870bool AnimationBezierTrackEdit::_is_track_displayed(int p_track_index) {871if (animation->track_get_type(p_track_index) != Animation::TrackType::TYPE_BEZIER) {872return false;873}874875if (is_filtered) {876String path = String(animation->track_get_path(p_track_index));877if (root && root->has_node(path)) {878Node *node = root->get_node(path);879if (!node) {880return false; // No node, no filter.881}882if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {883return false; // Skip track due to not selected.884}885}886}887888return true;889}890891// Check if the curves for a track are displayed in the editor (not hidden). Includes the check on the track visibility.892bool AnimationBezierTrackEdit::_is_track_curves_displayed(int p_track_index) {893// Is the track is visible in the editor?894if (!_is_track_displayed(p_track_index)) {895return false;896}897898// And curves visible?899if (hidden_tracks.has(p_track_index)) {900return false;901}902903return true;904}905906Ref<Animation> AnimationBezierTrackEdit::get_animation() const {907return animation;908}909910void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only) {911animation = p_animation;912read_only = p_read_only;913selected_track = p_track;914queue_redraw();915}916917Size2 AnimationBezierTrackEdit::get_minimum_size() const {918return Vector2(1, 1);919}920921Control::CursorShape AnimationBezierTrackEdit::get_cursor_shape(const Point2 &p_pos) const {922// Box selecting or moving a handle923if (box_selecting || Math::abs(moving_handle) == 1) {924return get_default_cursor_shape();925}926// Hovering a handle927if (!read_only) {928for (const EditPoint &edit_point : edit_points) {929if (edit_point.in_rect.has_point(p_pos) || edit_point.out_rect.has_point(p_pos)) {930return get_default_cursor_shape();931}932}933}934// Currently box scaling935if (scaling_selection) {936if (scaling_selection_handles == Vector2i(1, 1) || scaling_selection_handles == Vector2i(-1, -1)) {937return CURSOR_FDIAGSIZE;938} else if (scaling_selection_handles == Vector2i(1, -1) || scaling_selection_handles == Vector2i(-1, 1)) {939return CURSOR_BDIAGSIZE;940} else if (abs(scaling_selection_handles.x) == 1) {941return CURSOR_HSIZE;942} else if (abs(scaling_selection_handles.y) == 1) {943return CURSOR_VSIZE;944}945}946// Hovering the scaling box947const Vector2i rel_pos = p_pos - selection_rect.position;948if (selection_handles_rect.has_point(p_pos)) {949if ((rel_pos.x < 0 && rel_pos.y < 0) || (rel_pos.x > selection_rect.size.width && rel_pos.y > selection_rect.size.height)) {950return CURSOR_FDIAGSIZE;951} else if ((rel_pos.x < 0 && rel_pos.y > selection_rect.size.height) || (rel_pos.x > selection_rect.size.width && rel_pos.y < 0)) {952return CURSOR_BDIAGSIZE;953} else if (rel_pos.x < 0 || rel_pos.x > selection_rect.size.width) {954return CURSOR_HSIZE;955} else if (rel_pos.y < 0 || rel_pos.y > selection_rect.size.height) {956return CURSOR_VSIZE;957}958return CURSOR_MOVE;959}960return get_default_cursor_shape();961}962963void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {964timeline = p_timeline;965timeline->connect("zoom_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed));966timeline->connect("name_limit_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed));967}968969void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) {970editor = p_editor;971connect("clear_selection", callable_mp(editor, &AnimationTrackEditor::_clear_selection).bind(false));972connect("select_key", callable_mp(editor, &AnimationTrackEditor::_key_selected), CONNECT_DEFERRED);973connect("deselect_key", callable_mp(editor, &AnimationTrackEditor::_key_deselected), CONNECT_DEFERRED);974}975976void AnimationBezierTrackEdit::_play_position_draw() {977if (animation.is_null() || play_position_pos < 0) {978return;979}980981float scale = timeline->get_zoom_scale();982int h = get_size().height;983984int limit = timeline->get_name_limit();985986int px = (-timeline->get_value() + play_position_pos) * scale + limit;987988if (px >= limit && px < (get_size().width)) {989const Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));990play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));991}992}993994void AnimationBezierTrackEdit::set_play_position(real_t p_pos) {995play_position_pos = p_pos;996play_position->queue_redraw();997}998999void AnimationBezierTrackEdit::update_play_position() {1000play_position->queue_redraw();1001}10021003void AnimationBezierTrackEdit::set_root(Node *p_root) {1004root = p_root;1005}10061007void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {1008is_filtered = p_filtered;1009if (animation.is_null()) {1010return;1011}1012String base_path = String(animation->track_get_path(selected_track));1013if (is_filtered) {1014if (root && root->has_node(base_path)) {1015Node *node = root->get_node(base_path);1016if (!node || !EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {1017for (int i = 0; i < animation->get_track_count(); ++i) {1018if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) {1019continue;1020}10211022base_path = String(animation->track_get_path(i));1023if (root && root->has_node(base_path)) {1024node = root->get_node(base_path);1025if (!node) {1026continue; // No node, no filter.1027}1028if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {1029continue; // Skip track due to not selected.1030}10311032set_animation_and_track(animation, i, read_only);1033break;1034}1035}1036}1037}1038}1039queue_redraw();1040}10411042void AnimationBezierTrackEdit::auto_fit_vertically() {1043int track_count = animation->get_track_count();1044real_t minimum_value = Math::INF;1045real_t maximum_value = -Math::INF;10461047int nb_track_visible = 0;1048for (int i = 0; i < track_count; ++i) {1049if (!_is_track_curves_displayed(i) || locked_tracks.has(i)) {1050continue;1051}10521053int key_count = animation->track_get_key_count(i);10541055for (int j = 0; j < key_count; ++j) {1056real_t value = animation->bezier_track_get_key_value(i, j);10571058minimum_value = MIN(value, minimum_value);1059maximum_value = MAX(value, maximum_value);10601061// We also want to includes the handles...1062Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);1063Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);10641065minimum_value = MIN(value + in_vec.y, minimum_value);1066maximum_value = MAX(value + in_vec.y, maximum_value);1067minimum_value = MIN(value + out_vec.y, minimum_value);1068maximum_value = MAX(value + out_vec.y, maximum_value);1069}10701071nb_track_visible++;1072}10731074if (nb_track_visible == 0) {1075// No visible track... we will not adjust the vertical zoom1076return;1077}10781079if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {1080_zoom_vertically(minimum_value, maximum_value);1081queue_redraw();1082}1083}10841085void AnimationBezierTrackEdit::_zoom_vertically(real_t p_minimum_value, real_t p_maximum_value) {1086real_t target_height = p_maximum_value - p_minimum_value;1087if (target_height <= CMP_EPSILON) {1088timeline_v_scroll = p_maximum_value;1089return;1090}10911092timeline_v_scroll = (p_maximum_value + p_minimum_value) / 2.0;1093timeline_v_zoom = target_height / ((get_size().height - timeline->get_size().height) * 0.9);1094}10951096void AnimationBezierTrackEdit::_zoom_changed() {1097queue_redraw();1098play_position->queue_redraw();1099}11001101void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) {1102_unlock_track(p_track);11031104Vector<int> updated_locked_tracks;1105for (const int &E : locked_tracks) {1106updated_locked_tracks.push_back(E);1107}1108locked_tracks.clear();1109for (int i = 0; i < updated_locked_tracks.size(); ++i) {1110if (updated_locked_tracks[i] > p_track) {1111locked_tracks.insert(updated_locked_tracks[i] - 1);1112} else {1113locked_tracks.insert(updated_locked_tracks[i]);1114}1115}1116}11171118void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) {1119_show_track(p_track);11201121Vector<int> updated_hidden_tracks;1122for (const int &E : hidden_tracks) {1123updated_hidden_tracks.push_back(E);1124}1125hidden_tracks.clear();1126for (int i = 0; i < updated_hidden_tracks.size(); ++i) {1127if (updated_hidden_tracks[i] > p_track) {1128hidden_tracks.insert(updated_hidden_tracks[i] - 1);1129} else {1130hidden_tracks.insert(updated_hidden_tracks[i]);1131}1132}1133}11341135bool AnimationBezierTrackEdit::_lock_track(int p_track) {1136locked_tracks.insert(p_track);1137if (selected_track == p_track) {1138for (int i = 0; i < animation->get_track_count(); ++i) {1139if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {1140set_animation_and_track(animation, i, read_only);1141return true;1142}1143}1144}11451146return false;1147}11481149bool AnimationBezierTrackEdit::_unlock_track(int p_track) {1150return locked_tracks.erase(p_track);1151}11521153bool AnimationBezierTrackEdit::_hide_track(int p_track) {1154hidden_tracks.insert(p_track);1155if (selected_track == p_track) {1156for (int i = 0; i < animation->get_track_count(); ++i) {1157if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {1158set_animation_and_track(animation, i, read_only);1159return true;1160}1161}1162}11631164return false;1165}11661167bool AnimationBezierTrackEdit::_show_track(int p_track) {1168return hidden_tracks.erase(p_track);1169}11701171String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const {1172return Control::get_tooltip(p_pos);1173}11741175void AnimationBezierTrackEdit::_clear_selection() {1176selection.clear();1177emit_signal(SNAME("clear_selection"));1178queue_redraw();1179}11801181void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto) {1182EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1183undo_redo->create_action(TTR("Update Selected Key Handles"), UndoRedo::MERGE_DISABLE, animation.ptr());1184for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1185const IntPair track_key_pair = E->get();1186undo_redo->add_undo_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_handle_mode(track_key_pair.first, track_key_pair.second), Animation::HANDLE_SET_MODE_NONE);1187undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_in_handle(track_key_pair.first, track_key_pair.second));1188undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_out_handle(track_key_pair.first, track_key_pair.second));1189undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track_key_pair.first, track_key_pair.second, p_mode, p_auto ? Animation::HANDLE_SET_MODE_AUTO : Animation::HANDLE_SET_MODE_RESET);1190}1191AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();1192if (ape) {1193undo_redo->add_do_method(ape, "_animation_update_key_frame");1194undo_redo->add_undo_method(ape, "_animation_update_key_frame");1195}1196undo_redo->commit_action();1197}11981199void AnimationBezierTrackEdit::_clear_selection_for_anim(const Ref<Animation> &p_anim) {1200if (!(animation == p_anim) || !is_visible()) {1201return;1202}1203_clear_selection();1204}12051206void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos, bool p_single) {1207if (!(animation == p_anim) || !is_visible()) {1208return;1209}12101211int idx = animation->track_find_key(p_track, p_pos, Animation::FIND_MODE_APPROX);1212ERR_FAIL_COND(idx < 0);12131214selection.insert(IntPair(p_track, idx));1215emit_signal(SNAME("select_key"), idx, p_single, p_track);1216queue_redraw();1217}12181219void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {1220ERR_FAIL_COND(p_event.is_null());12211222if (panner->gui_input(p_event)) {1223accept_event();1224return;1225}12261227if (p_event->is_pressed()) {1228if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {1229if (!read_only) {1230duplicate_selected_keys(-1.0, false);1231}1232accept_event();1233}1234if (ED_IS_SHORTCUT("animation_editor/cut_selected_keys", p_event)) {1235if (!read_only) {1236copy_selected_keys(true);1237}1238accept_event();1239}1240if (ED_IS_SHORTCUT("animation_editor/copy_selected_keys", p_event)) {1241if (!read_only) {1242copy_selected_keys(false);1243}1244accept_event();1245}1246if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {1247if (!read_only) {1248paste_keys(-1.0, false);1249}1250accept_event();1251}1252if (ED_IS_SHORTCUT("animation_editor/delete_selection", p_event)) {1253if (!read_only) {1254delete_selection();1255}1256accept_event();1257}1258}12591260Ref<InputEventKey> key_press = p_event;12611262if (key_press.is_valid() && key_press->is_pressed()) {1263if (ED_IS_SHORTCUT("animation_bezier_editor/focus", p_event)) {1264SelectionSet focused_keys;1265if (selection.is_empty()) {1266for (int i = 0; i < edit_points.size(); ++i) {1267IntPair key_pair = IntPair(edit_points[i].track, edit_points[i].key);1268focused_keys.insert(key_pair);1269}1270} else {1271for (const IntPair &E : selection) {1272focused_keys.insert(E);1273if (E.second > 0) {1274IntPair previous_key = IntPair(E.first, E.second - 1);1275focused_keys.insert(previous_key);1276}1277if (E.second < animation->track_get_key_count(E.first) - 1) {1278IntPair next_key = IntPair(E.first, E.second + 1);1279focused_keys.insert(next_key);1280}1281}1282}1283if (focused_keys.is_empty()) {1284accept_event();1285return;1286}12871288real_t minimum_time = Math::INF;1289real_t maximum_time = -Math::INF;1290real_t minimum_value = Math::INF;1291real_t maximum_value = -Math::INF;12921293for (const IntPair &E : focused_keys) {1294IntPair key_pair = E;12951296real_t time = animation->track_get_key_time(key_pair.first, key_pair.second);1297real_t value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second);12981299minimum_time = MIN(time, minimum_time);1300maximum_time = MAX(time, maximum_time);1301minimum_value = MIN(value, minimum_value);1302maximum_value = MAX(value, maximum_value);1303}13041305float width = get_size().width - timeline->get_name_limit() - timeline->get_buttons_width();1306float padding = width * 0.1;1307float desired_scale = (width - padding / 2.0) / (maximum_time - minimum_time);1308minimum_time = MAX(0, minimum_time - (padding / 2.0) / desired_scale);13091310float zv = Math::pow(100 / desired_scale, 0.125f);1311if (zv < 1) {1312zv = Math::pow(desired_scale / 100, 0.125f) - 1;1313zv = 1 - zv;1314}1315float zoom_value = timeline->get_zoom()->get_max() - zv;13161317if (Math::is_finite(minimum_time) && Math::is_finite(maximum_time) && maximum_time - minimum_time > CMP_EPSILON) {1318timeline->get_zoom()->set_value(zoom_value);1319callable_mp((Range *)timeline, &Range::set_value).call_deferred(minimum_time);1320}13211322if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {1323_zoom_vertically(minimum_value, maximum_value);1324}13251326queue_redraw();1327accept_event();1328return;1329} else if (ED_IS_SHORTCUT("animation_bezier_editor/select_all_keys", p_event)) {1330for (int i = 0; i < edit_points.size(); ++i) {1331_select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), i == 0);1332}13331334queue_redraw();1335accept_event();1336return;1337} else if (ED_IS_SHORTCUT("animation_bezier_editor/deselect_all_keys", p_event)) {1338selection.clear();1339emit_signal(SNAME("clear_selection"));13401341queue_redraw();1342accept_event();1343return;1344}1345}13461347Ref<InputEventMouseButton> mb = p_event;1348int limit = timeline->get_name_limit();1349if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {1350menu_insert_key = mb->get_position();1351if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) {1352if (!read_only) {1353Vector2 popup_pos = get_screen_position() + mb->get_position();13541355bool selected = _try_select_at_ui_pos(mb->get_position(), mb->is_shift_pressed(), false);13561357menu->clear();1358menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);1359if (selected || selection.size()) {1360menu->add_separator();1361menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE);1362menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCut")), TTR("Cut Selected Key(s)"), MENU_KEY_CUT);1363menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCopy")), TTR("Copy Selected Key(s)"), MENU_KEY_COPY);1364}13651366if (editor->is_key_clipboard_active()) {1367menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTR("Paste Key(s)"), MENU_KEY_PASTE);1368}13691370if (selected || selection.size()) {1371menu->add_separator();1372menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);1373menu->add_separator();1374menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesFree")), TTR("Make Handles Free"), MENU_KEY_SET_HANDLE_FREE);1375menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesLinear")), TTR("Make Handles Linear"), MENU_KEY_SET_HANDLE_LINEAR);1376menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesBalanced")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED);1377menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesMirror")), TTR("Make Handles Mirrored"), MENU_KEY_SET_HANDLE_MIRRORED);1378menu->add_separator();1379menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesBalanced")), TTR("Make Handles Balanced (Auto Tangent)"), MENU_KEY_SET_HANDLE_AUTO_BALANCED);1380menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesMirror")), TTR("Make Handles Mirrored (Auto Tangent)"), MENU_KEY_SET_HANDLE_AUTO_MIRRORED);1381}13821383if (menu->get_item_count()) {1384menu->reset_size();1385menu->set_position(popup_pos);1386menu->popup();1387}1388}1389}1390}13911392if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1393Point2 pos = mb->get_position();1394bool no_mod_key_pressed = !mb->is_alt_pressed() && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed();1395if (mb->is_double_click() && !moving_selection && no_mod_key_pressed) {1396int x = pos.x - timeline->get_name_limit();1397float ofs = x / timeline->get_zoom_scale() + timeline->get_value();1398emit_signal(SNAME("timeline_changed"), ofs, false);1399}1400for (const KeyValue<int, Rect2> &E : subtracks) {1401if (E.value.has_point(mb->get_position())) {1402if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) {1403set_animation_and_track(animation, E.key, read_only);1404_clear_selection();1405}1406return;1407}1408}14091410for (const KeyValue<int, RBMap<int, Rect2>> &E : subtrack_icons) {1411int track = E.key;1412for (const KeyValue<int, Rect2> &I : E.value) {1413if (I.value.has_point(mb->get_position())) {1414if (I.key == REMOVE_ICON) {1415if (!read_only) {1416EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1417undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr());14181419undo_redo->add_do_method(this, "_update_locked_tracks_after", track);1420undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);14211422undo_redo->add_do_method(animation.ptr(), "remove_track", track);14231424undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track);1425undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));14261427if (locked_tracks.has(track)) {1428undo_redo->add_undo_method(this, "_lock_track", track);1429}14301431if (hidden_tracks.has(track)) {1432undo_redo->add_undo_method(this, "_hide_track", track);1433}14341435for (int i = 0; i < animation->track_get_key_count(track); ++i) {1436undo_redo->add_undo_method(1437this,1438"_bezier_track_insert_key_at_anim",1439animation,1440track,1441animation->track_get_key_time(track, i),1442animation->bezier_track_get_key_value(track, i),1443animation->bezier_track_get_key_in_handle(track, i),1444animation->bezier_track_get_key_out_handle(track, i),1445animation->bezier_track_get_key_handle_mode(track, i));1446}14471448undo_redo->commit_action();14491450for (selected_track = MIN(selected_track, animation->get_track_count() - 1); selected_track >= 0; --selected_track) {1451if (animation->track_get_type(selected_track) == Animation::TYPE_BEZIER) {1452break;1453}1454}1455}1456return;1457} else if (I.key == LOCK_ICON) {1458if (!_unlock_track(track)) {1459_lock_track(track);1460}1461queue_redraw();1462return;1463} else if (I.key == VISIBILITY_ICON) {1464if (!_show_track(track)) {1465_hide_track(track);1466}1467queue_redraw();1468return;1469} else if (I.key == SOLO_ICON) {1470bool show_other_tracks = true;1471for (int i = 0; i < animation->get_track_count(); ++i) {1472if (i != track && animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) {1473show_other_tracks = false;1474break;1475}1476}14771478if (_show_track(track)) {1479show_other_tracks = false;1480}14811482for (int i = 0; i < animation->get_track_count(); ++i) {1483if (i != track) {1484if (show_other_tracks) {1485_show_track(i);1486} else {1487_hide_track(i);1488}1489}1490}1491queue_redraw();1492return;1493}1494return;1495}1496}1497}14981499for (const KeyValue<String, RBMap<int, Rect2>> &E : node_icons) {1500for (const KeyValue<int, Rect2> &I : E.value) {1501if (I.value.has_point(mb->get_position())) {1502if (I.key == REMOVE_ICON) {1503if (!read_only) {1504EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1505undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr(), true);15061507for (int i = animation->get_track_count() - 1; i >= 0; --i) {1508if (animation->track_get_path(i).get_concatenated_names() != E.key) {1509continue;1510}15111512if (animation->track_get_type(i) != Animation::TYPE_BEZIER) {1513continue;1514}15151516undo_redo->add_do_method(this, "_update_locked_tracks_after", i);1517undo_redo->add_do_method(this, "_update_hidden_tracks_after", i);1518undo_redo->add_do_method(animation.ptr(), "remove_track", i);15191520for (int j = animation->track_get_key_count(i) - 1; j >= 0; --j) {1521undo_redo->add_undo_method(1522this,1523"_bezier_track_insert_key_at_anim",1524animation,1525i,1526animation->track_get_key_time(i, j),1527animation->bezier_track_get_key_value(i, j),1528animation->bezier_track_get_key_in_handle(i, j),1529animation->bezier_track_get_key_out_handle(i, j),1530animation->bezier_track_get_key_handle_mode(i, j));1531}15321533if (hidden_tracks.has(i)) {1534undo_redo->add_undo_method(this, "_hide_track", i);1535}15361537if (locked_tracks.has(i)) {1538undo_redo->add_undo_method(this, "_lock_track", i);1539}15401541undo_redo->add_undo_method(animation.ptr(), "track_set_path", i, animation->track_get_path(i));1542undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, i);1543}15441545undo_redo->commit_action();15461547for (selected_track = MIN(selected_track, animation->get_track_count() - 1); selected_track >= 0; --selected_track) {1548if (animation->track_get_type(selected_track) == Animation::TYPE_BEZIER) {1549break;1550}1551}1552}1553return;1554} else if (I.key == LOCK_ICON) {1555bool unlock_tracks = true;15561557for (int i = 0; i < animation->get_track_count(); ++i) {1558if (animation->track_get_path(i).get_concatenated_names() == E.key) {1559if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !locked_tracks.has(i)) {1560unlock_tracks = false;1561break;1562}1563}1564}15651566for (int i = 0; i < animation->get_track_count(); ++i) {1567if (animation->track_get_path(i).get_concatenated_names() == E.key) {1568if (unlock_tracks) {1569_unlock_track(i);1570} else {1571_lock_track(i);1572}1573}1574}15751576queue_redraw();1577return;1578} else if (I.key == VISIBILITY_ICON) {1579bool show_tracks = true;15801581for (int i = 0; i < animation->get_track_count(); ++i) {1582if (animation->track_get_path(i).get_concatenated_names() == E.key) {1583if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) {1584show_tracks = false;1585break;1586}1587}1588}15891590for (int i = 0; i < animation->get_track_count(); ++i) {1591if (animation->track_get_path(i).get_concatenated_names() == E.key) {1592if (show_tracks) {1593_show_track(i);1594} else {1595_hide_track(i);1596}1597}1598}15991600queue_redraw();1601return;1602} else if (I.key == SOLO_ICON) {1603bool show_other_tracks = true;16041605for (int i = 0; i < animation->get_track_count(); ++i) {1606if (animation->track_get_path(i).get_concatenated_names() != E.key) {1607if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) {1608show_other_tracks = false;1609break;1610}1611}1612}16131614bool show_own_tracks = true;16151616for (int i = 0; i < animation->get_track_count(); ++i) {1617if (animation->track_get_path(i).get_concatenated_names() == E.key) {1618if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) {1619show_own_tracks = false;1620break;1621}1622}1623}16241625if (show_own_tracks) {1626show_other_tracks = false;1627}16281629for (int i = 0; i < animation->get_track_count(); ++i) {1630if (animation->track_get_path(i).get_concatenated_names() == E.key) {1631if (show_own_tracks) {1632_show_track(i);1633}1634} else {1635if (show_other_tracks) {1636_show_track(i);1637} else {1638_hide_track(i);1639}1640}1641}16421643queue_redraw();1644return;1645}1646return;1647}1648}1649}16501651// Check this first, to allow manipulating key handles while ignoring keyframes before scaling/moving.1652bool inside_selection_handles_rect = !read_only && selection_handles_rect.has_point(mb->get_position());16531654// First, check keyframe.1655// Command/Control makes it ignore the keyframe, so control point editors can be force-edited.1656if (!inside_selection_handles_rect && !mb->is_command_or_control_pressed()) {1657if (_try_select_at_ui_pos(mb->get_position(), mb->is_shift_pressed(), true)) {1658return;1659}1660}1661// Second, check key handles.1662for (int i = 0; i < edit_points.size(); i++) {1663if (!read_only) {1664if (edit_points[i].in_rect.has_point(mb->get_position())) {1665moving_handle = -1;1666moving_handle_key = edit_points[i].key;1667moving_handle_track = edit_points[i].track;1668moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);1669moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);1670queue_redraw();1671return;1672}16731674if (edit_points[i].out_rect.has_point(mb->get_position())) {1675moving_handle = 1;1676moving_handle_key = edit_points[i].key;1677moving_handle_track = edit_points[i].track;1678moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);1679moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);1680queue_redraw();1681return;1682}1683}1684}16851686// Box scaling/movement.1687if (inside_selection_handles_rect) {1688const Vector2i rel_pos = mb->get_position() - selection_rect.position;1689scaling_selection_handles = Vector2i();16901691// Check which scaling handles are available.1692if (selection_rect.size.width > CMP_EPSILON) {1693if (rel_pos.x <= 0) {1694scaling_selection_handles.x = -1;1695} else if (rel_pos.x >= selection_rect.size.width) {1696scaling_selection_handles.x = 1;1697}1698}1699if (selection_rect.size.height > CMP_EPSILON) {1700if (rel_pos.y <= 0) {1701scaling_selection_handles.y = -1;1702} else if (rel_pos.y >= selection_rect.size.height) {1703scaling_selection_handles.y = 1;1704}1705}17061707if (scaling_selection_handles != Vector2i()) {1708scaling_selection = true;17091710const float time = ((selection_rect.position.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();1711const float h = (get_size().height / 2.0 - selection_rect.position.y) * timeline_v_zoom + timeline_v_scroll;1712scaling_selection_pivot = Point2(time, h);17131714return;1715}17161717// If not scaling, that means we're moving.1718moving_selection_attempt = true;1719moving_selection = false;1720moving_selection_mouse_begin = mb->get_position();1721// The pivot will be from the mouse click location, not a specific key.1722moving_selection_from_key = -1;1723moving_selection_from_track = selected_track;1724moving_selection_offset = Vector2();1725select_single_attempt = IntPair(-1, -1);17261727return;1728}17291730// Insert new point.1731if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_or_control_pressed()) {1732float h = (get_size().height / 2.0 - mb->get_position().y) * timeline_v_zoom + timeline_v_scroll;1733Array new_point = animation->make_default_bezier_key(h);17341735real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();1736while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {1737time += 0.0001;1738}17391740EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1741undo_redo->create_action(TTR("Add Bezier Point"));1742undo_redo->add_do_method(animation.ptr(), "bezier_track_insert_key", selected_track, time, new_point[0], Vector2(new_point[1], new_point[2]), Vector2(new_point[3], new_point[4]));1743undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode_at_time", animation.ptr(), selected_track, time, (Animation::HandleMode)editor->bezier_key_mode->get_selected_id(), Animation::HANDLE_SET_MODE_AUTO);1744undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);1745undo_redo->commit_action();17461747// Then attempt to move.1748int index = animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX);1749ERR_FAIL_COND(index == -1);1750_clear_selection();1751_select_at_anim(animation, selected_track, animation->track_get_key_time(selected_track, index), true);17521753moving_selection_attempt = true;1754moving_inserted_key = true;1755moving_selection = false;1756moving_selection_mouse_begin = mb->get_position();1757moving_selection_from_key = index;1758moving_selection_from_track = selected_track;1759moving_selection_offset = Vector2();1760select_single_attempt = IntPair(-1, -1);1761queue_redraw();17621763return;1764}17651766// Box select.1767if (mb->get_position().x >= limit && mb->get_position().x < get_size().width) {1768box_selecting_attempt = true;1769box_selecting = false;1770box_selecting_add = false;1771box_selection_from = mb->get_position();1772return;1773}1774}17751776if (box_selecting_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1777if (box_selecting) {1778// Do actual select.1779if (!box_selecting_add) {1780_clear_selection();1781}17821783Vector2 bs_from = box_selection_from;1784Vector2 bs_to = box_selection_to;1785if (bs_from.x > bs_to.x) {1786SWAP(bs_from.x, bs_to.x);1787}1788if (bs_from.y > bs_to.y) {1789SWAP(bs_from.y, bs_to.y);1790}1791Rect2 rect(bs_from, bs_to - bs_from);17921793bool track_set = false;1794int j = 0;1795for (int i = 0; i < edit_points.size(); i++) {1796if (edit_points[i].point_rect.intersects(rect)) {1797_select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), j == 0 && !box_selecting_add);1798if (!track_set) {1799track_set = true;1800set_animation_and_track(animation, edit_points[i].track, read_only);1801}1802j++;1803}1804}1805} else {1806_clear_selection(); // Clicked and nothing happened, so clear the selection.18071808// Select by clicking on curve.1809int track_count = animation->get_track_count();18101811real_t animation_length = animation->get_length();1812animation->set_length(real_t(INT_MAX)); // bezier_track_interpolate doesn't find keys if they exist beyond anim length.18131814real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();18151816for (int i = 0; i < track_count; ++i) {1817if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i) || locked_tracks.has(i)) {1818continue;1819}18201821float track_h = animation->bezier_track_interpolate(i, time);1822float track_height = _bezier_h_to_pixel(track_h);18231824if (std::abs(mb->get_position().y - track_height) < 10) {1825set_animation_and_track(animation, i, read_only);1826break;1827}1828}18291830animation->set_length(animation_length);1831}18321833box_selecting_attempt = false;1834box_selecting = false;1835queue_redraw();1836}18371838if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1839if (!read_only) {1840if (moving_selection && (std::abs(moving_selection_offset.x) > CMP_EPSILON || std::abs(moving_selection_offset.y) > CMP_EPSILON)) {1841// Commit it.1842EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1843undo_redo->create_action(TTR("Move Bezier Points"));18441845List<AnimMoveRestore> to_restore;1846List<Animation::HandleMode> to_restore_handle_modes;1847// 1 - Remove the keys.1848for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1849undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);1850}1851// 2 - Remove overlapped keys.1852for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1853real_t newtime = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;18541855int idx = animation->track_find_key(E->get().first, newtime, Animation::FIND_MODE_APPROX);1856if (idx == -1) {1857continue;1858}18591860if (selection.has(IntPair(E->get().first, idx))) {1861continue; // Already in selection, don't save.1862}18631864undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);1865AnimMoveRestore amr;18661867amr.key = animation->track_get_key_value(E->get().first, idx);1868amr.track = E->get().first;1869amr.time = newtime;18701871to_restore.push_back(amr);1872to_restore_handle_modes.push_back(animation->bezier_track_get_key_handle_mode(E->get().first, idx));1873}18741875// 3 - Move the keys (re-insert them).1876for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1877real_t newpos = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;1878Array key = animation->track_get_key_value(E->get().first, E->get().second);1879real_t h = key[0];1880h += moving_selection_offset.y;1881key[0] = h;18821883Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second);1884Animation::HandleSetMode handle_set_mode = Animation::HANDLE_SET_MODE_NONE;1885if (moving_inserted_key) {1886handle_mode = (Animation::HandleMode)editor->bezier_key_mode->get_selected_id();1887handle_set_mode = Animation::HANDLE_SET_MODE_AUTO;1888}18891890undo_redo->add_do_method(1891this,1892"_bezier_track_insert_key_at_anim",1893animation,1894E->get().first,1895newpos,1896key[0],1897Vector2(key[1], key[2]),1898Vector2(key[3], key[4]),1899handle_mode,1900handle_set_mode);1901}19021903// 4 - (undo) Remove inserted keys.1904for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1905real_t newpos = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;1906undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);1907}19081909// 5 - (undo) Reinsert keys.1910for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1911real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);1912Array key = animation->track_get_key_value(E->get().first, E->get().second);1913undo_redo->add_undo_method(1914this,1915"_bezier_track_insert_key_at_anim",1916animation,1917E->get().first,1918oldpos,1919key[0],1920Vector2(key[1], key[2]),1921Vector2(key[3], key[4]),1922animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second));1923}19241925// 6 - (undo) Reinsert overlapped keys.1926List<AnimMoveRestore>::ConstIterator restore_itr = to_restore.begin();1927List<Animation::HandleMode>::ConstIterator handle_itr = to_restore_handle_modes.begin();1928for (; restore_itr != to_restore.end() && handle_itr != to_restore_handle_modes.end(); ++restore_itr, ++handle_itr) {1929const AnimMoveRestore &amr = *restore_itr;1930Array key = amr.key;1931undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);1932undo_redo->add_undo_method(1933this,1934"_bezier_track_insert_key_at_anim",1935animation,1936amr.track,1937amr.time,1938key[0],1939Vector2(key[1], key[2]),1940Vector2(key[3], key[4]),1941*handle_itr);1942}19431944undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);1945undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);19461947// 7 - Reselect.1948int i = 0;1949for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1950real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);1951real_t newpos = oldpos + moving_selection_offset.x;19521953undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos, i == 0);1954undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos, i == 0);1955i++;1956}19571958AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();1959if (ape) {1960undo_redo->add_do_method(ape, "_animation_update_key_frame");1961undo_redo->add_undo_method(ape, "_animation_update_key_frame");1962}1963undo_redo->commit_action();19641965} else if (select_single_attempt != IntPair(-1, -1)) {1966selection.clear();1967set_animation_and_track(animation, select_single_attempt.first, read_only);1968_select_at_anim(animation, select_single_attempt.first, animation->track_get_key_time(select_single_attempt.first, select_single_attempt.second), true);1969}19701971moving_selection = false;1972moving_selection_attempt = false;1973moving_inserted_key = false;1974moving_selection_mouse_begin = Point2();1975queue_redraw();1976}1977}19781979if (scaling_selection && mb.is_valid() && !read_only && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1980if (std::abs(scaling_selection_scale.x - 1) > CMP_EPSILON || std::abs(scaling_selection_scale.y - 1) > CMP_EPSILON) {1981// Scale it.1982EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1983undo_redo->create_action(TTR("Scale Bezier Points"));19841985List<AnimMoveRestore> to_restore;1986List<Animation::HandleMode> to_restore_handle_modes;1987// 1 - Remove the keys.1988for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1989undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);1990}1991// 2 - Remove overlapped keys.1992for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1993real_t newtime = animation->track_get_key_time(E->get().first, E->get().second);1994newtime += -scaling_selection_offset.x + (newtime - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);19951996int idx = animation->track_find_key(E->get().first, newtime, Animation::FIND_MODE_APPROX);1997if (idx == -1) {1998continue;1999}20002001if (selection.has(IntPair(E->get().first, idx))) {2002continue; // Already in selection, don't save.2003}20042005undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);2006AnimMoveRestore amr;20072008amr.key = animation->track_get_key_value(E->get().first, idx);2009amr.track = E->get().first;2010amr.time = newtime;20112012to_restore.push_back(amr);2013to_restore_handle_modes.push_back(animation->bezier_track_get_key_handle_mode(E->get().first, idx));2014}20152016// 3 - Scale the keys (re-insert them).2017for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2018real_t newpos = animation->track_get_key_time(E->get().first, E->get().second);2019newpos += -scaling_selection_offset.x + (newpos - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);20202021Array key = animation->track_get_key_value(E->get().first, E->get().second);2022real_t h = key[0];2023h += -scaling_selection_offset.y + (h - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);2024key[0] = h;20252026undo_redo->add_do_method(2027this,2028"_bezier_track_insert_key_at_anim",2029animation,2030E->get().first,2031newpos,2032key[0],2033Vector2(key[1], key[2]),2034Vector2(key[3], key[4]),2035animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second));2036}20372038// 4 - (undo) Remove inserted keys.2039for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2040real_t newpos = animation->track_get_key_time(E->get().first, E->get().second);2041newpos += -scaling_selection_offset.x + (newpos - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);2042undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);2043}20442045// 5 - (undo) Reinsert keys.2046for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2047real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);2048Array key = animation->track_get_key_value(E->get().first, E->get().second);2049undo_redo->add_undo_method(2050this,2051"_bezier_track_insert_key_at_anim",2052animation,2053E->get().first,2054oldpos,2055key[0],2056Vector2(key[1], key[2]),2057Vector2(key[3], key[4]),2058animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second));2059}20602061// 6 - (undo) Reinsert overlapped keys.2062List<AnimMoveRestore>::ConstIterator restore_itr = to_restore.begin();2063List<Animation::HandleMode>::ConstIterator handle_itr = to_restore_handle_modes.begin();2064for (; restore_itr != to_restore.end() && handle_itr != to_restore_handle_modes.end(); ++restore_itr, ++handle_itr) {2065const AnimMoveRestore &amr = *restore_itr;2066Array key = amr.key;2067undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);2068undo_redo->add_undo_method(2069this,2070"_bezier_track_insert_key_at_anim",2071animation,2072amr.track,2073amr.time,2074key[0],2075Vector2(key[1], key[2]),2076Vector2(key[3], key[4]),2077*handle_itr);2078}20792080undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2081undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);20822083// 7 - Reselect.2084int i = 0;2085for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2086real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);2087real_t newpos = animation->track_get_key_time(E->get().first, E->get().second);2088newpos += -scaling_selection_offset.x + (newpos - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);20892090undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos, i == 0);2091undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos, i == 0);2092i++;2093}20942095AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2096if (ape) {2097undo_redo->add_do_method(ape, "_animation_update_key_frame");2098undo_redo->add_undo_method(ape, "_animation_update_key_frame");2099}2100undo_redo->commit_action();2101}21022103scaling_selection = false;2104scaling_selection_scale = Vector2(1, 1);2105scaling_selection_offset = Vector2();2106queue_redraw();2107}21082109Ref<InputEventMouseMotion> mm = p_event;2110if (moving_selection_attempt && mm.is_valid()) {2111Point2 new_pos = mm->get_position();2112if (mm->is_alt_pressed()) { // Axis snap key move when alt is pressed2113if (Math::abs(new_pos.x - moving_selection_mouse_begin.x) > Math::abs(new_pos.y - moving_selection_mouse_begin.y)) {2114new_pos.y = moving_selection_mouse_begin.y;2115} else {2116new_pos.x = moving_selection_mouse_begin.x;2117}2118}21192120if (!moving_selection) {2121moving_selection = true;2122select_single_attempt = IntPair(-1, -1);2123}21242125if (!read_only) {2126float y = (get_size().height / 2.0 - new_pos.y) * timeline_v_zoom + timeline_v_scroll;2127float moving_selection_begin_time = ((moving_selection_mouse_begin.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();2128float new_time = ((new_pos.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();2129float moving_selection_pivot = moving_selection_from_key != -1 ? animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key) : 0;2130float time_delta = new_time - moving_selection_begin_time;21312132float snapped_time = editor->snap_time(moving_selection_pivot + time_delta);2133float time_offset = 0.0;2134if (std::abs(moving_selection_offset.x) > CMP_EPSILON || (snapped_time > moving_selection_pivot && time_delta > CMP_EPSILON) || (snapped_time < moving_selection_pivot && time_delta < -CMP_EPSILON)) {2135time_offset = snapped_time - moving_selection_pivot;2136}21372138float moving_selection_begin_value;2139if (moving_selection_from_key == -1) {2140moving_selection_begin_value = (get_size().height / 2.0 - moving_selection_mouse_begin.y) * timeline_v_zoom + timeline_v_scroll;2141} else {2142moving_selection_begin_value = animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key);2143}21442145float y_offset = y - moving_selection_begin_value;2146moving_selection_offset = Vector2(time_offset, y_offset);2147}21482149additional_moving_handle_lefts.clear();2150additional_moving_handle_rights.clear();21512152queue_redraw();2153}21542155if (box_selecting_attempt && mm.is_valid()) {2156if (!box_selecting) {2157box_selecting = true;2158box_selecting_add = mm->is_shift_pressed();2159}21602161box_selection_to = mm->get_position();2162queue_redraw();2163}21642165if (scaling_selection && mm.is_valid() && !read_only) {2166Point2 mp = mm->get_position();2167const int handle_length = Math::round((selection_handles_rect.size.width - selection_rect.size.width) / 4.0);2168Point2 rel_pos;21692170// Calculate the scale according with the distance between the mouse's position (adjusted so that the cursor appears inside the handles)2171// and the opposite end of the `selection_rect`.21722173if (scaling_selection_handles.x != 0) {2174if (scaling_selection_handles.x == 1) { // Right Handle2175const int handle_adjust = Math::round(mp.x - (scaling_selection_scale.x >= 0 ? selection_rect.position.x : (selection_rect.position.x + selection_rect.size.width)));2176mp.x -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.x * SIGN(handle_adjust);21772178if (editor->is_snap_keys_enabled()) {2179mp.x = editor->snap_time((mp.x - limit) / timeline->get_zoom_scale(), true) + timeline->get_value();2180mp.x = (mp.x - timeline->get_value()) * timeline->get_zoom_scale() + limit;2181}21822183rel_pos.x = scaling_selection_scale.x >= 0 ? (mp.x - selection_rect.position.x) : selection_rect.position.x + selection_rect.size.width - mp.x;2184} else { // Left Handle2185const int handle_adjust = Math::round((scaling_selection_scale.x >= 0 ? (selection_rect.position.x + selection_rect.size.width) : selection_rect.position.x) - mp.x);2186mp.x -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.x * SIGN(handle_adjust);21872188const float x = editor->snap_time((mp.x - limit) / timeline->get_zoom_scale(), true) + timeline->get_value();2189if (editor->is_snap_keys_enabled()) {2190mp.x = (x - timeline->get_value()) * timeline->get_zoom_scale() + limit;2191}21922193rel_pos.x = scaling_selection_scale.x >= 0 ? (selection_rect.position.x + selection_rect.size.width - mp.x) : (mp.x - selection_rect.position.x);2194scaling_selection_offset.x = scaling_selection_pivot.x - x;2195}21962197scaling_selection_scale.x *= rel_pos.x / selection_rect.size.width;2198if (scaling_selection_scale.x == 0) {2199scaling_selection_scale.x = CMP_EPSILON;2200}2201}22022203if (scaling_selection_handles.y != 0) {2204if (scaling_selection_handles.y == 1) { // Bottom Handle2205const int handle_adjust = Math::round(mp.y - (scaling_selection_scale.y >= 0 ? selection_rect.position.y : (selection_rect.position.y + selection_rect.size.height)));2206mp.y -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.y * SIGN(handle_adjust);22072208if (scaling_selection_scale.y >= 0) {2209rel_pos.y = mp.y - selection_rect.position.y;2210} else {2211rel_pos.y = selection_rect.position.y + selection_rect.size.height - mp.y;2212}2213} else { // Top Handle2214const int handle_adjust = Math::round((scaling_selection_scale.y >= 0 ? (selection_rect.position.y + selection_rect.size.height) : selection_rect.position.y) - mp.y);2215mp.y -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.y * SIGN(handle_adjust);22162217if (scaling_selection_scale.y >= 0) {2218rel_pos.y = selection_rect.position.y + selection_rect.size.height - mp.y;2219} else {2220rel_pos.y = mp.y - selection_rect.position.y;2221}22222223const float h = (get_size().height / 2.0 - mp.y) * timeline_v_zoom + timeline_v_scroll;2224scaling_selection_offset.y = scaling_selection_pivot.y - h;2225}22262227scaling_selection_scale.y *= rel_pos.y / selection_rect.size.height;2228if (scaling_selection_scale.y == 0) {2229scaling_selection_scale.y = CMP_EPSILON;2230}2231}22322233queue_redraw();2234}22352236if ((moving_handle == 1 || moving_handle == -1) && mm.is_valid()) {2237float y = (get_size().height / 2.0 - mm->get_position().y) * timeline_v_zoom + timeline_v_scroll;2238float x = editor->snap_time((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();22392240Vector2 key_pos = Vector2(animation->track_get_key_time(moving_handle_track, moving_handle_key), animation->bezier_track_get_key_value(moving_handle_track, moving_handle_key));22412242Vector2 moving_handle_value = Vector2(x, y) - key_pos;22432244moving_handle_left = animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key);2245moving_handle_right = animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key);22462247if (moving_handle == -1) {2248moving_handle_left = moving_handle_value;22492250Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key);22512252if (handle_mode == Animation::HANDLE_MODE_BALANCED) {2253real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;2254Transform2D xform;2255xform.set_scale(Vector2(1.0, 1.0 / ratio));22562257Vector2 vec_out = xform.xform(moving_handle_right);2258Vector2 vec_in = xform.xform(moving_handle_left);22592260moving_handle_right = xform.affine_inverse().xform(-vec_in.normalized() * vec_out.length());2261} else if (handle_mode == Animation::HANDLE_MODE_MIRRORED) {2262moving_handle_right = -moving_handle_left;2263}2264} else if (moving_handle == 1) {2265moving_handle_right = moving_handle_value;22662267Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key);22682269if (handle_mode == Animation::HANDLE_MODE_BALANCED) {2270real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;2271Transform2D xform;2272xform.set_scale(Vector2(1.0, 1.0 / ratio));22732274Vector2 vec_in = xform.xform(moving_handle_left);2275Vector2 vec_out = xform.xform(moving_handle_right);22762277moving_handle_left = xform.affine_inverse().xform(-vec_out.normalized() * vec_in.length());2278} else if (handle_mode == Animation::HANDLE_MODE_MIRRORED) {2279moving_handle_left = -moving_handle_right;2280}2281}2282queue_redraw();2283}22842285if ((moving_handle == -1 || moving_handle == 1) && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {2286if (!read_only) {2287EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2288undo_redo->create_action(TTR("Move Bezier Points"));2289if (moving_handle == -1) {2290real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;2291undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio);2292undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio);2293} else if (moving_handle == 1) {2294real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;2295undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio);2296undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio);2297}2298AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2299if (ape) {2300undo_redo->add_do_method(ape, "_animation_update_key_frame");2301undo_redo->add_undo_method(ape, "_animation_update_key_frame");2302}2303undo_redo->commit_action();2304moving_handle = 0;2305queue_redraw();2306}2307}2308}23092310bool AnimationBezierTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable) {2311for (int i = 0; i < edit_points.size(); i++) {2312// Path 2D editing in the 3D and 2D editors works the same way. (?)2313if (edit_points[i].point_rect.has_point(p_pos)) {2314IntPair pair = IntPair(edit_points[i].track, edit_points[i].key);2315if (p_aggregate) {2316// Add to selection.2317if (selection.has(pair)) {2318if (p_deselectable) {2319selection.erase(pair);2320emit_signal(SNAME("deselect_key"), edit_points[i].key, edit_points[i].track);2321}2322} else {2323_select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), false);2324}2325queue_redraw();2326select_single_attempt = IntPair(-1, -1);2327} else {2328if (p_deselectable) {2329moving_selection_attempt = true;2330moving_selection_from_key = pair.second;2331moving_selection_from_track = pair.first;2332moving_selection_mouse_begin = p_pos;2333moving_selection_offset = Vector2();2334moving_handle_track = pair.first;2335moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second);2336moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second);23372338if (selection.has(pair)) {2339moving_selection = false;2340} else {2341moving_selection = true;2342}2343select_single_attempt = pair;2344}23452346set_animation_and_track(animation, pair.first, read_only);2347if (!selection.has(pair)) {2348selection.clear();2349_select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), true);2350}2351}2352return true;2353}2354}2355return false;2356}23572358void AnimationBezierTrackEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {2359Ref<InputEventMouseMotion> mm = p_event;2360if (mm.is_valid()) {2361if (mm->get_position().x > timeline->get_name_limit()) {2362timeline_v_scroll += p_scroll_vec.y * timeline_v_zoom;2363timeline_v_scroll = CLAMP(timeline_v_scroll, -100000, 100000);2364timeline->set_value(timeline->get_value() - p_scroll_vec.x / timeline->get_zoom_scale());2365} else {2366track_v_scroll += p_scroll_vec.y;2367if (track_v_scroll < -track_v_scroll_max) {2368track_v_scroll = -track_v_scroll_max;2369} else if (track_v_scroll > 0) {2370track_v_scroll = 0;2371}2372}2373queue_redraw();2374}2375}23762377void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {2378const float v_zoom_orig = timeline_v_zoom;2379Ref<InputEventWithModifiers> iewm = p_event;2380if (iewm.is_valid() && iewm->is_alt_pressed()) {2381// Alternate zoom (doesn't affect timeline).2382timeline_v_zoom = CLAMP(timeline_v_zoom / p_zoom_factor, 0.000001, 100000);2383} else {2384float zoom_factor = p_zoom_factor > 1.0 ? AnimationTimelineEdit::SCROLL_ZOOM_FACTOR_IN : AnimationTimelineEdit::SCROLL_ZOOM_FACTOR_OUT;2385timeline->_zoom_callback(zoom_factor, p_origin, p_event);2386}2387timeline_v_scroll = timeline_v_scroll + (p_origin.y - get_size().y / 2.0) * (timeline_v_zoom - v_zoom_orig);2388queue_redraw();2389}23902391float AnimationBezierTrackEdit::get_bezier_key_value(Array p_bezier_key_array) {2392return p_bezier_key_array[0];2393}23942395void AnimationBezierTrackEdit::_menu_selected(int p_index) {2396int limit = timeline->get_name_limit();23972398real_t time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();23992400switch (p_index) {2401case MENU_KEY_INSERT: {2402if (animation->get_track_count() > 0) {2403if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {2404time = editor->snap_time(time);2405}2406while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {2407time += 0.001;2408}2409float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;2410Array new_point = animation->make_default_bezier_key(h);2411Animation::HandleMode handle_mode = (Animation::HandleMode)editor->bezier_key_mode->get_selected_id();2412EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2413undo_redo->create_action(TTR("Add Bezier Point"));2414undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);2415undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode_at_time", animation.ptr(), selected_track, time, handle_mode, Animation::HANDLE_SET_MODE_AUTO);2416undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);2417undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);2418AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2419if (ape) {2420undo_redo->add_do_method(ape, "_animation_update_key_frame");2421undo_redo->add_undo_method(ape, "_animation_update_key_frame");2422}2423undo_redo->commit_action();2424queue_redraw();2425}2426} break;2427case MENU_KEY_DUPLICATE: {2428duplicate_selected_keys(time, true);2429} break;2430case MENU_KEY_DELETE: {2431delete_selection();2432} break;2433case MENU_KEY_CUT: {2434copy_selected_keys(true);2435} break;2436case MENU_KEY_COPY: {2437copy_selected_keys(false);2438} break;2439case MENU_KEY_PASTE: {2440paste_keys(time, true);2441} break;2442case MENU_KEY_SET_HANDLE_FREE: {2443_change_selected_keys_handle_mode(Animation::HANDLE_MODE_FREE);2444} break;2445case MENU_KEY_SET_HANDLE_LINEAR: {2446_change_selected_keys_handle_mode(Animation::HANDLE_MODE_LINEAR);2447} break;2448case MENU_KEY_SET_HANDLE_BALANCED: {2449_change_selected_keys_handle_mode(Animation::HANDLE_MODE_BALANCED);2450} break;2451case MENU_KEY_SET_HANDLE_MIRRORED: {2452_change_selected_keys_handle_mode(Animation::HANDLE_MODE_MIRRORED);2453} break;2454case MENU_KEY_SET_HANDLE_AUTO_BALANCED: {2455_change_selected_keys_handle_mode(Animation::HANDLE_MODE_BALANCED, true);2456} break;2457case MENU_KEY_SET_HANDLE_AUTO_MIRRORED: {2458_change_selected_keys_handle_mode(Animation::HANDLE_MODE_MIRRORED, true);2459} break;2460}2461}24622463void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid) {2464if (selection.is_empty()) {2465return;2466}24672468real_t top_time = 1e10;2469for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2470real_t t = animation->track_get_key_time(E->get().first, E->get().second);2471if (t < top_time) {2472top_time = t;2473}2474}24752476EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2477undo_redo->create_action(TTR("Animation Duplicate Keys"));24782479List<Pair<int, real_t>> new_selection_values;24802481for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2482real_t t = animation->track_get_key_time(E->get().first, E->get().second);2483real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();24842485if (p_ofs_valid) {2486if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {2487insert_pos = editor->snap_time(insert_pos);2488}2489}24902491real_t dst_time = t + (insert_pos - top_time);2492int existing_idx = animation->track_find_key(E->get().first, dst_time, Animation::FIND_MODE_APPROX);24932494undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second));2495undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time);24962497Pair<int, real_t> p;2498p.first = E->get().first;2499p.second = dst_time;2500new_selection_values.push_back(p);25012502if (existing_idx != -1) {2503undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, existing_idx), animation->track_get_key_transition(E->get().first, existing_idx));2504}2505}25062507undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2508undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);25092510// Reselect duplicated.2511int i = 0;2512for (const Pair<int, real_t> &E : new_selection_values) {2513undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second, i == 0);2514i++;2515}2516i = 0;2517for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2518real_t time = animation->track_get_key_time(E->get().first, E->get().second);2519undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, time, i == 0);2520i++;2521}25222523AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2524if (ape) {2525undo_redo->add_do_method(ape, "_animation_update_key_frame");2526undo_redo->add_undo_method(ape, "_animation_update_key_frame");2527}2528undo_redo->add_do_method(this, "queue_redraw");2529undo_redo->add_undo_method(this, "queue_redraw");2530undo_redo->commit_action();2531}25322533void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) {2534if (selection.is_empty()) {2535return;2536}25372538float top_time = 1e10;2539for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2540float t = animation->track_get_key_time(E->get().first, E->get().second);2541if (t < top_time) {2542top_time = t;2543}2544}25452546RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo> keys;2547for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2548AnimationTrackEditor::SelectedKey sk;2549AnimationTrackEditor::KeyInfo ki;2550sk.track = E->get().first;2551sk.key = E->get().second;2552ki.pos = animation->track_get_key_time(E->get().first, E->get().second);2553keys.insert(sk, ki);2554}2555editor->_set_key_clipboard(selected_track, top_time, keys);25562557if (p_cut) {2558EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2559undo_redo->create_action(TTR("Animation Cut Keys"), UndoRedo::MERGE_DISABLE, animation.ptr());2560undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2561undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);2562int i = 0;2563for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) {2564int track_idx = E->key().track;2565int key_idx = E->key().key;2566float time = E->value().pos;2567undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track_idx, time);2568undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track_idx, time, animation->track_get_key_value(track_idx, key_idx), animation->track_get_key_transition(track_idx, key_idx));2569undo_redo->add_undo_method(this, "_select_at_anim", animation, track_idx, time, i == 0);2570i++;2571}2572i = 0;2573for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) {2574undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos, i == 0);2575i++;2576}25772578AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2579if (ape) {2580undo_redo->add_do_method(ape, "_animation_update_key_frame");2581undo_redo->add_undo_method(ape, "_animation_update_key_frame");2582}2583undo_redo->add_do_method(this, "queue_redraw");2584undo_redo->add_undo_method(this, "queue_redraw");25852586undo_redo->commit_action();2587}2588}25892590void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {2591if (editor->is_key_clipboard_active() && animation.is_valid() && (selected_track >= 0 && selected_track < animation->get_track_count())) {2592EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2593undo_redo->create_action(TTR("Animation Paste Keys"));25942595bool same_track = true;2596bool all_compatible = true;25972598for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {2599const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];26002601if (key.track != 0) {2602same_track = false;2603break;2604}26052606if (!editor->_is_track_compatible(selected_track, key.value.get_type(), key.track_type)) {2607all_compatible = false;2608break;2609}2610}26112612ERR_FAIL_COND_MSG(!all_compatible, "Paste failed: Not all animation keys were compatible with their target tracks");2613if (!same_track) {2614WARN_PRINT("Pasted animation keys from multiple tracks into single Bezier track");2615}26162617List<Pair<int, float>> new_selection_values;2618for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {2619const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];26202621float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();2622if (p_ofs_valid) {2623if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {2624insert_pos = editor->snap_time(insert_pos);2625}2626}2627float dst_time = key.time + insert_pos;26282629int existing_idx = animation->track_find_key(selected_track, dst_time, Animation::FIND_MODE_APPROX);26302631Variant value = key.value;2632if (key.track_type != Animation::TYPE_BEZIER) {2633value = animation->make_default_bezier_key(key.value);2634}26352636undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, dst_time, value, key.transition);2637undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, dst_time);26382639Pair<int, float> p;2640p.first = selected_track;2641p.second = dst_time;2642new_selection_values.push_back(p);26432644if (existing_idx != -1) {2645undo_redo->add_undo_method(animation.ptr(), "track_insert_key", selected_track, dst_time, animation->track_get_key_value(selected_track, existing_idx), animation->track_get_key_transition(selected_track, existing_idx));2646}2647}26482649undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2650undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);26512652// Reselect pasted.2653int i = 0;2654for (const Pair<int, float> &E : new_selection_values) {2655undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second, i == 0);2656i++;2657}2658i = 0;2659for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2660undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, animation->track_get_key_time(E->get().first, E->get().second), i == 0);2661i++;2662}26632664AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2665if (ape) {2666undo_redo->add_do_method(ape, "_animation_update_key_frame");2667undo_redo->add_undo_method(ape, "_animation_update_key_frame");2668}2669undo_redo->add_do_method(this, "queue_redraw");2670undo_redo->add_undo_method(this, "queue_redraw");26712672undo_redo->commit_action();2673}2674}26752676void AnimationBezierTrackEdit::delete_selection() {2677if (selection.size()) {2678EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2679undo_redo->create_action(TTR("Animation Delete Keys"));26802681for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2682undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);2683undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, animation->track_get_key_time(E->get().first, E->get().second), animation->track_get_key_value(E->get().first, E->get().second), 1);2684}2685undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2686undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);2687AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2688if (ape) {2689undo_redo->add_do_method(ape, "_animation_update_key_frame");2690undo_redo->add_undo_method(ape, "_animation_update_key_frame");2691}2692undo_redo->commit_action();26932694//selection.clear();2695}2696}26972698void AnimationBezierTrackEdit::_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) {2699int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);2700p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode, p_handle_set_mode);2701}27022703void AnimationBezierTrackEdit::_bind_methods() {2704ClassDB::bind_method(D_METHOD("_clear_selection"), &AnimationBezierTrackEdit::_clear_selection);2705ClassDB::bind_method(D_METHOD("_clear_selection_for_anim"), &AnimationBezierTrackEdit::_clear_selection_for_anim);2706ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim);2707ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after);2708ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after);2709ClassDB::bind_method(D_METHOD("_lock_track"), &AnimationBezierTrackEdit::_lock_track);2710ClassDB::bind_method(D_METHOD("_hide_track"), &AnimationBezierTrackEdit::_hide_track);2711ClassDB::bind_method(D_METHOD("_bezier_track_insert_key_at_anim"), &AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim, DEFVAL(Animation::HANDLE_SET_MODE_NONE));27122713ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track")));2714ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::INT, "track")));2715ADD_SIGNAL(MethodInfo("clear_selection"));2716ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "timeline_only")));2717}27182719AnimationBezierTrackEdit::AnimationBezierTrackEdit() {2720panner.instantiate();2721panner->set_callbacks(callable_mp(this, &AnimationBezierTrackEdit::_pan_callback), callable_mp(this, &AnimationBezierTrackEdit::_zoom_callback));27222723play_position = memnew(Control);2724play_position->set_mouse_filter(MOUSE_FILTER_PASS);2725add_child(play_position);2726play_position->set_anchors_and_offsets_preset(PRESET_FULL_RECT);2727play_position->connect(SceneStringName(draw), callable_mp(this, &AnimationBezierTrackEdit::_play_position_draw));2728set_focus_mode(FOCUS_CLICK);27292730set_clip_contents(true);27312732ED_SHORTCUT("animation_bezier_editor/focus", TTRC("Focus"), Key::F);2733ED_SHORTCUT("animation_bezier_editor/select_all_keys", TTRC("Select All Keys"), KeyModifierMask::CMD_OR_CTRL | Key::A);2734ED_SHORTCUT("animation_bezier_editor/deselect_all_keys", TTRC("Deselect All Keys"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::A);27352736menu = memnew(PopupMenu);2737add_child(menu);2738menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationBezierTrackEdit::_menu_selected));2739}274027412742