Path: blob/master/editor/animation/animation_bezier_editor.cpp
9903 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 "editor/animation/animation_player_editor_plugin.h"33#include "editor/editor_node.h"34#include "editor/editor_string_names.h"35#include "editor/editor_undo_redo_manager.h"36#include "editor/gui/editor_spin_slider.h"37#include "editor/settings/editor_settings.h"38#include "editor/themes/editor_scale.h"39#include "scene/gui/option_button.h"40#include "scene/gui/view_panner.h"41#include "scene/resources/text_line.h"4243#include <climits>4445float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) {46float h = p_h;47h = (h - timeline_v_scroll) / timeline_v_zoom;48h = (get_size().height / 2.0) - h;49return h;50}5152void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {53float scale = timeline->get_zoom_scale();5455int limit = timeline->get_name_limit();56int right_limit = get_size().width;5758// Selection may have altered the order of keys.59RBMap<real_t, int> key_order;6061for (int i = 0; i < animation->track_get_key_count(p_track); i++) {62real_t ofs = animation->track_get_key_time(p_track, i);63if (selection.has(IntPair(p_track, i))) {64if (moving_selection) {65ofs += moving_selection_offset.x;66} else if (scaling_selection) {67ofs += -scaling_selection_offset.x + (ofs - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);68}69}7071key_order[ofs] = i;72}7374for (RBMap<real_t, int>::Element *E = key_order.front(); E; E = E->next()) {75int i = E->get();7677if (!E->next()) {78break;79}8081int i_n = E->next()->get();8283float offset = animation->track_get_key_time(p_track, i);84float height = animation->bezier_track_get_key_value(p_track, i);85Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i);86if (p_track == moving_handle_track && (moving_handle == -1 || moving_handle == 1) && moving_handle_key == i) {87out_handle = moving_handle_right;88}8990if (selection.has(IntPair(p_track, i))) {91if (moving_selection) {92offset += moving_selection_offset.x;93height += moving_selection_offset.y;94} else if (scaling_selection) {95offset += -scaling_selection_offset.x + (offset - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);96height += -scaling_selection_offset.y + (height - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);97}98}99100float offset_n = animation->track_get_key_time(p_track, i_n);101float height_n = animation->bezier_track_get_key_value(p_track, i_n);102Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n);103if (p_track == moving_handle_track && (moving_handle == -1 || moving_handle == 1) && moving_handle_key == i_n) {104in_handle = moving_handle_left;105}106107if (selection.has(IntPair(p_track, i_n))) {108if (moving_selection) {109offset_n += moving_selection_offset.x;110height_n += moving_selection_offset.y;111} else if (scaling_selection) {112offset_n += -scaling_selection_offset.x + (offset_n - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);113height_n += -scaling_selection_offset.y + (height_n - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);114}115}116117if (moving_inserted_key && moving_selection_from_track == p_track) {118if (moving_selection_from_key == i) {119Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(p_track, i);120if (handle_mode != Animation::HANDLE_MODE_FREE) {121float offset_p = offset;122float height_p = height;123if (E->prev()) {124int i_p = E->prev()->get();125offset_p = animation->track_get_key_time(p_track, i_p);126height_p = animation->bezier_track_get_key_value(p_track, i_p);127}128129animation->bezier_track_calculate_handles(offset, offset_p, height_p, offset_n, height_n, handle_mode, Animation::HANDLE_SET_MODE_AUTO, nullptr, &out_handle);130}131} else if (moving_selection_from_key == i_n) {132Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(p_track, i_n);133if (handle_mode != Animation::HANDLE_MODE_FREE) {134float offset_nn = offset_n;135float height_nn = height_n;136if (E->next()->next()) {137int i_nn = E->next()->next()->get();138offset_nn = animation->track_get_key_time(p_track, i_nn);139height_nn = animation->bezier_track_get_key_value(p_track, i_nn);140}141142animation->bezier_track_calculate_handles(offset_n, offset, height, offset_nn, height_nn, handle_mode, Animation::HANDLE_SET_MODE_AUTO, &in_handle, nullptr);143}144}145}146147out_handle += Vector2(offset, height);148in_handle += Vector2(offset_n, height_n);149150Vector2 start(offset, height);151Vector2 end(offset_n, height_n);152153int from_x = (offset - timeline->get_value()) * scale + limit;154int point_start = from_x;155int to_x = (offset_n - timeline->get_value()) * scale + limit;156int point_end = to_x;157158if (from_x > right_limit) { // Not visible.159continue;160}161162if (to_x < limit) { // Not visible.163continue;164}165166from_x = MAX(from_x, limit);167to_x = MIN(to_x, right_limit);168169Vector<Vector2> lines;170171Vector2 prev_pos;172173for (int j = from_x; j <= to_x; j++) {174float t = (j - limit) / scale + timeline->get_value();175176float h;177178if (j == point_end) {179h = end.y; // Make sure it always connects.180} else if (j == point_start) {181h = start.y; // Make sure it always connects.182} else { // Custom interpolation, used because it needs to show paths affected by moving the selection or handles.183int iterations = 10;184float low = 0;185float high = 1;186187// Narrow high and low as much as possible.188for (int k = 0; k < iterations; k++) {189float middle = (low + high) / 2.0;190191Vector2 interp = start.bezier_interpolate(out_handle, in_handle, end, middle);192193if (interp.x < t) {194low = middle;195} else {196high = middle;197}198}199200// Interpolate the result.201Vector2 low_pos = start.bezier_interpolate(out_handle, in_handle, end, low);202Vector2 high_pos = start.bezier_interpolate(out_handle, in_handle, end, high);203204float c = (t - low_pos.x) / (high_pos.x - low_pos.x);205206h = low_pos.lerp(high_pos, c).y;207}208209h = _bezier_h_to_pixel(h);210211Vector2 pos(j, h);212213if (j > from_x) {214lines.push_back(prev_pos);215lines.push_back(pos);216}217prev_pos = pos;218}219220if (lines.size() >= 2) {221draw_multiline(lines, p_color, Math::round(EDSCALE), true);222}223}224}225226void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right) {227Vector2 from = p_from;228Vector2 to = p_to;229230if (from.x == to.x && from.y == to.y) {231return;232}233if (to.x < from.x) {234SWAP(to, from);235}236237if (to.x < p_clip_left) {238return;239}240241if (from.x > p_clip_right) {242return;243}244245if (to.x > p_clip_right) {246float c = (p_clip_right - from.x) / (to.x - from.x);247to = from.lerp(to, c);248}249250if (from.x < p_clip_left) {251float c = (p_clip_left - from.x) / (to.x - from.x);252from = from.lerp(to, c);253}254255draw_line(from, to, p_color, Math::round(EDSCALE), true);256}257258void AnimationBezierTrackEdit::_notification(int p_what) {259switch (p_what) {260case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {261if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {262panner->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")));263panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));264}265} break;266267case NOTIFICATION_ENTER_TREE: {268panner->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")));269panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));270} break;271case NOTIFICATION_THEME_CHANGED: {272bezier_icon = get_editor_theme_icon(SNAME("KeyBezierPoint"));273bezier_handle_icon = get_editor_theme_icon(SNAME("KeyBezierHandle"));274selected_icon = get_editor_theme_icon(SNAME("KeyBezierSelected"));275} break;276277case NOTIFICATION_ACCESSIBILITY_UPDATE: {278RID ae = get_accessibility_element();279ERR_FAIL_COND(ae.is_null());280281//TODO282DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);283DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "Animation bezier track editor")));284} break;285286case NOTIFICATION_DRAW: {287if (animation.is_null()) {288return;289}290291int limit = timeline->get_name_limit();292293const Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));294const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));295const Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));296297const Color h_line_color = get_theme_color(SNAME("h_line_color"), SNAME("AnimationBezierTrackEdit"));298const Color v_line_color = get_theme_color(SNAME("v_line_color"), SNAME("AnimationBezierTrackEdit"));299const Color focus_color = get_theme_color(SNAME("focus_color"), SNAME("AnimationBezierTrackEdit"));300const Color track_focus_color = get_theme_color(SNAME("track_focus_color"), SNAME("AnimationBezierTrackEdit"));301302const int h_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit"));303const int v_separation = get_theme_constant(SNAME("h_separation"), SNAME("AnimationBezierTrackEdit"));304305if (has_focus()) {306draw_rect(Rect2(Point2(), get_size()), focus_color, false, Math::round(EDSCALE));307}308309draw_line(Point2(limit, 0), Point2(limit, get_size().height), v_line_color, Math::round(EDSCALE));310311int right_limit = get_size().width;312313track_v_scroll_max = v_separation;314315int vofs = v_separation + track_v_scroll;316int margin = 0;317318RBMap<int, Color> subtrack_colors;319Color selected_track_color;320subtracks.clear();321subtrack_icons.clear();322323RBMap<String, Vector<int>> track_indices;324int track_count = animation->get_track_count();325for (int i = 0; i < track_count; ++i) {326if (!_is_track_displayed(i)) {327continue;328}329330String base_path = String(animation->track_get_path(i));331int end = base_path.find_char(':');332if (end != -1) {333base_path = base_path.substr(0, end + 1);334}335Vector<int> indices = track_indices.has(base_path) ? track_indices[base_path] : Vector<int>();336indices.push_back(i);337track_indices[base_path] = indices;338}339340for (const KeyValue<String, Vector<int>> &E : track_indices) {341String base_path = E.key;342343Vector<int> tracks = E.value;344345// Names and icon.346{347NodePath path = animation->track_get_path(tracks[0]);348349Node *node = nullptr;350351if (root && root->has_node(path)) {352node = root->get_node(path);353}354355String text;356357if (node) {358int ofs = 0;359360Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node");361362text = node->get_name();363ofs += h_separation;364365TextLine text_buf = TextLine(text, font, font_size);366text_buf.set_width(limit - ofs - icon->get_width() - h_separation);367368int h = MAX(text_buf.get_size().y, icon->get_height());369370draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2.0));371ofs += icon->get_width() + h_separation;372373margin = icon->get_width();374375Vector2 string_pos = Point2(ofs, vofs);376string_pos = string_pos.floor();377text_buf.draw(get_canvas_item(), string_pos, color);378379vofs += h + v_separation;380track_v_scroll_max += h + v_separation;381}382}383384const Color dc = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor));385386Ref<Texture2D> remove = get_editor_theme_icon(SNAME("Remove"));387float remove_hpos = limit - h_separation - remove->get_width();388389Ref<Texture2D> lock = get_editor_theme_icon(SNAME("Lock"));390Ref<Texture2D> unlock = get_editor_theme_icon(SNAME("Unlock"));391float lock_hpos = remove_hpos - h_separation - lock->get_width();392393Ref<Texture2D> visibility_visible = get_editor_theme_icon(SNAME("GuiVisibilityVisible"));394Ref<Texture2D> visibility_hidden = get_editor_theme_icon(SNAME("GuiVisibilityHidden"));395float visibility_hpos = lock_hpos - h_separation - visibility_visible->get_width();396397Ref<Texture2D> solo = get_editor_theme_icon(SNAME("AudioBusSolo"));398float solo_hpos = visibility_hpos - h_separation - solo->get_width();399400float buttons_width = remove->get_width() + lock->get_width() + visibility_visible->get_width() + solo->get_width() + h_separation * 3;401402for (int i = 0; i < tracks.size(); ++i) {403// Related track titles.404405int current_track = tracks[i];406407String path = String(animation->track_get_path(current_track));408path = path.replace_first(base_path, "");409410Color cc = color;411TextLine text_buf = TextLine(path, font, font_size);412text_buf.set_width(limit - margin - buttons_width - h_separation * 2);413414Rect2 rect = Rect2(margin, vofs, solo_hpos - h_separation - solo->get_width(), text_buf.get_size().y + v_separation);415416cc.a *= 0.7;417float h;418if (path.ends_with(":x")) {419h = 0;420} else if (path.ends_with(":y")) {421h = 0.33f;422} else if (path.ends_with(":z")) {423h = 0.66f;424} else {425uint32_t hash = path.hash();426hash = ((hash >> 16) ^ hash) * 0x45d9f3b;427hash = ((hash >> 16) ^ hash) * 0x45d9f3b;428hash = (hash >> 16) ^ hash;429h = (hash % 65535) / 65536.0;430}431432if (current_track != selected_track) {433Color track_color;434if (locked_tracks.has(current_track)) {435track_color.set_hsv(h, 0, 0.4);436} else {437track_color.set_hsv(h, 0.2, 0.8);438}439track_color.a = 0.5;440draw_rect(Rect2(0, vofs, margin - h_separation, text_buf.get_size().y * 0.8), track_color);441subtrack_colors[current_track] = track_color;442443subtracks[current_track] = rect;444} else {445draw_rect(rect, track_focus_color);446if (locked_tracks.has(selected_track)) {447selected_track_color.set_hsv(h, 0.0, 0.4);448} else {449selected_track_color.set_hsv(h, 0.8, 0.8);450}451}452453Vector2 string_pos = Point2(margin + h_separation, vofs);454text_buf.draw(get_canvas_item(), string_pos, cc);455456float icon_start_height = vofs + rect.size.y / 2.0;457Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2.0, remove->get_width(), remove->get_height());458if (read_only) {459draw_texture(remove, remove_rect.position, dc);460} else {461draw_texture(remove, remove_rect.position);462}463464Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2.0, lock->get_width(), lock->get_height());465if (locked_tracks.has(current_track)) {466draw_texture(lock, lock_rect.position);467} else {468draw_texture(unlock, lock_rect.position);469}470471Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visibility_visible->get_height() / 2.0, visibility_visible->get_width(), visibility_visible->get_height());472if (hidden_tracks.has(current_track)) {473draw_texture(visibility_hidden, visible_rect.position);474} else {475draw_texture(visibility_visible, visible_rect.position);476}477478Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2.0, solo->get_width(), solo->get_height());479draw_texture(solo, solo_rect.position);480481RBMap<int, Rect2> track_icons;482track_icons[REMOVE_ICON] = remove_rect;483track_icons[LOCK_ICON] = lock_rect;484track_icons[VISIBILITY_ICON] = visible_rect;485track_icons[SOLO_ICON] = solo_rect;486487subtrack_icons[current_track] = track_icons;488489vofs += text_buf.get_size().y + v_separation;490track_v_scroll_max += text_buf.get_size().y + v_separation;491}492}493494const Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));495496// Guides.497{498float min_left_scale = font->get_height(font_size) + v_separation;499500float scale = (min_left_scale * 2) * timeline_v_zoom;501float step = Math::pow(10.0, Math::round(Math::log(scale / 5.0) / Math::log(10.0))) * 5.0;502scale = Math::snapped(scale, step);503504while (scale / timeline_v_zoom < min_left_scale * 2) {505scale += step;506}507508bool first = true;509int prev_iv = 0;510for (int i = font->get_height(font_size); i < get_size().height; i++) {511float ofs = get_size().height / 2.0 - i;512ofs *= timeline_v_zoom;513ofs += timeline_v_scroll;514515int iv = int(ofs / scale);516if (ofs < 0) {517iv -= 1;518}519if (!first && iv != prev_iv) {520Color lc = h_line_color;521lc.a *= 0.5;522draw_line(Point2(limit, i), Point2(right_limit, i), lc, Math::round(EDSCALE));523Color c = color;524c.a *= 0.5;525draw_string(font, Point2(limit + 8, i - 2), TS->format_number(rtos(Math::snapped((iv + 1) * scale, step))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, c);526}527528first = false;529prev_iv = iv;530}531}532533// Draw other curves.534{535float scale = timeline->get_zoom_scale();536Ref<Texture2D> point = get_editor_theme_icon(SNAME("KeyValue"));537for (const KeyValue<int, Color> &E : subtrack_colors) {538if (hidden_tracks.has(E.key)) {539continue;540}541_draw_track(E.key, E.value);542543for (int i = 0; i < animation->track_get_key_count(E.key); i++) {544float offset = animation->track_get_key_time(E.key, i);545float value = animation->bezier_track_get_key_value(E.key, i);546547Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));548549if (pos.x >= limit && pos.x <= right_limit) {550draw_texture(point, pos - point->get_size() / 2.0, E.value);551}552}553}554555if (track_count > 0 && !hidden_tracks.has(selected_track)) {556// Draw edited curve.557_draw_track(selected_track, selected_track_color);558}559}560561const bool draw_selection_handles = selection.size() > 1;562LocalVector<Point2> selected_pos;563564// Draw editor handles.565{566edit_points.clear();567float scale = timeline->get_zoom_scale();568569for (int i = 0; i < track_count; ++i) {570bool draw_track = _is_track_curves_displayed(i) && !locked_tracks.has(i);571if (!draw_selection_handles && !draw_track) {572continue;573}574575int key_count = animation->track_get_key_count(i);576for (int j = 0; j < key_count; ++j) {577float offset = animation->track_get_key_time(i, j);578float value = animation->bezier_track_get_key_value(i, j);579bool is_selected = selection.has(IntPair(i, j));580581if (is_selected) {582if (moving_selection) {583offset += moving_selection_offset.x;584value += moving_selection_offset.y;585} else if (scaling_selection) {586offset += -scaling_selection_offset.x + (offset - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);587value += -scaling_selection_offset.y + (value - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);588}589}590591Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value));592593if (draw_selection_handles && is_selected) {594selected_pos.push_back(pos);595}596597if (!draw_track) {598continue;599}600601Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);602Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);603604if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {605in_vec = moving_handle_left;606}607608if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {609out_vec = moving_handle_right;610}611612if (moving_inserted_key && moving_selection_from_key == j) {613Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(i, j);614if (handle_mode != Animation::HANDLE_MODE_FREE) {615int key_prev = 0;616int key_next = moving_selection_from_key;617for (int k = 0; k < key_count; k++) {618if (k == moving_selection_from_key) {619continue;620}621622if (animation->track_get_key_time(i, k) < offset) {623key_prev = k;624} else {625key_next = k;626break;627}628}629630float prev_time = offset;631float prev_value = value;632if (key_prev != moving_selection_from_key) {633prev_time = animation->track_get_key_time(i, key_prev);634prev_value = animation->bezier_track_get_key_value(i, key_prev);635}636637float next_time = offset;638float next_value = value;639if (key_next != moving_selection_from_key) {640next_time = animation->track_get_key_time(i, key_next);641next_value = animation->bezier_track_get_key_value(i, key_next);642}643644animation->bezier_track_calculate_handles(offset, prev_time, prev_value, next_time, next_value, handle_mode, Animation::HANDLE_SET_MODE_AUTO, &in_vec, &out_vec);645}646}647648Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y));649Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y));650651if (i == selected_track || is_selected) {652_draw_line_clipped(pos, pos_in, accent, limit, right_limit);653_draw_line_clipped(pos, pos_out, accent, limit, right_limit);654}655656EditPoint ep;657ep.track = i;658ep.key = j;659if (pos.x >= limit && pos.x <= right_limit) {660ep.point_rect.position = (pos - bezier_icon->get_size() / 2.0).floor();661ep.point_rect.size = bezier_icon->get_size();662if (is_selected) {663draw_texture(selected_icon, ep.point_rect.position);664draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.0001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);665draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);666} else {667Color track_color = Color(1, 1, 1, 1);668if (i != selected_track) {669track_color = subtrack_colors[i];670}671draw_texture(bezier_icon, ep.point_rect.position, track_color);672}673ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5);674}675ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5);676677if (i == selected_track || is_selected) {678if (animation->bezier_track_get_key_handle_mode(i, j) != Animation::HANDLE_MODE_LINEAR) {679if (pos_in.x >= limit && pos_in.x <= right_limit) {680ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2.0).floor();681ep.in_rect.size = bezier_handle_icon->get_size();682draw_texture(bezier_handle_icon, ep.in_rect.position);683ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5);684}685if (pos_out.x >= limit && pos_out.x <= right_limit) {686ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2.0).floor();687ep.out_rect.size = bezier_handle_icon->get_size();688draw_texture(bezier_handle_icon, ep.out_rect.position);689ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5);690}691}692}693if (!locked_tracks.has(i)) {694edit_points.push_back(ep);695}696}697}698699for (int i = 0; i < edit_points.size(); ++i) {700if (edit_points[i].track == selected_track) {701EditPoint ep = edit_points[i];702edit_points.remove_at(i);703edit_points.insert(0, ep);704}705}706}707708selection_rect = Rect2();709selection_handles_rect = Rect2();710// Draw scale handles.711if (draw_selection_handles) {712selection_rect.position = selected_pos[0];713selected_pos.remove_at(0);714for (const Point2 &pos : selected_pos) {715selection_rect = selection_rect.expand(pos);716}717718const int outer_ofs = Math::round(12 * EDSCALE);719const int inner_ofs = Math::round(outer_ofs / 2.0);720721// Draw horizontal handles.722if (selection_rect.size.height > CMP_EPSILON) {723_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);724_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);725}726// Draw vertical handles.727if (selection_rect.size.width > CMP_EPSILON) {728_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);729_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);730}731732selection_handles_rect.position = selection_rect.position - Vector2(outer_ofs, outer_ofs);733selection_handles_rect.size = selection_rect.size + Vector2(outer_ofs, outer_ofs) * 2;734}735736if (box_selecting) {737Vector2 bs_from = box_selection_from;738Vector2 bs_to = box_selection_to;739if (bs_from.x > bs_to.x) {740SWAP(bs_from.x, bs_to.x);741}742if (bs_from.y > bs_to.y) {743SWAP(bs_from.y, bs_to.y);744}745draw_rect(746Rect2(bs_from, bs_to - bs_from),747get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));748draw_rect(749Rect2(bs_from, bs_to - bs_from),750get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),751false,752Math::round(EDSCALE));753}754} break;755}756}757758// Check if a track is displayed in the bezier editor (track type = bezier and track not filtered).759bool AnimationBezierTrackEdit::_is_track_displayed(int p_track_index) {760if (animation->track_get_type(p_track_index) != Animation::TrackType::TYPE_BEZIER) {761return false;762}763764if (is_filtered) {765String path = String(animation->track_get_path(p_track_index));766if (root && root->has_node(path)) {767Node *node = root->get_node(path);768if (!node) {769return false; // No node, no filter.770}771if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {772return false; // Skip track due to not selected.773}774}775}776777return true;778}779780// Check if the curves for a track are displayed in the editor (not hidden). Includes the check on the track visibility.781bool AnimationBezierTrackEdit::_is_track_curves_displayed(int p_track_index) {782// Is the track is visible in the editor?783if (!_is_track_displayed(p_track_index)) {784return false;785}786787// And curves visible?788if (hidden_tracks.has(p_track_index)) {789return false;790}791792return true;793}794795Ref<Animation> AnimationBezierTrackEdit::get_animation() const {796return animation;797}798799void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only) {800animation = p_animation;801read_only = p_read_only;802selected_track = p_track;803queue_redraw();804}805806Size2 AnimationBezierTrackEdit::get_minimum_size() const {807return Vector2(1, 1);808}809810Control::CursorShape AnimationBezierTrackEdit::get_cursor_shape(const Point2 &p_pos) const {811// Box selecting or moving a handle812if (box_selecting || Math::abs(moving_handle) == 1) {813return get_default_cursor_shape();814}815// Hovering a handle816if (!read_only) {817for (const EditPoint &edit_point : edit_points) {818if (edit_point.in_rect.has_point(p_pos) || edit_point.out_rect.has_point(p_pos)) {819return get_default_cursor_shape();820}821}822}823// Currently box scaling824if (scaling_selection) {825if (scaling_selection_handles == Vector2i(1, 1) || scaling_selection_handles == Vector2i(-1, -1)) {826return CURSOR_FDIAGSIZE;827} else if (scaling_selection_handles == Vector2i(1, -1) || scaling_selection_handles == Vector2i(-1, 1)) {828return CURSOR_BDIAGSIZE;829} else if (abs(scaling_selection_handles.x) == 1) {830return CURSOR_HSIZE;831} else if (abs(scaling_selection_handles.y) == 1) {832return CURSOR_VSIZE;833}834}835// Hovering the scaling box836const Vector2i rel_pos = p_pos - selection_rect.position;837if (selection_handles_rect.has_point(p_pos)) {838if ((rel_pos.x < 0 && rel_pos.y < 0) || (rel_pos.x > selection_rect.size.width && rel_pos.y > selection_rect.size.height)) {839return CURSOR_FDIAGSIZE;840} else if ((rel_pos.x < 0 && rel_pos.y > selection_rect.size.height) || (rel_pos.x > selection_rect.size.width && rel_pos.y < 0)) {841return CURSOR_BDIAGSIZE;842} else if (rel_pos.x < 0 || rel_pos.x > selection_rect.size.width) {843return CURSOR_HSIZE;844} else if (rel_pos.y < 0 || rel_pos.y > selection_rect.size.height) {845return CURSOR_VSIZE;846}847return CURSOR_MOVE;848}849return get_default_cursor_shape();850}851852void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) {853timeline = p_timeline;854timeline->connect("zoom_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed));855timeline->connect("name_limit_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed));856}857858void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) {859editor = p_editor;860connect("clear_selection", callable_mp(editor, &AnimationTrackEditor::_clear_selection).bind(false));861connect("select_key", callable_mp(editor, &AnimationTrackEditor::_key_selected), CONNECT_DEFERRED);862connect("deselect_key", callable_mp(editor, &AnimationTrackEditor::_key_deselected), CONNECT_DEFERRED);863}864865void AnimationBezierTrackEdit::_play_position_draw() {866if (animation.is_null() || play_position_pos < 0) {867return;868}869870float scale = timeline->get_zoom_scale();871int h = get_size().height;872873int limit = timeline->get_name_limit();874875int px = (-timeline->get_value() + play_position_pos) * scale + limit;876877if (px >= limit && px < (get_size().width)) {878const Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));879play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));880}881}882883void AnimationBezierTrackEdit::set_play_position(real_t p_pos) {884play_position_pos = p_pos;885play_position->queue_redraw();886}887888void AnimationBezierTrackEdit::update_play_position() {889play_position->queue_redraw();890}891892void AnimationBezierTrackEdit::set_root(Node *p_root) {893root = p_root;894}895896void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {897is_filtered = p_filtered;898if (animation.is_null()) {899return;900}901String base_path = String(animation->track_get_path(selected_track));902if (is_filtered) {903if (root && root->has_node(base_path)) {904Node *node = root->get_node(base_path);905if (!node || !EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {906for (int i = 0; i < animation->get_track_count(); ++i) {907if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) {908continue;909}910911base_path = String(animation->track_get_path(i));912if (root && root->has_node(base_path)) {913node = root->get_node(base_path);914if (!node) {915continue; // No node, no filter.916}917if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) {918continue; // Skip track due to not selected.919}920921set_animation_and_track(animation, i, read_only);922break;923}924}925}926}927}928queue_redraw();929}930931void AnimationBezierTrackEdit::auto_fit_vertically() {932int track_count = animation->get_track_count();933real_t minimum_value = Math::INF;934real_t maximum_value = -Math::INF;935936int nb_track_visible = 0;937for (int i = 0; i < track_count; ++i) {938if (!_is_track_curves_displayed(i) || locked_tracks.has(i)) {939continue;940}941942int key_count = animation->track_get_key_count(i);943944for (int j = 0; j < key_count; ++j) {945real_t value = animation->bezier_track_get_key_value(i, j);946947minimum_value = MIN(value, minimum_value);948maximum_value = MAX(value, maximum_value);949950// We also want to includes the handles...951Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);952Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);953954minimum_value = MIN(value + in_vec.y, minimum_value);955maximum_value = MAX(value + in_vec.y, maximum_value);956minimum_value = MIN(value + out_vec.y, minimum_value);957maximum_value = MAX(value + out_vec.y, maximum_value);958}959960nb_track_visible++;961}962963if (nb_track_visible == 0) {964// No visible track... we will not adjust the vertical zoom965return;966}967968if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {969_zoom_vertically(minimum_value, maximum_value);970queue_redraw();971}972}973974void AnimationBezierTrackEdit::_zoom_vertically(real_t p_minimum_value, real_t p_maximum_value) {975real_t target_height = p_maximum_value - p_minimum_value;976if (target_height <= CMP_EPSILON) {977timeline_v_scroll = p_maximum_value;978return;979}980981timeline_v_scroll = (p_maximum_value + p_minimum_value) / 2.0;982timeline_v_zoom = target_height / ((get_size().height - timeline->get_size().height) * 0.9);983}984985void AnimationBezierTrackEdit::_zoom_changed() {986queue_redraw();987play_position->queue_redraw();988}989990void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) {991if (locked_tracks.has(p_track)) {992locked_tracks.erase(p_track);993}994995Vector<int> updated_locked_tracks;996for (const int &E : locked_tracks) {997updated_locked_tracks.push_back(E);998}999locked_tracks.clear();1000for (int i = 0; i < updated_locked_tracks.size(); ++i) {1001if (updated_locked_tracks[i] > p_track) {1002locked_tracks.insert(updated_locked_tracks[i] - 1);1003} else {1004locked_tracks.insert(updated_locked_tracks[i]);1005}1006}1007}10081009void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) {1010if (hidden_tracks.has(p_track)) {1011hidden_tracks.erase(p_track);1012}10131014Vector<int> updated_hidden_tracks;1015for (const int &E : hidden_tracks) {1016updated_hidden_tracks.push_back(E);1017}1018hidden_tracks.clear();1019for (int i = 0; i < updated_hidden_tracks.size(); ++i) {1020if (updated_hidden_tracks[i] > p_track) {1021hidden_tracks.insert(updated_hidden_tracks[i] - 1);1022} else {1023hidden_tracks.insert(updated_hidden_tracks[i]);1024}1025}1026}10271028String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const {1029return Control::get_tooltip(p_pos);1030}10311032void AnimationBezierTrackEdit::_clear_selection() {1033selection.clear();1034emit_signal(SNAME("clear_selection"));1035queue_redraw();1036}10371038void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto) {1039EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1040undo_redo->create_action(TTR("Update Selected Key Handles"), UndoRedo::MERGE_DISABLE, animation.ptr());1041for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1042const IntPair track_key_pair = E->get();1043undo_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);1044undo_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));1045undo_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));1046undo_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);1047}1048AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();1049if (ape) {1050undo_redo->add_do_method(ape, "_animation_update_key_frame");1051undo_redo->add_undo_method(ape, "_animation_update_key_frame");1052}1053undo_redo->commit_action();1054}10551056void AnimationBezierTrackEdit::_clear_selection_for_anim(const Ref<Animation> &p_anim) {1057if (!(animation == p_anim) || !is_visible()) {1058return;1059}1060_clear_selection();1061}10621063void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos, bool p_single) {1064if (!(animation == p_anim) || !is_visible()) {1065return;1066}10671068int idx = animation->track_find_key(p_track, p_pos, Animation::FIND_MODE_APPROX);1069ERR_FAIL_COND(idx < 0);10701071selection.insert(IntPair(p_track, idx));1072emit_signal(SNAME("select_key"), idx, p_single, p_track);1073queue_redraw();1074}10751076void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {1077ERR_FAIL_COND(p_event.is_null());10781079if (panner->gui_input(p_event)) {1080accept_event();1081return;1082}10831084if (p_event->is_pressed()) {1085if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {1086if (!read_only) {1087duplicate_selected_keys(-1.0, false);1088}1089accept_event();1090}1091if (ED_IS_SHORTCUT("animation_editor/cut_selected_keys", p_event)) {1092if (!read_only) {1093copy_selected_keys(true);1094}1095accept_event();1096}1097if (ED_IS_SHORTCUT("animation_editor/copy_selected_keys", p_event)) {1098if (!read_only) {1099copy_selected_keys(false);1100}1101accept_event();1102}1103if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {1104if (!read_only) {1105paste_keys(-1.0, false);1106}1107accept_event();1108}1109if (ED_IS_SHORTCUT("animation_editor/delete_selection", p_event)) {1110if (!read_only) {1111delete_selection();1112}1113accept_event();1114}1115}11161117Ref<InputEventKey> key_press = p_event;11181119if (key_press.is_valid() && key_press->is_pressed()) {1120if (ED_IS_SHORTCUT("animation_bezier_editor/focus", p_event)) {1121SelectionSet focused_keys;1122if (selection.is_empty()) {1123for (int i = 0; i < edit_points.size(); ++i) {1124IntPair key_pair = IntPair(edit_points[i].track, edit_points[i].key);1125focused_keys.insert(key_pair);1126}1127} else {1128for (const IntPair &E : selection) {1129focused_keys.insert(E);1130if (E.second > 0) {1131IntPair previous_key = IntPair(E.first, E.second - 1);1132focused_keys.insert(previous_key);1133}1134if (E.second < animation->track_get_key_count(E.first) - 1) {1135IntPair next_key = IntPair(E.first, E.second + 1);1136focused_keys.insert(next_key);1137}1138}1139}1140if (focused_keys.is_empty()) {1141accept_event();1142return;1143}11441145real_t minimum_time = Math::INF;1146real_t maximum_time = -Math::INF;1147real_t minimum_value = Math::INF;1148real_t maximum_value = -Math::INF;11491150for (const IntPair &E : focused_keys) {1151IntPair key_pair = E;11521153real_t time = animation->track_get_key_time(key_pair.first, key_pair.second);1154real_t value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second);11551156minimum_time = MIN(time, minimum_time);1157maximum_time = MAX(time, maximum_time);1158minimum_value = MIN(value, minimum_value);1159maximum_value = MAX(value, maximum_value);1160}11611162float width = get_size().width - timeline->get_name_limit() - timeline->get_buttons_width();1163float padding = width * 0.1;1164float desired_scale = (width - padding / 2.0) / (maximum_time - minimum_time);1165minimum_time = MAX(0, minimum_time - (padding / 2.0) / desired_scale);11661167float zv = Math::pow(100 / desired_scale, 0.125f);1168if (zv < 1) {1169zv = Math::pow(desired_scale / 100, 0.125f) - 1;1170zv = 1 - zv;1171}1172float zoom_value = timeline->get_zoom()->get_max() - zv;11731174if (Math::is_finite(minimum_time) && Math::is_finite(maximum_time) && maximum_time - minimum_time > CMP_EPSILON) {1175timeline->get_zoom()->set_value(zoom_value);1176callable_mp((Range *)timeline, &Range::set_value).call_deferred(minimum_time);1177}11781179if (Math::is_finite(minimum_value) && Math::is_finite(maximum_value)) {1180_zoom_vertically(minimum_value, maximum_value);1181}11821183queue_redraw();1184accept_event();1185return;1186} else if (ED_IS_SHORTCUT("animation_bezier_editor/select_all_keys", p_event)) {1187for (int i = 0; i < edit_points.size(); ++i) {1188_select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), i == 0);1189}11901191queue_redraw();1192accept_event();1193return;1194} else if (ED_IS_SHORTCUT("animation_bezier_editor/deselect_all_keys", p_event)) {1195selection.clear();1196emit_signal(SNAME("clear_selection"));11971198queue_redraw();1199accept_event();1200return;1201}1202}12031204Ref<InputEventMouseButton> mb = p_event;1205int limit = timeline->get_name_limit();1206if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {1207menu_insert_key = mb->get_position();1208if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) {1209if (!read_only) {1210Vector2 popup_pos = get_screen_position() + mb->get_position();12111212bool selected = _try_select_at_ui_pos(mb->get_position(), mb->is_shift_pressed(), false);12131214menu->clear();1215menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);1216if (selected || selection.size()) {1217menu->add_separator();1218menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE);1219menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCut")), TTR("Cut Selected Key(s)"), MENU_KEY_CUT);1220menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCopy")), TTR("Copy Selected Key(s)"), MENU_KEY_COPY);1221}12221223if (editor->is_key_clipboard_active()) {1224menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTR("Paste Key(s)"), MENU_KEY_PASTE);1225}12261227if (selected || selection.size()) {1228menu->add_separator();1229menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);1230menu->add_separator();1231menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesFree")), TTR("Make Handles Free"), MENU_KEY_SET_HANDLE_FREE);1232menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesLinear")), TTR("Make Handles Linear"), MENU_KEY_SET_HANDLE_LINEAR);1233menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesBalanced")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED);1234menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesMirror")), TTR("Make Handles Mirrored"), MENU_KEY_SET_HANDLE_MIRRORED);1235menu->add_separator();1236menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesBalanced")), TTR("Make Handles Balanced (Auto Tangent)"), MENU_KEY_SET_HANDLE_AUTO_BALANCED);1237menu->add_icon_item(get_editor_theme_icon(SNAME("BezierHandlesMirror")), TTR("Make Handles Mirrored (Auto Tangent)"), MENU_KEY_SET_HANDLE_AUTO_MIRRORED);1238}12391240if (menu->get_item_count()) {1241menu->reset_size();1242menu->set_position(popup_pos);1243menu->popup();1244}1245}1246}1247}12481249if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1250Point2 pos = mb->get_position();1251bool no_mod_key_pressed = !mb->is_alt_pressed() && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed();1252if (mb->is_double_click() && !moving_selection && no_mod_key_pressed) {1253int x = pos.x - timeline->get_name_limit();1254float ofs = x / timeline->get_zoom_scale() + timeline->get_value();1255emit_signal(SNAME("timeline_changed"), ofs, false);1256}1257for (const KeyValue<int, Rect2> &E : subtracks) {1258if (E.value.has_point(mb->get_position())) {1259if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) {1260set_animation_and_track(animation, E.key, read_only);1261_clear_selection();1262}1263return;1264}1265}12661267for (const KeyValue<int, RBMap<int, Rect2>> &E : subtrack_icons) {1268int track = E.key;1269RBMap<int, Rect2> track_icons = E.value;1270for (const KeyValue<int, Rect2> &I : track_icons) {1271if (I.value.has_point(mb->get_position())) {1272if (I.key == REMOVE_ICON) {1273if (!read_only) {1274EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1275undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr());12761277undo_redo->add_do_method(this, "_update_locked_tracks_after", track);1278undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);12791280undo_redo->add_do_method(animation.ptr(), "remove_track", track);12811282undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track);1283undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));12841285for (int i = 0; i < animation->track_get_key_count(track); ++i) {1286undo_redo->add_undo_method(1287this,1288"_bezier_track_insert_key_at_anim",1289animation,1290track,1291animation->track_get_key_time(track, i),1292animation->bezier_track_get_key_value(track, i),1293animation->bezier_track_get_key_in_handle(track, i),1294animation->bezier_track_get_key_out_handle(track, i),1295animation->bezier_track_get_key_handle_mode(track, i));1296}12971298undo_redo->commit_action();12991300selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1);1301}1302return;1303} else if (I.key == LOCK_ICON) {1304if (locked_tracks.has(track)) {1305locked_tracks.erase(track);1306} else {1307locked_tracks.insert(track);1308if (selected_track == track) {1309for (int i = 0; i < animation->get_track_count(); ++i) {1310if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {1311set_animation_and_track(animation, i, read_only);1312break;1313}1314}1315}1316}1317queue_redraw();1318return;1319} else if (I.key == VISIBILITY_ICON) {1320if (hidden_tracks.has(track)) {1321hidden_tracks.erase(track);1322} else {1323hidden_tracks.insert(track);1324if (selected_track == track) {1325for (int i = 0; i < animation->get_track_count(); ++i) {1326if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {1327set_animation_and_track(animation, i, read_only);1328break;1329}1330}1331}1332}13331334Vector<int> visible_tracks;1335for (int i = 0; i < animation->get_track_count(); ++i) {1336if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {1337visible_tracks.push_back(i);1338}1339}13401341if (visible_tracks.size() == 1) {1342solo_track = visible_tracks[0];1343} else {1344solo_track = -1;1345}13461347queue_redraw();1348return;1349} else if (I.key == SOLO_ICON) {1350if (solo_track == track) {1351solo_track = -1;13521353hidden_tracks.clear();1354} else {1355if (hidden_tracks.has(track)) {1356hidden_tracks.erase(track);1357}1358for (int i = 0; i < animation->get_track_count(); ++i) {1359if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {1360if (i != track && !hidden_tracks.has(i)) {1361hidden_tracks.insert(i);1362}1363}1364}13651366set_animation_and_track(animation, track, read_only);1367solo_track = track;1368}1369queue_redraw();1370return;1371}1372return;1373}1374}1375}13761377// Check this first, to allow manipulating key handles while ignoring keyframes before scaling/moving.1378bool inside_selection_handles_rect = !read_only && selection_handles_rect.has_point(mb->get_position());13791380// First, check keyframe.1381// Command/Control makes it ignore the keyframe, so control point editors can be force-edited.1382if (!inside_selection_handles_rect && !mb->is_command_or_control_pressed()) {1383if (_try_select_at_ui_pos(mb->get_position(), mb->is_shift_pressed(), true)) {1384return;1385}1386}1387// Second, check key handles.1388for (int i = 0; i < edit_points.size(); i++) {1389if (!read_only) {1390if (edit_points[i].in_rect.has_point(mb->get_position())) {1391moving_handle = -1;1392moving_handle_key = edit_points[i].key;1393moving_handle_track = edit_points[i].track;1394moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);1395moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);1396queue_redraw();1397return;1398}13991400if (edit_points[i].out_rect.has_point(mb->get_position())) {1401moving_handle = 1;1402moving_handle_key = edit_points[i].key;1403moving_handle_track = edit_points[i].track;1404moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);1405moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);1406queue_redraw();1407return;1408}1409}1410}14111412// Box scaling/movement.1413if (inside_selection_handles_rect) {1414const Vector2i rel_pos = mb->get_position() - selection_rect.position;1415scaling_selection_handles = Vector2i();14161417// Check which scaling handles are available.1418if (selection_rect.size.width > CMP_EPSILON) {1419if (rel_pos.x <= 0) {1420scaling_selection_handles.x = -1;1421} else if (rel_pos.x >= selection_rect.size.width) {1422scaling_selection_handles.x = 1;1423}1424}1425if (selection_rect.size.height > CMP_EPSILON) {1426if (rel_pos.y <= 0) {1427scaling_selection_handles.y = -1;1428} else if (rel_pos.y >= selection_rect.size.height) {1429scaling_selection_handles.y = 1;1430}1431}14321433if (scaling_selection_handles != Vector2i()) {1434scaling_selection = true;14351436const float time = ((selection_rect.position.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();1437const float h = (get_size().height / 2.0 - selection_rect.position.y) * timeline_v_zoom + timeline_v_scroll;1438scaling_selection_pivot = Point2(time, h);14391440return;1441}14421443// If not scaling, that means we're moving.1444moving_selection_attempt = true;1445moving_selection = false;1446moving_selection_mouse_begin = mb->get_position();1447// The pivot will be from the mouse click location, not a specific key.1448moving_selection_from_key = -1;1449moving_selection_from_track = selected_track;1450moving_selection_offset = Vector2();1451select_single_attempt = IntPair(-1, -1);14521453return;1454}14551456// Insert new point.1457if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_or_control_pressed()) {1458float h = (get_size().height / 2.0 - mb->get_position().y) * timeline_v_zoom + timeline_v_scroll;1459Array new_point = animation->make_default_bezier_key(h);14601461real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();1462while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {1463time += 0.0001;1464}14651466EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1467undo_redo->create_action(TTR("Add Bezier Point"));1468undo_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]));1469undo_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);1470undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);1471undo_redo->commit_action();14721473// Then attempt to move.1474int index = animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX);1475ERR_FAIL_COND(index == -1);1476_clear_selection();1477_select_at_anim(animation, selected_track, animation->track_get_key_time(selected_track, index), true);14781479moving_selection_attempt = true;1480moving_inserted_key = true;1481moving_selection = false;1482moving_selection_mouse_begin = mb->get_position();1483moving_selection_from_key = index;1484moving_selection_from_track = selected_track;1485moving_selection_offset = Vector2();1486select_single_attempt = IntPair(-1, -1);1487queue_redraw();14881489return;1490}14911492// Box select.1493if (mb->get_position().x >= limit && mb->get_position().x < get_size().width) {1494box_selecting_attempt = true;1495box_selecting = false;1496box_selecting_add = false;1497box_selection_from = mb->get_position();1498return;1499}1500}15011502if (box_selecting_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1503if (box_selecting) {1504// Do actual select.1505if (!box_selecting_add) {1506_clear_selection();1507}15081509Vector2 bs_from = box_selection_from;1510Vector2 bs_to = box_selection_to;1511if (bs_from.x > bs_to.x) {1512SWAP(bs_from.x, bs_to.x);1513}1514if (bs_from.y > bs_to.y) {1515SWAP(bs_from.y, bs_to.y);1516}1517Rect2 rect(bs_from, bs_to - bs_from);15181519bool track_set = false;1520int j = 0;1521for (int i = 0; i < edit_points.size(); i++) {1522if (edit_points[i].point_rect.intersects(rect)) {1523_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);1524if (!track_set) {1525track_set = true;1526set_animation_and_track(animation, edit_points[i].track, read_only);1527}1528j++;1529}1530}1531} else {1532_clear_selection(); // Clicked and nothing happened, so clear the selection.15331534// Select by clicking on curve.1535int track_count = animation->get_track_count();15361537real_t animation_length = animation->get_length();1538animation->set_length(real_t(INT_MAX)); // bezier_track_interpolate doesn't find keys if they exist beyond anim length.15391540real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();15411542for (int i = 0; i < track_count; ++i) {1543if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i) || locked_tracks.has(i)) {1544continue;1545}15461547float track_h = animation->bezier_track_interpolate(i, time);1548float track_height = _bezier_h_to_pixel(track_h);15491550if (std::abs(mb->get_position().y - track_height) < 10) {1551set_animation_and_track(animation, i, read_only);1552break;1553}1554}15551556animation->set_length(animation_length);1557}15581559box_selecting_attempt = false;1560box_selecting = false;1561queue_redraw();1562}15631564if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1565if (!read_only) {1566if (moving_selection && (std::abs(moving_selection_offset.x) > CMP_EPSILON || std::abs(moving_selection_offset.y) > CMP_EPSILON)) {1567// Commit it.1568EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1569undo_redo->create_action(TTR("Move Bezier Points"));15701571List<AnimMoveRestore> to_restore;1572List<Animation::HandleMode> to_restore_handle_modes;1573// 1 - Remove the keys.1574for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1575undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);1576}1577// 2 - Remove overlapped keys.1578for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1579real_t newtime = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;15801581int idx = animation->track_find_key(E->get().first, newtime, Animation::FIND_MODE_APPROX);1582if (idx == -1) {1583continue;1584}15851586if (selection.has(IntPair(E->get().first, idx))) {1587continue; // Already in selection, don't save.1588}15891590undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);1591AnimMoveRestore amr;15921593amr.key = animation->track_get_key_value(E->get().first, idx);1594amr.track = E->get().first;1595amr.time = newtime;15961597to_restore.push_back(amr);1598to_restore_handle_modes.push_back(animation->bezier_track_get_key_handle_mode(E->get().first, idx));1599}16001601// 3 - Move the keys (re-insert them).1602for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1603real_t newpos = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;1604Array key = animation->track_get_key_value(E->get().first, E->get().second);1605real_t h = key[0];1606h += moving_selection_offset.y;1607key[0] = h;16081609Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second);1610Animation::HandleSetMode handle_set_mode = Animation::HANDLE_SET_MODE_NONE;1611if (moving_inserted_key) {1612handle_mode = (Animation::HandleMode)editor->bezier_key_mode->get_selected_id();1613handle_set_mode = Animation::HANDLE_SET_MODE_AUTO;1614}16151616undo_redo->add_do_method(1617this,1618"_bezier_track_insert_key_at_anim",1619animation,1620E->get().first,1621newpos,1622key[0],1623Vector2(key[1], key[2]),1624Vector2(key[3], key[4]),1625handle_mode,1626handle_set_mode);1627}16281629// 4 - (undo) Remove inserted keys.1630for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1631real_t newpos = animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x;1632undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);1633}16341635// 5 - (undo) Reinsert keys.1636for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1637real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);1638Array key = animation->track_get_key_value(E->get().first, E->get().second);1639undo_redo->add_undo_method(1640this,1641"_bezier_track_insert_key_at_anim",1642animation,1643E->get().first,1644oldpos,1645key[0],1646Vector2(key[1], key[2]),1647Vector2(key[3], key[4]),1648animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second));1649}16501651// 6 - (undo) Reinsert overlapped keys.1652List<AnimMoveRestore>::ConstIterator restore_itr = to_restore.begin();1653List<Animation::HandleMode>::ConstIterator handle_itr = to_restore_handle_modes.begin();1654for (; restore_itr != to_restore.end() && handle_itr != to_restore_handle_modes.end(); ++restore_itr, ++handle_itr) {1655const AnimMoveRestore &amr = *restore_itr;1656Array key = amr.key;1657undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);1658undo_redo->add_undo_method(1659this,1660"_bezier_track_insert_key_at_anim",1661animation,1662amr.track,1663amr.time,1664key[0],1665Vector2(key[1], key[2]),1666Vector2(key[3], key[4]),1667*handle_itr);1668}16691670undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);1671undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);16721673// 7 - Reselect.1674int i = 0;1675for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1676real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);1677real_t newpos = oldpos + moving_selection_offset.x;16781679undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos, i == 0);1680undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos, i == 0);1681i++;1682}16831684AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();1685if (ape) {1686undo_redo->add_do_method(ape, "_animation_update_key_frame");1687undo_redo->add_undo_method(ape, "_animation_update_key_frame");1688}1689undo_redo->commit_action();16901691} else if (select_single_attempt != IntPair(-1, -1)) {1692selection.clear();1693set_animation_and_track(animation, select_single_attempt.first, read_only);1694_select_at_anim(animation, select_single_attempt.first, animation->track_get_key_time(select_single_attempt.first, select_single_attempt.second), true);1695}16961697moving_selection = false;1698moving_selection_attempt = false;1699moving_inserted_key = false;1700moving_selection_mouse_begin = Point2();1701queue_redraw();1702}1703}17041705if (scaling_selection && mb.is_valid() && !read_only && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1706if (std::abs(scaling_selection_scale.x - 1) > CMP_EPSILON || std::abs(scaling_selection_scale.y - 1) > CMP_EPSILON) {1707// Scale it.1708EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1709undo_redo->create_action(TTR("Scale Bezier Points"));17101711List<AnimMoveRestore> to_restore;1712List<Animation::HandleMode> to_restore_handle_modes;1713// 1 - Remove the keys.1714for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1715undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);1716}1717// 2 - Remove overlapped keys.1718for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1719real_t newtime = animation->track_get_key_time(E->get().first, E->get().second);1720newtime += -scaling_selection_offset.x + (newtime - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);17211722int idx = animation->track_find_key(E->get().first, newtime, Animation::FIND_MODE_APPROX);1723if (idx == -1) {1724continue;1725}17261727if (selection.has(IntPair(E->get().first, idx))) {1728continue; // Already in selection, don't save.1729}17301731undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);1732AnimMoveRestore amr;17331734amr.key = animation->track_get_key_value(E->get().first, idx);1735amr.track = E->get().first;1736amr.time = newtime;17371738to_restore.push_back(amr);1739to_restore_handle_modes.push_back(animation->bezier_track_get_key_handle_mode(E->get().first, idx));1740}17411742// 3 - Scale the keys (re-insert them).1743for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1744real_t newpos = animation->track_get_key_time(E->get().first, E->get().second);1745newpos += -scaling_selection_offset.x + (newpos - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);17461747Array key = animation->track_get_key_value(E->get().first, E->get().second);1748real_t h = key[0];1749h += -scaling_selection_offset.y + (h - scaling_selection_pivot.y) * (scaling_selection_scale.y - 1);1750key[0] = h;17511752undo_redo->add_do_method(1753this,1754"_bezier_track_insert_key_at_anim",1755animation,1756E->get().first,1757newpos,1758key[0],1759Vector2(key[1], key[2]),1760Vector2(key[3], key[4]),1761animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second));1762}17631764// 4 - (undo) Remove inserted keys.1765for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1766real_t newpos = animation->track_get_key_time(E->get().first, E->get().second);1767newpos += -scaling_selection_offset.x + (newpos - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);1768undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);1769}17701771// 5 - (undo) Reinsert keys.1772for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1773real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);1774Array key = animation->track_get_key_value(E->get().first, E->get().second);1775undo_redo->add_undo_method(1776this,1777"_bezier_track_insert_key_at_anim",1778animation,1779E->get().first,1780oldpos,1781key[0],1782Vector2(key[1], key[2]),1783Vector2(key[3], key[4]),1784animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second));1785}17861787// 6 - (undo) Reinsert overlapped keys.1788List<AnimMoveRestore>::ConstIterator restore_itr = to_restore.begin();1789List<Animation::HandleMode>::ConstIterator handle_itr = to_restore_handle_modes.begin();1790for (; restore_itr != to_restore.end() && handle_itr != to_restore_handle_modes.end(); ++restore_itr, ++handle_itr) {1791const AnimMoveRestore &amr = *restore_itr;1792Array key = amr.key;1793undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);1794undo_redo->add_undo_method(1795this,1796"_bezier_track_insert_key_at_anim",1797animation,1798amr.track,1799amr.time,1800key[0],1801Vector2(key[1], key[2]),1802Vector2(key[3], key[4]),1803*handle_itr);1804}18051806undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);1807undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);18081809// 7 - Reselect.1810int i = 0;1811for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {1812real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);1813real_t newpos = animation->track_get_key_time(E->get().first, E->get().second);1814newpos += -scaling_selection_offset.x + (newpos - scaling_selection_pivot.x) * (scaling_selection_scale.x - 1);18151816undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos, i == 0);1817undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos, i == 0);1818i++;1819}18201821AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();1822if (ape) {1823undo_redo->add_do_method(ape, "_animation_update_key_frame");1824undo_redo->add_undo_method(ape, "_animation_update_key_frame");1825}1826undo_redo->commit_action();1827}18281829scaling_selection = false;1830scaling_selection_scale = Vector2(1, 1);1831scaling_selection_offset = Vector2();1832queue_redraw();1833}18341835Ref<InputEventMouseMotion> mm = p_event;1836if (moving_selection_attempt && mm.is_valid()) {1837Point2 new_pos = mm->get_position();1838if (mm->is_alt_pressed()) { // Axis snap key move when alt is pressed1839if (Math::abs(new_pos.x - moving_selection_mouse_begin.x) > Math::abs(new_pos.y - moving_selection_mouse_begin.y)) {1840new_pos.y = moving_selection_mouse_begin.y;1841} else {1842new_pos.x = moving_selection_mouse_begin.x;1843}1844}18451846if (!moving_selection) {1847moving_selection = true;1848select_single_attempt = IntPair(-1, -1);1849}18501851if (!read_only) {1852float y = (get_size().height / 2.0 - new_pos.y) * timeline_v_zoom + timeline_v_scroll;1853float moving_selection_begin_time = ((moving_selection_mouse_begin.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();1854float new_time = ((new_pos.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();1855float moving_selection_pivot = moving_selection_from_key != -1 ? animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key) : 0;1856float time_delta = new_time - moving_selection_begin_time;18571858float snapped_time = editor->snap_time(moving_selection_pivot + time_delta);1859float time_offset = 0.0;1860if (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)) {1861time_offset = snapped_time - moving_selection_pivot;1862}18631864float moving_selection_begin_value;1865if (moving_selection_from_key == -1) {1866moving_selection_begin_value = (get_size().height / 2.0 - moving_selection_mouse_begin.y) * timeline_v_zoom + timeline_v_scroll;1867} else {1868moving_selection_begin_value = animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key);1869}18701871float y_offset = y - moving_selection_begin_value;1872moving_selection_offset = Vector2(time_offset, y_offset);1873}18741875additional_moving_handle_lefts.clear();1876additional_moving_handle_rights.clear();18771878queue_redraw();1879}18801881if (box_selecting_attempt && mm.is_valid()) {1882if (!box_selecting) {1883box_selecting = true;1884box_selecting_add = mm->is_shift_pressed();1885}18861887box_selection_to = mm->get_position();1888queue_redraw();1889}18901891if (scaling_selection && mm.is_valid() && !read_only) {1892Point2 mp = mm->get_position();1893const int handle_length = Math::round((selection_handles_rect.size.width - selection_rect.size.width) / 4.0);1894Point2 rel_pos;18951896// Calculate the scale according with the distance between the mouse's position (adjusted so that the cursor appears inside the handles)1897// and the opposite end of the `selection_rect`.18981899if (scaling_selection_handles.x != 0) {1900if (scaling_selection_handles.x == 1) { // Right Handle1901const int handle_adjust = Math::round(mp.x - (scaling_selection_scale.x >= 0 ? selection_rect.position.x : (selection_rect.position.x + selection_rect.size.width)));1902mp.x -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.x * SIGN(handle_adjust);19031904if (editor->is_snap_keys_enabled()) {1905mp.x = editor->snap_time((mp.x - limit) / timeline->get_zoom_scale(), true) + timeline->get_value();1906mp.x = (mp.x - timeline->get_value()) * timeline->get_zoom_scale() + limit;1907}19081909rel_pos.x = scaling_selection_scale.x >= 0 ? (mp.x - selection_rect.position.x) : selection_rect.position.x + selection_rect.size.width - mp.x;1910} else { // Left Handle1911const int handle_adjust = Math::round((scaling_selection_scale.x >= 0 ? (selection_rect.position.x + selection_rect.size.width) : selection_rect.position.x) - mp.x);1912mp.x -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.x * SIGN(handle_adjust);19131914const float x = editor->snap_time((mp.x - limit) / timeline->get_zoom_scale(), true) + timeline->get_value();1915if (editor->is_snap_keys_enabled()) {1916mp.x = (x - timeline->get_value()) * timeline->get_zoom_scale() + limit;1917}19181919rel_pos.x = scaling_selection_scale.x >= 0 ? (selection_rect.position.x + selection_rect.size.width - mp.x) : (mp.x - selection_rect.position.x);1920scaling_selection_offset.x = scaling_selection_pivot.x - x;1921}19221923scaling_selection_scale.x *= rel_pos.x / selection_rect.size.width;1924if (scaling_selection_scale.x == 0) {1925scaling_selection_scale.x = CMP_EPSILON;1926}1927}19281929if (scaling_selection_handles.y != 0) {1930if (scaling_selection_handles.y == 1) { // Bottom Handle1931const int handle_adjust = Math::round(mp.y - (scaling_selection_scale.y >= 0 ? selection_rect.position.y : (selection_rect.position.y + selection_rect.size.height)));1932mp.y -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.y * SIGN(handle_adjust);19331934if (scaling_selection_scale.y >= 0) {1935rel_pos.y = mp.y - selection_rect.position.y;1936} else {1937rel_pos.y = selection_rect.position.y + selection_rect.size.height - mp.y;1938}1939} else { // Top Handle1940const int handle_adjust = Math::round((scaling_selection_scale.y >= 0 ? (selection_rect.position.y + selection_rect.size.height) : selection_rect.position.y) - mp.y);1941mp.y -= MIN(Math::abs(handle_adjust), handle_length) * scaling_selection_handles.y * SIGN(handle_adjust);19421943if (scaling_selection_scale.y >= 0) {1944rel_pos.y = selection_rect.position.y + selection_rect.size.height - mp.y;1945} else {1946rel_pos.y = mp.y - selection_rect.position.y;1947}19481949const float h = (get_size().height / 2.0 - mp.y) * timeline_v_zoom + timeline_v_scroll;1950scaling_selection_offset.y = scaling_selection_pivot.y - h;1951}19521953scaling_selection_scale.y *= rel_pos.y / selection_rect.size.height;1954if (scaling_selection_scale.y == 0) {1955scaling_selection_scale.y = CMP_EPSILON;1956}1957}19581959queue_redraw();1960}19611962if ((moving_handle == 1 || moving_handle == -1) && mm.is_valid()) {1963float y = (get_size().height / 2.0 - mm->get_position().y) * timeline_v_zoom + timeline_v_scroll;1964float x = editor->snap_time((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();19651966Vector2 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));19671968Vector2 moving_handle_value = Vector2(x, y) - key_pos;19691970moving_handle_left = animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key);1971moving_handle_right = animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key);19721973if (moving_handle == -1) {1974moving_handle_left = moving_handle_value;19751976Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key);19771978if (handle_mode == Animation::HANDLE_MODE_BALANCED) {1979real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;1980Transform2D xform;1981xform.set_scale(Vector2(1.0, 1.0 / ratio));19821983Vector2 vec_out = xform.xform(moving_handle_right);1984Vector2 vec_in = xform.xform(moving_handle_left);19851986moving_handle_right = xform.affine_inverse().xform(-vec_in.normalized() * vec_out.length());1987} else if (handle_mode == Animation::HANDLE_MODE_MIRRORED) {1988moving_handle_right = -moving_handle_left;1989}1990} else if (moving_handle == 1) {1991moving_handle_right = moving_handle_value;19921993Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key);19941995if (handle_mode == Animation::HANDLE_MODE_BALANCED) {1996real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;1997Transform2D xform;1998xform.set_scale(Vector2(1.0, 1.0 / ratio));19992000Vector2 vec_in = xform.xform(moving_handle_left);2001Vector2 vec_out = xform.xform(moving_handle_right);20022003moving_handle_left = xform.affine_inverse().xform(-vec_out.normalized() * vec_in.length());2004} else if (handle_mode == Animation::HANDLE_MODE_MIRRORED) {2005moving_handle_left = -moving_handle_right;2006}2007}2008queue_redraw();2009}20102011if ((moving_handle == -1 || moving_handle == 1) && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {2012if (!read_only) {2013EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2014undo_redo->create_action(TTR("Move Bezier Points"));2015if (moving_handle == -1) {2016real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;2017undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio);2018undo_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);2019} else if (moving_handle == 1) {2020real_t ratio = timeline->get_zoom_scale() * timeline_v_zoom;2021undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio);2022undo_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);2023}2024AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2025if (ape) {2026undo_redo->add_do_method(ape, "_animation_update_key_frame");2027undo_redo->add_undo_method(ape, "_animation_update_key_frame");2028}2029undo_redo->commit_action();2030moving_handle = 0;2031queue_redraw();2032}2033}2034}20352036bool AnimationBezierTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable) {2037for (int i = 0; i < edit_points.size(); i++) {2038// Path 2D editing in the 3D and 2D editors works the same way. (?)2039if (edit_points[i].point_rect.has_point(p_pos)) {2040IntPair pair = IntPair(edit_points[i].track, edit_points[i].key);2041if (p_aggregate) {2042// Add to selection.2043if (selection.has(pair)) {2044if (p_deselectable) {2045selection.erase(pair);2046emit_signal(SNAME("deselect_key"), edit_points[i].key, edit_points[i].track);2047}2048} else {2049_select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), false);2050}2051queue_redraw();2052select_single_attempt = IntPair(-1, -1);2053} else {2054if (p_deselectable) {2055moving_selection_attempt = true;2056moving_selection_from_key = pair.second;2057moving_selection_from_track = pair.first;2058moving_selection_mouse_begin = p_pos;2059moving_selection_offset = Vector2();2060moving_handle_track = pair.first;2061moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second);2062moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second);20632064if (selection.has(pair)) {2065moving_selection = false;2066} else {2067moving_selection = true;2068}2069select_single_attempt = pair;2070}20712072set_animation_and_track(animation, pair.first, read_only);2073if (!selection.has(pair)) {2074selection.clear();2075_select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), true);2076}2077}2078return true;2079}2080}2081return false;2082}20832084void AnimationBezierTrackEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {2085Ref<InputEventMouseMotion> mm = p_event;2086if (mm.is_valid()) {2087if (mm->get_position().x > timeline->get_name_limit()) {2088timeline_v_scroll += p_scroll_vec.y * timeline_v_zoom;2089timeline_v_scroll = CLAMP(timeline_v_scroll, -100000, 100000);2090timeline->set_value(timeline->get_value() - p_scroll_vec.x / timeline->get_zoom_scale());2091} else {2092track_v_scroll += p_scroll_vec.y;2093if (track_v_scroll < -track_v_scroll_max) {2094track_v_scroll = -track_v_scroll_max;2095} else if (track_v_scroll > 0) {2096track_v_scroll = 0;2097}2098}2099queue_redraw();2100}2101}21022103void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {2104const float v_zoom_orig = timeline_v_zoom;2105Ref<InputEventWithModifiers> iewm = p_event;2106if (iewm.is_valid() && iewm->is_alt_pressed()) {2107// Alternate zoom (doesn't affect timeline).2108timeline_v_zoom = CLAMP(timeline_v_zoom / p_zoom_factor, 0.000001, 100000);2109} else {2110float zoom_factor = p_zoom_factor > 1.0 ? AnimationTimelineEdit::SCROLL_ZOOM_FACTOR_IN : AnimationTimelineEdit::SCROLL_ZOOM_FACTOR_OUT;2111timeline->_zoom_callback(zoom_factor, p_origin, p_event);2112}2113timeline_v_scroll = timeline_v_scroll + (p_origin.y - get_size().y / 2.0) * (timeline_v_zoom - v_zoom_orig);2114queue_redraw();2115}21162117float AnimationBezierTrackEdit::get_bezier_key_value(Array p_bezier_key_array) {2118return p_bezier_key_array[0];2119}21202121void AnimationBezierTrackEdit::_menu_selected(int p_index) {2122int limit = timeline->get_name_limit();21232124real_t time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();21252126switch (p_index) {2127case MENU_KEY_INSERT: {2128if (animation->get_track_count() > 0) {2129if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {2130time = editor->snap_time(time);2131}2132while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {2133time += 0.001;2134}2135float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;2136Array new_point = animation->make_default_bezier_key(h);2137Animation::HandleMode handle_mode = (Animation::HandleMode)editor->bezier_key_mode->get_selected_id();2138EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2139undo_redo->create_action(TTR("Add Bezier Point"));2140undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);2141undo_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);2142undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);2143undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);2144AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2145if (ape) {2146undo_redo->add_do_method(ape, "_animation_update_key_frame");2147undo_redo->add_undo_method(ape, "_animation_update_key_frame");2148}2149undo_redo->commit_action();2150queue_redraw();2151}2152} break;2153case MENU_KEY_DUPLICATE: {2154duplicate_selected_keys(time, true);2155} break;2156case MENU_KEY_DELETE: {2157delete_selection();2158} break;2159case MENU_KEY_CUT: {2160copy_selected_keys(true);2161} break;2162case MENU_KEY_COPY: {2163copy_selected_keys(false);2164} break;2165case MENU_KEY_PASTE: {2166paste_keys(time, true);2167} break;2168case MENU_KEY_SET_HANDLE_FREE: {2169_change_selected_keys_handle_mode(Animation::HANDLE_MODE_FREE);2170} break;2171case MENU_KEY_SET_HANDLE_LINEAR: {2172_change_selected_keys_handle_mode(Animation::HANDLE_MODE_LINEAR);2173} break;2174case MENU_KEY_SET_HANDLE_BALANCED: {2175_change_selected_keys_handle_mode(Animation::HANDLE_MODE_BALANCED);2176} break;2177case MENU_KEY_SET_HANDLE_MIRRORED: {2178_change_selected_keys_handle_mode(Animation::HANDLE_MODE_MIRRORED);2179} break;2180case MENU_KEY_SET_HANDLE_AUTO_BALANCED: {2181_change_selected_keys_handle_mode(Animation::HANDLE_MODE_BALANCED, true);2182} break;2183case MENU_KEY_SET_HANDLE_AUTO_MIRRORED: {2184_change_selected_keys_handle_mode(Animation::HANDLE_MODE_MIRRORED, true);2185} break;2186}2187}21882189void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid) {2190if (selection.is_empty()) {2191return;2192}21932194real_t top_time = 1e10;2195for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2196real_t t = animation->track_get_key_time(E->get().first, E->get().second);2197if (t < top_time) {2198top_time = t;2199}2200}22012202EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2203undo_redo->create_action(TTR("Animation Duplicate Keys"));22042205List<Pair<int, real_t>> new_selection_values;22062207for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2208real_t t = animation->track_get_key_time(E->get().first, E->get().second);2209real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();22102211if (p_ofs_valid) {2212if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {2213insert_pos = editor->snap_time(insert_pos);2214}2215}22162217real_t dst_time = t + (insert_pos - top_time);2218int existing_idx = animation->track_find_key(E->get().first, dst_time, Animation::FIND_MODE_APPROX);22192220undo_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));2221undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time);22222223Pair<int, real_t> p;2224p.first = E->get().first;2225p.second = dst_time;2226new_selection_values.push_back(p);22272228if (existing_idx != -1) {2229undo_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));2230}2231}22322233undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2234undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);22352236// Reselect duplicated.2237int i = 0;2238for (const Pair<int, real_t> &E : new_selection_values) {2239undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second, i == 0);2240i++;2241}2242i = 0;2243for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2244real_t time = animation->track_get_key_time(E->get().first, E->get().second);2245undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, time, i == 0);2246i++;2247}22482249AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2250if (ape) {2251undo_redo->add_do_method(ape, "_animation_update_key_frame");2252undo_redo->add_undo_method(ape, "_animation_update_key_frame");2253}2254undo_redo->add_do_method(this, "queue_redraw");2255undo_redo->add_undo_method(this, "queue_redraw");2256undo_redo->commit_action();2257}22582259void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) {2260if (selection.is_empty()) {2261return;2262}22632264float top_time = 1e10;2265for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2266float t = animation->track_get_key_time(E->get().first, E->get().second);2267if (t < top_time) {2268top_time = t;2269}2270}22712272RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo> keys;2273for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2274AnimationTrackEditor::SelectedKey sk;2275AnimationTrackEditor::KeyInfo ki;2276sk.track = E->get().first;2277sk.key = E->get().second;2278ki.pos = animation->track_get_key_time(E->get().first, E->get().second);2279keys.insert(sk, ki);2280}2281editor->_set_key_clipboard(selected_track, top_time, keys);22822283if (p_cut) {2284EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2285undo_redo->create_action(TTR("Animation Cut Keys"), UndoRedo::MERGE_DISABLE, animation.ptr());2286undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2287undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);2288int i = 0;2289for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) {2290int track_idx = E->key().track;2291int key_idx = E->key().key;2292float time = E->value().pos;2293undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track_idx, time);2294undo_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));2295undo_redo->add_undo_method(this, "_select_at_anim", animation, track_idx, time, i == 0);2296i++;2297}2298i = 0;2299for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) {2300undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos, i == 0);2301i++;2302}23032304AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2305if (ape) {2306undo_redo->add_do_method(ape, "_animation_update_key_frame");2307undo_redo->add_undo_method(ape, "_animation_update_key_frame");2308}2309undo_redo->add_do_method(this, "queue_redraw");2310undo_redo->add_undo_method(this, "queue_redraw");23112312undo_redo->commit_action();2313}2314}23152316void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {2317if (editor->is_key_clipboard_active() && animation.is_valid() && (selected_track >= 0 && selected_track < animation->get_track_count())) {2318EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2319undo_redo->create_action(TTR("Animation Paste Keys"));23202321bool same_track = true;2322bool all_compatible = true;23232324for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {2325const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];23262327if (key.track != 0) {2328same_track = false;2329break;2330}23312332if (!editor->_is_track_compatible(selected_track, key.value.get_type(), key.track_type)) {2333all_compatible = false;2334break;2335}2336}23372338ERR_FAIL_COND_MSG(!all_compatible, "Paste failed: Not all animation keys were compatible with their target tracks");2339if (!same_track) {2340WARN_PRINT("Pasted animation keys from multiple tracks into single Bezier track");2341}23422343List<Pair<int, float>> new_selection_values;2344for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {2345const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];23462347float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();2348if (p_ofs_valid) {2349if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {2350insert_pos = editor->snap_time(insert_pos);2351}2352}2353float dst_time = key.time + insert_pos;23542355int existing_idx = animation->track_find_key(selected_track, dst_time, Animation::FIND_MODE_APPROX);23562357Variant value = key.value;2358if (key.track_type != Animation::TYPE_BEZIER) {2359value = animation->make_default_bezier_key(key.value);2360}23612362undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, dst_time, value, key.transition);2363undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, dst_time);23642365Pair<int, float> p;2366p.first = selected_track;2367p.second = dst_time;2368new_selection_values.push_back(p);23692370if (existing_idx != -1) {2371undo_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));2372}2373}23742375undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2376undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);23772378// Reselect pasted.2379int i = 0;2380for (const Pair<int, float> &E : new_selection_values) {2381undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second, i == 0);2382i++;2383}2384i = 0;2385for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2386undo_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);2387i++;2388}23892390AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2391if (ape) {2392undo_redo->add_do_method(ape, "_animation_update_key_frame");2393undo_redo->add_undo_method(ape, "_animation_update_key_frame");2394}2395undo_redo->add_do_method(this, "queue_redraw");2396undo_redo->add_undo_method(this, "queue_redraw");23972398undo_redo->commit_action();2399}2400}24012402void AnimationBezierTrackEdit::delete_selection() {2403if (selection.size()) {2404EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2405undo_redo->create_action(TTR("Animation Delete Keys"));24062407for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {2408undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);2409undo_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);2410}2411undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);2412undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);2413AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();2414if (ape) {2415undo_redo->add_do_method(ape, "_animation_update_key_frame");2416undo_redo->add_undo_method(ape, "_animation_update_key_frame");2417}2418undo_redo->commit_action();24192420//selection.clear();2421}2422}24232424void 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) {2425int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);2426p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode, p_handle_set_mode);2427}24282429void AnimationBezierTrackEdit::_bind_methods() {2430ClassDB::bind_method(D_METHOD("_clear_selection"), &AnimationBezierTrackEdit::_clear_selection);2431ClassDB::bind_method(D_METHOD("_clear_selection_for_anim"), &AnimationBezierTrackEdit::_clear_selection_for_anim);2432ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim);2433ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after);2434ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after);2435ClassDB::bind_method(D_METHOD("_bezier_track_insert_key_at_anim"), &AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim, DEFVAL(Animation::HANDLE_SET_MODE_NONE));24362437ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track")));2438ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::INT, "track")));2439ADD_SIGNAL(MethodInfo("clear_selection"));2440ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "timeline_only")));2441}24422443AnimationBezierTrackEdit::AnimationBezierTrackEdit() {2444panner.instantiate();2445panner->set_callbacks(callable_mp(this, &AnimationBezierTrackEdit::_pan_callback), callable_mp(this, &AnimationBezierTrackEdit::_zoom_callback));24462447play_position = memnew(Control);2448play_position->set_mouse_filter(MOUSE_FILTER_PASS);2449add_child(play_position);2450play_position->set_anchors_and_offsets_preset(PRESET_FULL_RECT);2451play_position->connect(SceneStringName(draw), callable_mp(this, &AnimationBezierTrackEdit::_play_position_draw));2452set_focus_mode(FOCUS_CLICK);24532454set_clip_contents(true);24552456ED_SHORTCUT("animation_bezier_editor/focus", TTRC("Focus"), Key::F);2457ED_SHORTCUT("animation_bezier_editor/select_all_keys", TTRC("Select All Keys"), KeyModifierMask::CMD_OR_CTRL | Key::A);2458ED_SHORTCUT("animation_bezier_editor/deselect_all_keys", TTRC("Deselect All Keys"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::A);24592460menu = memnew(PopupMenu);2461add_child(menu);2462menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationBezierTrackEdit::_menu_selected));2463}246424652466