Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/scene/sprite_frames_editor_plugin.cpp
9898 views
1
/**************************************************************************/
2
/* sprite_frames_editor_plugin.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "sprite_frames_editor_plugin.h"
32
33
#include "core/io/resource_loader.h"
34
#include "core/os/keyboard.h"
35
#include "editor/docks/filesystem_dock.h"
36
#include "editor/docks/scene_tree_dock.h"
37
#include "editor/editor_node.h"
38
#include "editor/editor_string_names.h"
39
#include "editor/editor_undo_redo_manager.h"
40
#include "editor/file_system/editor_file_system.h"
41
#include "editor/gui/editor_bottom_panel.h"
42
#include "editor/gui/editor_file_dialog.h"
43
#include "editor/settings/editor_command_palette.h"
44
#include "editor/settings/editor_settings.h"
45
#include "editor/themes/editor_scale.h"
46
#include "scene/2d/animated_sprite_2d.h"
47
#include "scene/3d/sprite_3d.h"
48
#include "scene/gui/center_container.h"
49
#include "scene/gui/flow_container.h"
50
#include "scene/gui/margin_container.h"
51
#include "scene/gui/option_button.h"
52
#include "scene/gui/panel_container.h"
53
#include "scene/gui/separator.h"
54
#include "scene/resources/atlas_texture.h"
55
56
static void _draw_shadowed_line(Control *p_control, const Point2 &p_from, const Size2 &p_size, const Size2 &p_shadow_offset, Color p_color, Color p_shadow_color) {
57
p_control->draw_line(p_from, p_from + p_size, p_color);
58
p_control->draw_line(p_from + p_shadow_offset, p_from + p_size + p_shadow_offset, p_shadow_color);
59
}
60
61
void SpriteFramesEditor::_open_sprite_sheet() {
62
file_split_sheet->clear_filters();
63
List<String> extensions;
64
ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);
65
for (const String &extension : extensions) {
66
file_split_sheet->add_filter("*." + extension);
67
}
68
69
file_split_sheet->popup_file_dialog();
70
}
71
72
int SpriteFramesEditor::_sheet_preview_position_to_frame_index(const Point2 &p_position) {
73
const Size2i offset = _get_offset();
74
const Size2i frame_size = _get_frame_size();
75
const Size2i separation = _get_separation();
76
const Size2i block_size = frame_size + separation;
77
const Point2i position = p_position / sheet_zoom - offset;
78
79
if (position.x < 0 || position.y < 0) {
80
return -1; // Out of bounds.
81
}
82
83
if (position.x % block_size.x >= frame_size.x || position.y % block_size.y >= frame_size.y) {
84
return -1; // Gap between frames.
85
}
86
87
const Point2i frame = position / block_size;
88
const Size2i frame_count = _get_frame_count();
89
if (frame.x >= frame_count.x || frame.y >= frame_count.y) {
90
return -1; // Out of bounds.
91
}
92
93
return frame_count.x * frame.y + frame.x;
94
}
95
96
void SpriteFramesEditor::_sheet_preview_draw() {
97
const Size2i frame_count = _get_frame_count();
98
const Size2i separation = _get_separation();
99
100
const Size2 draw_offset = Size2(_get_offset()) * sheet_zoom;
101
const Size2 draw_sep = Size2(separation) * sheet_zoom;
102
const Size2 draw_frame_size = Size2(_get_frame_size()) * sheet_zoom;
103
const Size2 draw_size = draw_frame_size * frame_count + draw_sep * (frame_count - Size2i(1, 1));
104
105
const Color line_color = Color(1, 1, 1, 0.3);
106
const Color shadow_color = Color(0, 0, 0, 0.3);
107
108
// Vertical lines.
109
_draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);
110
for (int i = 0; i < frame_count.x - 1; i++) {
111
const Point2 start = draw_offset + Vector2(i * draw_sep.x + (i + 1) * draw_frame_size.x, 0);
112
if (separation.x == 0) {
113
_draw_shadowed_line(split_sheet_preview, start, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);
114
} else {
115
const Size2 size = Size2(draw_sep.x, draw_size.y);
116
split_sheet_preview->draw_rect(Rect2(start, size), line_color);
117
}
118
}
119
_draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(draw_size.x, 0), Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);
120
121
// Horizontal lines.
122
_draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);
123
for (int i = 0; i < frame_count.y - 1; i++) {
124
const Point2 start = draw_offset + Vector2(0, i * draw_sep.y + (i + 1) * draw_frame_size.y);
125
if (separation.y == 0) {
126
_draw_shadowed_line(split_sheet_preview, start, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);
127
} else {
128
const Size2 size = Size2(draw_size.x, draw_sep.y);
129
split_sheet_preview->draw_rect(Rect2(start, size), line_color);
130
}
131
}
132
_draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(0, draw_size.y), Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);
133
134
if (frames_selected.is_empty()) {
135
split_sheet_dialog->get_ok_button()->set_disabled(true);
136
split_sheet_dialog->set_ok_button_text(TTR("No Frames Selected"));
137
return;
138
}
139
140
Color accent = get_theme_color("accent_color", EditorStringName(Editor));
141
142
_sheet_sort_frames();
143
144
Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
145
int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
146
147
for (int i = 0; i < frames_ordered.size(); ++i) {
148
const int idx = frames_ordered[i].second;
149
150
const int x = idx % frame_count.x;
151
const int y = idx / frame_count.x;
152
const Point2 pos = draw_offset + Point2(x, y) * (draw_frame_size + draw_sep);
153
split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 0.35), true);
154
split_sheet_preview->draw_rect(Rect2(pos, draw_frame_size), Color(0, 0, 0, 1), false);
155
split_sheet_preview->draw_rect(Rect2(pos + Size2(1, 1), draw_frame_size - Size2(2, 2)), Color(0, 0, 0, 1), false);
156
split_sheet_preview->draw_rect(Rect2(pos + Size2(2, 2), draw_frame_size - Size2(4, 4)), accent, false);
157
split_sheet_preview->draw_rect(Rect2(pos + Size2(3, 3), draw_frame_size - Size2(6, 6)), accent, false);
158
split_sheet_preview->draw_rect(Rect2(pos + Size2(4, 4), draw_frame_size - Size2(8, 8)), Color(0, 0, 0, 1), false);
159
split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 1), false);
160
161
const String text = itos(i);
162
const Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
163
164
// Stop rendering text if too large.
165
if (string_size.x + 6 < draw_frame_size.x && string_size.y / 2 + 10 < draw_frame_size.y) {
166
split_sheet_preview->draw_string_outline(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, 1, Color(0, 0, 0, 1));
167
split_sheet_preview->draw_string(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, Color(1, 1, 1));
168
}
169
}
170
171
split_sheet_dialog->get_ok_button()->set_disabled(false);
172
split_sheet_dialog->set_ok_button_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size()));
173
}
174
175
void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
176
const Ref<InputEventMouseButton> mb = p_event;
177
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
178
const int idx = _sheet_preview_position_to_frame_index(mb->get_position());
179
180
if (idx != -1) {
181
if (mb->is_shift_pressed() && last_frame_selected >= 0) {
182
// Select multiple frames.
183
const int from = last_frame_selected;
184
const int to = idx;
185
186
const int diff = Math::abs(to - from);
187
const int dir = SIGN(to - from);
188
189
for (int i = 0; i <= diff; i++) {
190
const int this_idx = from + i * dir;
191
192
// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
193
frames_toggled_by_mouse_hover.insert(this_idx);
194
195
if (mb->is_command_or_control_pressed()) {
196
frames_selected.erase(this_idx);
197
} else if (!frames_selected.has(this_idx)) {
198
frames_selected.insert(this_idx, selected_count);
199
selected_count++;
200
}
201
}
202
} else {
203
// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
204
frames_toggled_by_mouse_hover.insert(idx);
205
206
if (frames_selected.has(idx)) {
207
frames_selected.erase(idx);
208
} else {
209
frames_selected.insert(idx, selected_count);
210
selected_count++;
211
}
212
}
213
}
214
215
if (last_frame_selected != idx || idx != -1) {
216
last_frame_selected = idx;
217
frames_need_sort = true;
218
split_sheet_preview->queue_redraw();
219
}
220
}
221
222
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
223
frames_toggled_by_mouse_hover.clear();
224
}
225
226
const Ref<InputEventMouseMotion> mm = p_event;
227
if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
228
// Select by holding down the mouse button on frames.
229
const int idx = _sheet_preview_position_to_frame_index(mm->get_position());
230
231
if (idx != -1 && !frames_toggled_by_mouse_hover.has(idx)) {
232
// Only allow toggling each tile once per mouse hold.
233
// Otherwise, the selection would constantly "flicker" in and out when moving the mouse cursor.
234
// The mouse button must be released before it can be toggled again.
235
frames_toggled_by_mouse_hover.insert(idx);
236
237
if (frames_selected.has(idx)) {
238
frames_selected.erase(idx);
239
} else {
240
frames_selected.insert(idx, selected_count);
241
selected_count++;
242
}
243
244
last_frame_selected = idx;
245
frames_need_sort = true;
246
split_sheet_preview->queue_redraw();
247
}
248
}
249
250
if (frames_selected.is_empty()) {
251
selected_count = 0;
252
}
253
}
254
255
void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {
256
const Ref<InputEventMouseButton> mb = p_event;
257
258
if (mb.is_valid()) {
259
// Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer
260
// to allow performing this action anywhere, even if the cursor isn't
261
// hovering the texture in the workspace.
262
// keep CTRL and not CMD_OR_CTRL as CTRL is expected even on MacOS.
263
if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {
264
_sheet_zoom_on_position(scale_ratio, mb->get_position());
265
// Don't scroll up after zooming in.
266
split_sheet_scroll->accept_event();
267
} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {
268
_sheet_zoom_on_position(1 / scale_ratio, mb->get_position());
269
// Don't scroll down after zooming out.
270
split_sheet_scroll->accept_event();
271
}
272
}
273
274
const Ref<InputEventMouseMotion> mm = p_event;
275
if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
276
const Vector2 dragged = Input::get_singleton()->warp_mouse_motion(mm, split_sheet_scroll->get_global_rect());
277
split_sheet_scroll->set_h_scroll(split_sheet_scroll->get_h_scroll() - dragged.x);
278
split_sheet_scroll->set_v_scroll(split_sheet_scroll->get_v_scroll() - dragged.y);
279
}
280
}
281
282
void SpriteFramesEditor::_sheet_add_frames() {
283
const Size2i frame_count = _get_frame_count();
284
const Size2i frame_size = _get_frame_size();
285
const Size2i offset = _get_offset();
286
const Size2i separation = _get_separation();
287
288
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
289
undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());
290
int fc = frames->get_frame_count(edited_anim);
291
292
_sheet_sort_frames();
293
294
for (const Pair<int, int> &pair : frames_ordered) {
295
const int idx = pair.second;
296
297
const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x);
298
299
Ref<AtlasTexture> at;
300
at.instantiate();
301
at->set_atlas(split_sheet_preview->get_texture());
302
at->set_region(Rect2(offset + frame_coords * (frame_size + separation), frame_size));
303
304
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, at, 1.0, -1);
305
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, fc);
306
}
307
308
undo_redo->add_do_method(this, "_update_library");
309
undo_redo->add_undo_method(this, "_update_library");
310
undo_redo->commit_action();
311
}
312
313
void SpriteFramesEditor::_sheet_zoom_on_position(float p_zoom, const Vector2 &p_position) {
314
const float old_zoom = sheet_zoom;
315
sheet_zoom = CLAMP(sheet_zoom * p_zoom, min_sheet_zoom, max_sheet_zoom);
316
317
const Size2 texture_size = split_sheet_preview->get_texture()->get_size();
318
split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);
319
320
Vector2 offset = Vector2(split_sheet_scroll->get_h_scroll(), split_sheet_scroll->get_v_scroll());
321
offset = (offset + p_position) / old_zoom * sheet_zoom - p_position;
322
split_sheet_scroll->set_h_scroll(offset.x);
323
split_sheet_scroll->set_v_scroll(offset.y);
324
}
325
326
void SpriteFramesEditor::_sheet_zoom_in() {
327
_sheet_zoom_on_position(scale_ratio, Vector2());
328
}
329
330
void SpriteFramesEditor::_sheet_zoom_out() {
331
_sheet_zoom_on_position(1 / scale_ratio, Vector2());
332
}
333
334
void SpriteFramesEditor::_sheet_zoom_reset() {
335
// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.
336
sheet_zoom = MAX(1.0f, EDSCALE);
337
Size2 texture_size = split_sheet_preview->get_texture()->get_size();
338
split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);
339
}
340
341
void SpriteFramesEditor::_sheet_order_selected(int p_option) {
342
frames_need_sort = true;
343
split_sheet_preview->queue_redraw();
344
}
345
346
void SpriteFramesEditor::_sheet_select_all_frames() {
347
for (int i = 0; i < split_sheet_h->get_value() * split_sheet_v->get_value(); i++) {
348
if (!frames_selected.has(i)) {
349
frames_selected.insert(i, selected_count);
350
selected_count++;
351
frames_need_sort = true;
352
}
353
}
354
355
split_sheet_preview->queue_redraw();
356
}
357
358
void SpriteFramesEditor::_sheet_clear_all_frames() {
359
frames_selected.clear();
360
selected_count = 0;
361
362
split_sheet_preview->queue_redraw();
363
}
364
365
void SpriteFramesEditor::_sheet_sort_frames() {
366
if (!frames_need_sort) {
367
return;
368
}
369
frames_need_sort = false;
370
frames_ordered.resize(frames_selected.size());
371
if (frames_selected.is_empty()) {
372
return;
373
}
374
375
const Size2i frame_count = _get_frame_count();
376
const int frame_order = split_sheet_order->get_selected_id();
377
int index = 0;
378
379
// Fill based on order.
380
for (const KeyValue<int, int> &from_pair : frames_selected) {
381
const int idx = from_pair.key;
382
383
const int selection_order = from_pair.value;
384
385
// Default to using selection order.
386
int order_by = selection_order;
387
388
// Extract coordinates for sorting.
389
const int pos_frame_x = idx % frame_count.x;
390
const int pos_frame_y = idx / frame_count.x;
391
392
const int neg_frame_x = frame_count.x - (pos_frame_x + 1);
393
const int neg_frame_y = frame_count.y - (pos_frame_y + 1);
394
395
switch (frame_order) {
396
case FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM: {
397
order_by = frame_count.x * pos_frame_y + pos_frame_x;
398
} break;
399
400
case FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP: {
401
order_by = frame_count.x * neg_frame_y + pos_frame_x;
402
} break;
403
404
case FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM: {
405
order_by = frame_count.x * pos_frame_y + neg_frame_x;
406
} break;
407
408
case FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP: {
409
order_by = frame_count.x * neg_frame_y + neg_frame_x;
410
} break;
411
412
case FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT: {
413
order_by = pos_frame_y + frame_count.y * pos_frame_x;
414
} break;
415
416
case FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT: {
417
order_by = pos_frame_y + frame_count.y * neg_frame_x;
418
} break;
419
420
case FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT: {
421
order_by = neg_frame_y + frame_count.y * pos_frame_x;
422
} break;
423
424
case FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT: {
425
order_by = neg_frame_y + frame_count.y * neg_frame_x;
426
} break;
427
}
428
429
// Assign in vector.
430
frames_ordered.set(index, Pair<int, int>(order_by, idx));
431
index++;
432
}
433
434
// Sort frames.
435
frames_ordered.sort_custom<PairSort<int, int>>();
436
}
437
438
void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_param) {
439
if (updating_split_settings) {
440
return;
441
}
442
updating_split_settings = true;
443
444
if (p_dominant_param != PARAM_USE_CURRENT) {
445
dominant_param = p_dominant_param;
446
}
447
448
const Size2i texture_size = split_sheet_preview->get_texture()->get_size();
449
const Size2i size = texture_size - _get_offset();
450
451
switch (dominant_param) {
452
case PARAM_SIZE: {
453
const Size2i frame_size = _get_frame_size();
454
455
const Size2i offset_max = texture_size - frame_size;
456
split_sheet_offset_x->set_max(offset_max.x);
457
split_sheet_offset_y->set_max(offset_max.y);
458
459
const Size2i sep_max = size - frame_size * 2;
460
split_sheet_sep_x->set_max(sep_max.x);
461
split_sheet_sep_y->set_max(sep_max.y);
462
463
const Size2i separation = _get_separation();
464
const Size2i count = (size + separation) / (frame_size + separation);
465
split_sheet_h->set_value(count.x);
466
split_sheet_v->set_value(count.y);
467
} break;
468
469
case PARAM_FRAME_COUNT: {
470
const Size2i count = _get_frame_count();
471
472
const Size2i offset_max = texture_size - count;
473
split_sheet_offset_x->set_max(offset_max.x);
474
split_sheet_offset_y->set_max(offset_max.y);
475
476
const Size2i gap_count = count - Size2i(1, 1);
477
split_sheet_sep_x->set_max(gap_count.x == 0 ? size.x : (size.x - count.x) / gap_count.x);
478
split_sheet_sep_y->set_max(gap_count.y == 0 ? size.y : (size.y - count.y) / gap_count.y);
479
480
const Size2i separation = _get_separation();
481
const Size2i frame_size = (size - separation * gap_count) / count;
482
split_sheet_size_x->set_value(frame_size.x);
483
split_sheet_size_y->set_value(frame_size.y);
484
} break;
485
}
486
487
updating_split_settings = false;
488
489
frames_selected.clear();
490
selected_count = 0;
491
last_frame_selected = -1;
492
split_sheet_preview->queue_redraw();
493
}
494
495
void SpriteFramesEditor::_toggle_show_settings() {
496
split_sheet_settings_vb->set_visible(!split_sheet_settings_vb->is_visible());
497
498
_update_show_settings();
499
}
500
501
void SpriteFramesEditor::_update_show_settings() {
502
if (is_layout_rtl()) {
503
toggle_settings_button->set_button_icon(get_editor_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Back") : SNAME("Forward")));
504
} else {
505
toggle_settings_button->set_button_icon(get_editor_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Forward") : SNAME("Back")));
506
}
507
}
508
509
void SpriteFramesEditor::_auto_slice_sprite_sheet() {
510
if (updating_split_settings) {
511
return;
512
}
513
updating_split_settings = true;
514
515
const Size2i size = split_sheet_preview->get_texture()->get_size();
516
517
const Size2i split_sheet = _estimate_sprite_sheet_size(split_sheet_preview->get_texture());
518
split_sheet_h->set_value(split_sheet.x);
519
split_sheet_v->set_value(split_sheet.y);
520
split_sheet_size_x->set_value(size.x / split_sheet.x);
521
split_sheet_size_y->set_value(size.y / split_sheet.y);
522
split_sheet_sep_x->set_value(0);
523
split_sheet_sep_y->set_value(0);
524
split_sheet_offset_x->set_value(0);
525
split_sheet_offset_y->set_value(0);
526
527
updating_split_settings = false;
528
529
frames_selected.clear();
530
selected_count = 0;
531
last_frame_selected = -1;
532
split_sheet_preview->queue_redraw();
533
}
534
535
bool SpriteFramesEditor::_matches_background_color(const Color &p_background_color, const Color &p_pixel_color) {
536
if ((p_background_color.a == 0 && p_pixel_color.a == 0) || p_background_color.is_equal_approx(p_pixel_color)) {
537
return true;
538
}
539
540
Color d = p_background_color - p_pixel_color;
541
// 0.04f is the threshold for how much a colour can deviate from background colour and still be considered a match. Arrived at through experimentation, can be tweaked.
542
return (d.r * d.r) + (d.g * d.g) + (d.b * d.b) + (d.a * d.a) < 0.04f;
543
}
544
545
Size2i SpriteFramesEditor::_estimate_sprite_sheet_size(const Ref<Texture2D> p_texture) {
546
Ref<Image> image = p_texture->get_image();
547
if (image->is_compressed()) {
548
image = image->duplicate();
549
ERR_FAIL_COND_V(image->decompress() != OK, p_texture->get_size());
550
}
551
Size2i size = image->get_size();
552
553
Color assumed_background_color = image->get_pixel(0, 0);
554
Size2i sheet_size;
555
556
bool previous_line_background = true;
557
for (int x = 0; x < size.x; x++) {
558
int y = 0;
559
while (y < size.y && _matches_background_color(assumed_background_color, image->get_pixel(x, y))) {
560
y++;
561
}
562
bool current_line_background = (y == size.y);
563
if (previous_line_background && !current_line_background) {
564
sheet_size.x++;
565
}
566
previous_line_background = current_line_background;
567
}
568
569
previous_line_background = true;
570
for (int y = 0; y < size.y; y++) {
571
int x = 0;
572
while (x < size.x && _matches_background_color(assumed_background_color, image->get_pixel(x, y))) {
573
x++;
574
}
575
bool current_line_background = (x == size.x);
576
if (previous_line_background && !current_line_background) {
577
sheet_size.y++;
578
}
579
previous_line_background = current_line_background;
580
}
581
582
if (sheet_size == Size2i(0, 0) || sheet_size == Size2i(1, 1)) {
583
sheet_size = Size2i(4, 4);
584
}
585
586
return sheet_size;
587
}
588
589
void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
590
Ref<Texture2D> texture = ResourceLoader::load(p_file);
591
if (texture.is_null()) {
592
EditorNode::get_singleton()->show_warning(TTR("Unable to load images"));
593
ERR_FAIL_COND(texture.is_null());
594
}
595
frames_selected.clear();
596
selected_count = 0;
597
last_frame_selected = -1;
598
599
bool new_texture = texture != split_sheet_preview->get_texture();
600
split_sheet_preview->set_texture(texture);
601
if (new_texture) {
602
// Reset spin max.
603
const Size2i size = texture->get_size();
604
split_sheet_size_x->set_max(size.x);
605
split_sheet_size_y->set_max(size.y);
606
split_sheet_sep_x->set_max(size.x);
607
split_sheet_sep_y->set_max(size.y);
608
split_sheet_offset_x->set_max(size.x);
609
split_sheet_offset_y->set_max(size.y);
610
611
if (size != previous_texture_size) {
612
// Different texture, reset to 4x4.
613
dominant_param = PARAM_FRAME_COUNT;
614
updating_split_settings = true;
615
const Size2i split_sheet = Size2i(4, 4);
616
split_sheet_h->set_value(split_sheet.x);
617
split_sheet_v->set_value(split_sheet.y);
618
split_sheet_size_x->set_value(size.x / split_sheet.x);
619
split_sheet_size_y->set_value(size.y / split_sheet.y);
620
split_sheet_sep_x->set_value(0);
621
split_sheet_sep_y->set_value(0);
622
split_sheet_offset_x->set_value(0);
623
split_sheet_offset_y->set_value(0);
624
updating_split_settings = false;
625
}
626
previous_texture_size = size;
627
628
// Reset zoom.
629
_sheet_zoom_reset();
630
}
631
632
split_sheet_dialog->popup_centered_ratio(0.65);
633
}
634
635
void SpriteFramesEditor::_notification(int p_what) {
636
switch (p_what) {
637
case NOTIFICATION_ENTER_TREE: {
638
get_tree()->connect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed));
639
640
[[fallthrough]];
641
}
642
case NOTIFICATION_THEME_CHANGED: {
643
autoplay_icon = get_editor_theme_icon(SNAME("AutoPlay"));
644
stop_icon = get_editor_theme_icon(SNAME("Stop"));
645
pause_icon = get_editor_theme_icon(SNAME("Pause"));
646
_update_stop_icon();
647
648
autoplay->set_button_icon(get_editor_theme_icon(SNAME("AutoPlay")));
649
anim_loop->set_button_icon(get_editor_theme_icon(SNAME("Loop")));
650
play->set_button_icon(get_editor_theme_icon(SNAME("PlayStart")));
651
play_from->set_button_icon(get_editor_theme_icon(SNAME("Play")));
652
play_bw->set_button_icon(get_editor_theme_icon(SNAME("PlayStartBackwards")));
653
play_bw_from->set_button_icon(get_editor_theme_icon(SNAME("PlayBackwards")));
654
655
load->set_button_icon(get_editor_theme_icon(SNAME("Load")));
656
load_sheet->set_button_icon(get_editor_theme_icon(SNAME("SpriteSheet")));
657
copy->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));
658
paste->set_button_icon(get_editor_theme_icon(SNAME("ActionPaste")));
659
empty_before->set_button_icon(get_editor_theme_icon(SNAME("InsertBefore")));
660
empty_after->set_button_icon(get_editor_theme_icon(SNAME("InsertAfter")));
661
move_up->set_button_icon(get_editor_theme_icon(SNAME("MoveLeft")));
662
move_down->set_button_icon(get_editor_theme_icon(SNAME("MoveRight")));
663
delete_frame->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
664
zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));
665
zoom_reset->set_button_icon(get_editor_theme_icon(SNAME("ZoomReset")));
666
zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));
667
add_anim->set_button_icon(get_editor_theme_icon(SNAME("New")));
668
duplicate_anim->set_button_icon(get_editor_theme_icon(SNAME("Duplicate")));
669
delete_anim->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
670
anim_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
671
split_sheet_zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));
672
split_sheet_zoom_reset->set_button_icon(get_editor_theme_icon(SNAME("ZoomReset")));
673
split_sheet_zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));
674
split_sheet_scroll->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
675
676
_update_show_settings();
677
} break;
678
679
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
680
case NOTIFICATION_TRANSLATION_CHANGED: {
681
_update_show_settings();
682
} break;
683
684
case NOTIFICATION_READY: {
685
add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up.
686
} break;
687
688
case NOTIFICATION_EXIT_TREE: {
689
get_tree()->disconnect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed));
690
} break;
691
}
692
}
693
694
void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_at_pos) {
695
ERR_FAIL_COND(!frames->has_animation(edited_anim));
696
697
List<Ref<Texture2D>> resources;
698
699
for (int i = 0; i < p_path.size(); i++) {
700
Ref<Texture2D> resource;
701
resource = ResourceLoader::load(p_path[i]);
702
703
if (resource.is_null()) {
704
dialog->set_text(TTR("ERROR: Couldn't load frame resource!"));
705
dialog->set_title(TTR("Error!"));
706
707
//dialog->get_cancel()->set_text("Close");
708
dialog->set_ok_button_text(TTR("Close"));
709
dialog->popup_centered();
710
return; ///beh should show an error i guess
711
}
712
713
resources.push_back(resource);
714
}
715
716
if (resources.is_empty()) {
717
return;
718
}
719
720
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
721
undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());
722
int fc = frames->get_frame_count(edited_anim);
723
724
int count = 0;
725
726
for (const Ref<Texture2D> &E : resources) {
727
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, E, 1.0, p_at_pos == -1 ? -1 : p_at_pos + count);
728
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos);
729
count++;
730
}
731
undo_redo->add_do_method(this, "_update_library");
732
undo_redo->add_undo_method(this, "_update_library");
733
734
undo_redo->commit_action();
735
}
736
737
Size2i SpriteFramesEditor::_get_frame_count() const {
738
return Size2i(split_sheet_h->get_value(), split_sheet_v->get_value());
739
}
740
741
Size2i SpriteFramesEditor::_get_frame_size() const {
742
return Size2i(split_sheet_size_x->get_value(), split_sheet_size_y->get_value());
743
}
744
745
Size2i SpriteFramesEditor::_get_offset() const {
746
return Size2i(split_sheet_offset_x->get_value(), split_sheet_offset_y->get_value());
747
}
748
749
Size2i SpriteFramesEditor::_get_separation() const {
750
return Size2i(split_sheet_sep_x->get_value(), split_sheet_sep_y->get_value());
751
}
752
753
void SpriteFramesEditor::_load_pressed() {
754
ERR_FAIL_COND(!frames->has_animation(edited_anim));
755
loading_scene = false;
756
757
file->clear_filters();
758
List<String> extensions;
759
ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);
760
for (const String &extension : extensions) {
761
file->add_filter("*." + extension);
762
}
763
764
file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
765
file->popup_file_dialog();
766
}
767
768
void SpriteFramesEditor::_paste_pressed() {
769
ERR_FAIL_COND(!frames->has_animation(edited_anim));
770
771
Ref<ClipboardSpriteFrames> clipboard_frames = EditorSettings::get_singleton()->get_resource_clipboard();
772
if (clipboard_frames.is_valid()) {
773
_paste_frame_array(clipboard_frames);
774
return;
775
}
776
777
Ref<Texture2D> texture = EditorSettings::get_singleton()->get_resource_clipboard();
778
if (texture.is_valid()) {
779
_paste_texture(texture);
780
return;
781
}
782
}
783
784
void SpriteFramesEditor::_paste_frame_array(const Ref<ClipboardSpriteFrames> &p_clipboard_frames) {
785
if (p_clipboard_frames->frames.is_empty()) {
786
return;
787
}
788
789
Ref<Texture2D> texture;
790
float duration = 1.0;
791
792
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
793
undo_redo->create_action(TTR("Paste Frame(s)"), UndoRedo::MERGE_DISABLE, frames.ptr());
794
795
int undo_index = frames->get_frame_count(edited_anim);
796
797
for (int index = 0; index < p_clipboard_frames->frames.size(); index++) {
798
const ClipboardSpriteFrames::Frame &frame = p_clipboard_frames->frames[index];
799
texture = frame.texture;
800
duration = frame.duration;
801
802
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration);
803
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, undo_index);
804
}
805
806
undo_redo->add_do_method(this, "_update_library");
807
undo_redo->add_undo_method(this, "_update_library");
808
undo_redo->commit_action();
809
}
810
811
void SpriteFramesEditor::_paste_texture(const Ref<Texture2D> &p_texture) {
812
float duration = 1.0;
813
814
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
815
undo_redo->create_action(TTR("Paste Texture"), UndoRedo::MERGE_DISABLE, frames.ptr());
816
817
int undo_index = frames->get_frame_count(edited_anim);
818
819
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, p_texture, duration);
820
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, undo_index);
821
822
undo_redo->add_do_method(this, "_update_library");
823
undo_redo->add_undo_method(this, "_update_library");
824
undo_redo->commit_action();
825
}
826
827
void SpriteFramesEditor::_copy_pressed() {
828
ERR_FAIL_COND(!frames->has_animation(edited_anim));
829
830
Vector<int> selected_items = frame_list->get_selected_items();
831
832
if (selected_items.is_empty()) {
833
return;
834
}
835
836
Ref<ClipboardSpriteFrames> clipboard_frames = memnew(ClipboardSpriteFrames);
837
838
for (const int &frame_index : selected_items) {
839
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, frame_index);
840
if (texture.is_null()) {
841
continue;
842
}
843
844
ClipboardSpriteFrames::Frame frame;
845
frame.texture = texture;
846
frame.duration = frames->get_frame_duration(edited_anim, frame_index);
847
848
clipboard_frames->frames.push_back(frame);
849
}
850
EditorSettings::get_singleton()->set_resource_clipboard(clipboard_frames);
851
}
852
853
void SpriteFramesEditor::_empty_pressed() {
854
ERR_FAIL_COND(!frames->has_animation(edited_anim));
855
856
int from = -1;
857
Vector<int> selected_items = frame_list->get_selected_items();
858
859
if (!selected_items.is_empty()) {
860
from = selected_items[0];
861
selection.clear();
862
selection.push_back(from + 1);
863
} else {
864
from = frames->get_frame_count(edited_anim);
865
}
866
867
Ref<Texture2D> texture;
868
869
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
870
undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, frames.ptr());
871
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from);
872
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from);
873
undo_redo->add_do_method(this, "_update_library");
874
undo_redo->add_undo_method(this, "_update_library");
875
undo_redo->commit_action();
876
}
877
878
void SpriteFramesEditor::_empty2_pressed() {
879
ERR_FAIL_COND(!frames->has_animation(edited_anim));
880
881
int from = -1;
882
Vector<int> selected_items = frame_list->get_selected_items();
883
884
if (!selected_items.is_empty()) {
885
from = selected_items[selected_items.size() - 1];
886
selection.clear();
887
selection.push_back(from);
888
} else {
889
from = frames->get_frame_count(edited_anim);
890
}
891
892
Ref<Texture2D> texture;
893
894
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
895
undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, frames.ptr());
896
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from + 1);
897
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from + 1);
898
undo_redo->add_do_method(this, "_update_library");
899
undo_redo->add_undo_method(this, "_update_library");
900
undo_redo->commit_action();
901
}
902
903
void SpriteFramesEditor::_up_pressed() {
904
ERR_FAIL_COND(!frames->has_animation(edited_anim));
905
906
Vector<int> selected_items = frame_list->get_selected_items();
907
908
int nb_selected_items = selected_items.size();
909
if (nb_selected_items <= 0) {
910
return;
911
}
912
913
int first_selected_frame_index = selected_items[0];
914
if (first_selected_frame_index < 1) {
915
return;
916
}
917
918
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
919
undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());
920
921
int last_overwritten_frame = -1;
922
923
for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {
924
int to_move = selected_items[selected_index];
925
int new_index = to_move - 1;
926
selected_items.set(selected_index, new_index);
927
928
undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move));
929
undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, new_index), frames->get_frame_duration(edited_anim, new_index));
930
931
bool is_next_item_in_selection = selected_index + 1 < nb_selected_items && selected_items[selected_index + 1] == to_move + 1;
932
if (last_overwritten_frame == -1) {
933
last_overwritten_frame = new_index;
934
}
935
936
if (!is_next_item_in_selection) {
937
undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, last_overwritten_frame), frames->get_frame_duration(edited_anim, last_overwritten_frame));
938
undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move));
939
last_overwritten_frame = -1;
940
}
941
}
942
selection = selected_items;
943
944
undo_redo->add_do_method(this, "_update_library");
945
undo_redo->add_undo_method(this, "_update_library");
946
undo_redo->commit_action();
947
}
948
949
void SpriteFramesEditor::_down_pressed() {
950
ERR_FAIL_COND(!frames->has_animation(edited_anim));
951
952
Vector<int> selected_items = frame_list->get_selected_items();
953
954
int nb_selected_items = selected_items.size();
955
if (nb_selected_items <= 0) {
956
return;
957
}
958
959
int last_selected_frame_index = selected_items[nb_selected_items - 1];
960
if (last_selected_frame_index >= frames->get_frame_count(edited_anim) - 1) {
961
return;
962
}
963
964
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
965
undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());
966
967
int first_moved_frame = -1;
968
969
for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {
970
int to_move = selected_items[selected_index];
971
int new_index = to_move + 1;
972
selected_items.set(selected_index, new_index);
973
974
undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move));
975
undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, new_index), frames->get_frame_duration(edited_anim, new_index));
976
977
bool is_next_item_in_selection = selected_index + 1 < nb_selected_items && selected_items[selected_index + 1] == new_index;
978
if (first_moved_frame == -1) {
979
first_moved_frame = to_move;
980
}
981
982
if (!is_next_item_in_selection) {
983
undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, first_moved_frame, frames->get_frame_texture(edited_anim, new_index), frames->get_frame_duration(edited_anim, new_index));
984
undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, first_moved_frame, frames->get_frame_texture(edited_anim, first_moved_frame), frames->get_frame_duration(edited_anim, first_moved_frame));
985
first_moved_frame = -1;
986
}
987
}
988
selection = selected_items;
989
990
undo_redo->add_do_method(this, "_update_library");
991
undo_redo->add_undo_method(this, "_update_library");
992
undo_redo->commit_action();
993
}
994
995
void SpriteFramesEditor::_delete_pressed() {
996
ERR_FAIL_COND(!frames->has_animation(edited_anim));
997
998
Vector<int> selected_items = frame_list->get_selected_items();
999
1000
int nb_selected_items = selected_items.size();
1001
if (nb_selected_items <= 0) {
1002
return;
1003
}
1004
1005
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1006
undo_redo->create_action(TTR("Delete Resource"), UndoRedo::MERGE_DISABLE, frames.ptr());
1007
for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {
1008
int to_delete = selected_items[selected_index];
1009
undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, to_delete - selected_index);
1010
undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, frames->get_frame_texture(edited_anim, to_delete), frames->get_frame_duration(edited_anim, to_delete), to_delete);
1011
}
1012
1013
undo_redo->add_do_method(this, "_update_library");
1014
undo_redo->add_undo_method(this, "_update_library");
1015
undo_redo->commit_action();
1016
}
1017
1018
void SpriteFramesEditor::_animation_selected() {
1019
if (updating) {
1020
return;
1021
}
1022
1023
TreeItem *selected = animations->get_selected();
1024
ERR_FAIL_NULL(selected);
1025
edited_anim = selected->get_text(0);
1026
1027
if (animated_sprite) {
1028
sprite_node_updating = true;
1029
animated_sprite->call("set_animation", edited_anim);
1030
sprite_node_updating = false;
1031
}
1032
1033
_update_library(true);
1034
}
1035
1036
void SpriteFramesEditor::_sync_animation() {
1037
if (!animated_sprite || sprite_node_updating) {
1038
return;
1039
}
1040
_select_animation(animated_sprite->call("get_animation"), false);
1041
_update_stop_icon();
1042
}
1043
1044
void SpriteFramesEditor::_select_animation(const String &p_name, bool p_update_node) {
1045
if (frames.is_null() || !frames->has_animation(p_name)) {
1046
return;
1047
}
1048
edited_anim = p_name;
1049
1050
if (animated_sprite) {
1051
if (p_update_node) {
1052
animated_sprite->call("set_animation", edited_anim);
1053
}
1054
}
1055
1056
_update_library();
1057
}
1058
1059
static void _find_anim_sprites(Node *p_node, List<Node *> *r_nodes, Ref<SpriteFrames> p_sfames) {
1060
Node *edited = EditorNode::get_singleton()->get_edited_scene();
1061
if (!edited) {
1062
return;
1063
}
1064
if (p_node != edited && p_node->get_owner() != edited) {
1065
return;
1066
}
1067
1068
{
1069
AnimatedSprite2D *as = Object::cast_to<AnimatedSprite2D>(p_node);
1070
if (as && as->get_sprite_frames() == p_sfames) {
1071
r_nodes->push_back(p_node);
1072
}
1073
}
1074
1075
{
1076
AnimatedSprite3D *as = Object::cast_to<AnimatedSprite3D>(p_node);
1077
if (as && as->get_sprite_frames() == p_sfames) {
1078
r_nodes->push_back(p_node);
1079
}
1080
}
1081
1082
for (int i = 0; i < p_node->get_child_count(); i++) {
1083
_find_anim_sprites(p_node->get_child(i), r_nodes, p_sfames);
1084
}
1085
}
1086
1087
void SpriteFramesEditor::_animation_name_edited() {
1088
if (updating) {
1089
return;
1090
}
1091
1092
if (!frames->has_animation(edited_anim)) {
1093
return;
1094
}
1095
1096
TreeItem *edited = animations->get_edited();
1097
if (!edited) {
1098
return;
1099
}
1100
1101
String new_name = edited->get_text(0);
1102
1103
if (new_name == String(edited_anim)) {
1104
return;
1105
}
1106
1107
if (new_name.is_empty()) {
1108
new_name = "new_animation";
1109
}
1110
1111
new_name = new_name.replace_char('/', '_').replace_char(',', ' ');
1112
1113
String name = new_name;
1114
int counter = 0;
1115
while (frames->has_animation(name)) {
1116
if (name == String(edited_anim)) {
1117
edited->set_text(0, name); // The name didn't change, just updated the column text to name.
1118
return;
1119
}
1120
counter++;
1121
name = new_name + "_" + itos(counter);
1122
}
1123
edited->set_text(0, name);
1124
1125
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1126
undo_redo->create_action(TTR("Rename Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());
1127
undo_redo->add_do_method(frames.ptr(), "rename_animation", edited_anim, name);
1128
undo_redo->add_undo_method(frames.ptr(), "rename_animation", name, edited_anim);
1129
_rename_node_animation(undo_redo, false, edited_anim, name, name);
1130
_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);
1131
undo_redo->add_do_method(this, "_select_animation", name);
1132
undo_redo->add_undo_method(this, "_select_animation", edited_anim);
1133
undo_redo->add_do_method(this, "_update_library");
1134
undo_redo->add_undo_method(this, "_update_library");
1135
undo_redo->commit_action();
1136
1137
animations->grab_focus();
1138
}
1139
1140
void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo, bool is_undo, const String &p_filter, const String &p_new_animation, const String &p_new_autoplay) {
1141
List<Node *> nodes;
1142
_find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref<SpriteFrames>(frames));
1143
1144
if (is_undo) {
1145
for (Node *E : nodes) {
1146
String current_name = E->call("get_animation");
1147
if (current_name == p_filter) {
1148
undo_redo->force_fixed_history(); // Fixes corner-case when editing SpriteFrames stored as separate file.
1149
undo_redo->add_undo_method(E, "set_animation", p_new_animation);
1150
}
1151
String autoplay_name = E->call("get_autoplay");
1152
if (autoplay_name == p_filter) {
1153
undo_redo->force_fixed_history();
1154
undo_redo->add_undo_method(E, "set_autoplay", p_new_autoplay);
1155
}
1156
}
1157
} else {
1158
for (Node *E : nodes) {
1159
String current_name = E->call("get_animation");
1160
if (current_name == p_filter) {
1161
undo_redo->force_fixed_history();
1162
undo_redo->add_do_method(E, "set_animation", p_new_animation);
1163
}
1164
String autoplay_name = E->call("get_autoplay");
1165
if (autoplay_name == p_filter) {
1166
undo_redo->force_fixed_history();
1167
undo_redo->add_do_method(E, "set_autoplay", p_new_autoplay);
1168
}
1169
}
1170
}
1171
}
1172
1173
void SpriteFramesEditor::_animation_add() {
1174
String name = "new_animation";
1175
int counter = 0;
1176
while (frames->has_animation(name)) {
1177
counter++;
1178
name = vformat("new_animation_%d", counter);
1179
}
1180
1181
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1182
undo_redo->create_action(TTR("Add Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());
1183
undo_redo->add_do_method(frames.ptr(), "add_animation", name);
1184
undo_redo->add_undo_method(frames.ptr(), "remove_animation", name);
1185
undo_redo->add_do_method(this, "_select_animation", name);
1186
undo_redo->add_undo_method(this, "_select_animation", edited_anim);
1187
undo_redo->add_do_method(this, "_update_library");
1188
undo_redo->add_undo_method(this, "_update_library");
1189
undo_redo->commit_action();
1190
1191
animations->grab_focus();
1192
}
1193
1194
void SpriteFramesEditor::_animation_duplicate() {
1195
if (updating) {
1196
return;
1197
}
1198
1199
if (!frames->has_animation(edited_anim)) {
1200
return;
1201
}
1202
1203
int counter = 1;
1204
String new_name = edited_anim;
1205
PackedStringArray name_component = new_name.rsplit("_", true, 1);
1206
String base_name = name_component[0];
1207
if (name_component.size() > 1 && name_component[1].is_valid_int() && name_component[1].to_int() >= 0) {
1208
counter = name_component[1].to_int();
1209
}
1210
new_name = base_name + "_" + itos(counter);
1211
while (frames->has_animation(new_name)) {
1212
counter++;
1213
new_name = base_name + "_" + itos(counter);
1214
}
1215
1216
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1217
undo_redo->create_action(TTR("Duplicate Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());
1218
undo_redo->add_do_method(frames.ptr(), "duplicate_animation", edited_anim, new_name);
1219
undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name);
1220
undo_redo->add_do_method(this, "_select_animation", new_name);
1221
undo_redo->add_undo_method(this, "_select_animation", edited_anim);
1222
undo_redo->add_do_method(this, "_update_library");
1223
undo_redo->add_undo_method(this, "_update_library");
1224
undo_redo->commit_action();
1225
1226
animations->grab_focus();
1227
}
1228
1229
void SpriteFramesEditor::_animation_remove() {
1230
if (updating) {
1231
return;
1232
}
1233
1234
if (!frames->has_animation(edited_anim)) {
1235
return;
1236
}
1237
1238
delete_dialog->set_text(TTR("Delete Animation?"));
1239
delete_dialog->popup_centered();
1240
}
1241
1242
void SpriteFramesEditor::_animation_remove_confirmed() {
1243
StringName new_edited;
1244
List<StringName> anim_names;
1245
frames->get_animation_list(&anim_names);
1246
anim_names.sort_custom<StringName::AlphCompare>();
1247
if (anim_names.size() >= 2) {
1248
if (edited_anim == anim_names.get(0)) {
1249
new_edited = anim_names.get(1);
1250
} else {
1251
new_edited = anim_names.get(0);
1252
}
1253
} else {
1254
new_edited = StringName();
1255
}
1256
1257
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1258
undo_redo->create_action(TTR("Remove Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());
1259
_rename_node_animation(undo_redo, false, edited_anim, new_edited, "");
1260
undo_redo->add_do_method(frames.ptr(), "remove_animation", edited_anim);
1261
undo_redo->add_undo_method(frames.ptr(), "add_animation", edited_anim);
1262
_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);
1263
undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));
1264
undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));
1265
int fc = frames->get_frame_count(edited_anim);
1266
for (int i = 0; i < fc; i++) {
1267
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);
1268
float duration = frames->get_frame_duration(edited_anim, i);
1269
undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration);
1270
}
1271
undo_redo->add_do_method(this, "_select_animation", new_edited);
1272
undo_redo->add_undo_method(this, "_select_animation", edited_anim);
1273
undo_redo->add_do_method(this, "_update_library");
1274
undo_redo->add_undo_method(this, "_update_library");
1275
undo_redo->commit_action();
1276
}
1277
1278
void SpriteFramesEditor::_animation_search_text_changed(const String &p_text) {
1279
_update_library();
1280
}
1281
1282
void SpriteFramesEditor::_animation_loop_changed() {
1283
if (updating) {
1284
return;
1285
}
1286
1287
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1288
undo_redo->create_action(TTR("Change Animation Loop"), UndoRedo::MERGE_DISABLE, frames.ptr());
1289
undo_redo->add_do_method(frames.ptr(), "set_animation_loop", edited_anim, anim_loop->is_pressed());
1290
undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));
1291
undo_redo->add_do_method(this, "_update_library", true);
1292
undo_redo->add_undo_method(this, "_update_library", true);
1293
undo_redo->commit_action();
1294
}
1295
1296
void SpriteFramesEditor::_animation_speed_resized() {
1297
anim_speed->update_minimum_size();
1298
}
1299
1300
void SpriteFramesEditor::_animation_speed_changed(double p_value) {
1301
if (frames.is_null()) {
1302
return;
1303
}
1304
1305
if (updating) {
1306
return;
1307
}
1308
1309
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1310
undo_redo->create_action(TTR("Change Animation FPS"), UndoRedo::MERGE_ENDS, frames.ptr());
1311
undo_redo->add_do_method(frames.ptr(), "set_animation_speed", edited_anim, p_value);
1312
undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));
1313
undo_redo->add_do_method(this, "_update_library", true);
1314
undo_redo->add_undo_method(this, "_update_library", true);
1315
undo_redo->commit_action();
1316
}
1317
1318
void SpriteFramesEditor::_frame_list_gui_input(const Ref<InputEvent> &p_event) {
1319
const Ref<InputEventMouseButton> mb = p_event;
1320
1321
if (mb.is_valid()) {
1322
if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {
1323
_zoom_in();
1324
// Don't scroll up after zooming in.
1325
accept_event();
1326
} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {
1327
_zoom_out();
1328
// Don't scroll down after zooming out.
1329
accept_event();
1330
} else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
1331
Point2 pos = mb->get_position();
1332
right_clicked_frame = frame_list->get_item_at_position(pos, true);
1333
if (right_clicked_frame != -1) {
1334
Ref<Texture2D> tex = frames->get_frame_texture(edited_anim, right_clicked_frame);
1335
if (tex.is_null()) {
1336
return;
1337
}
1338
if (!menu) {
1339
menu = memnew(PopupMenu);
1340
add_child(menu);
1341
menu->connect(SceneStringName(id_pressed), callable_mp(this, &SpriteFramesEditor::_menu_selected));
1342
menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTRC("Show in FileSystem"), MENU_SHOW_IN_FILESYSTEM);
1343
}
1344
1345
menu->set_position(get_screen_position() + get_local_mouse_position());
1346
menu->popup();
1347
}
1348
}
1349
}
1350
}
1351
1352
void SpriteFramesEditor::_menu_selected(int p_id) {
1353
switch (p_id) {
1354
case MENU_SHOW_IN_FILESYSTEM: {
1355
Ref<Texture2D> frame_texture = frames->get_frame_texture(edited_anim, right_clicked_frame);
1356
ERR_FAIL_COND(frame_texture.is_null());
1357
String path = frame_texture->get_path();
1358
// Check if the file is an atlas resource, if it is find the source texture.
1359
Ref<AtlasTexture> at = frame_texture;
1360
while (at.is_valid() && at->get_atlas().is_valid()) {
1361
path = at->get_atlas()->get_path();
1362
at = at->get_atlas();
1363
}
1364
FileSystemDock::get_singleton()->navigate_to_path(path);
1365
} break;
1366
}
1367
}
1368
1369
void SpriteFramesEditor::_frame_list_item_selected(int p_index, bool p_selected) {
1370
if (updating) {
1371
return;
1372
}
1373
1374
selection = frame_list->get_selected_items();
1375
if (selection.is_empty() || !p_selected) {
1376
return;
1377
}
1378
1379
updating = true;
1380
frame_duration->set_value(frames->get_frame_duration(edited_anim, selection[0]));
1381
updating = false;
1382
}
1383
1384
void SpriteFramesEditor::_frame_duration_changed(double p_value) {
1385
if (frames.is_null()) {
1386
return;
1387
}
1388
1389
if (updating) {
1390
return;
1391
}
1392
1393
if (selection.is_empty()) {
1394
return;
1395
}
1396
1397
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1398
undo_redo->create_action(TTR("Set Frame Duration"), UndoRedo::MERGE_ENDS, frames.ptr());
1399
1400
for (const int &index : selection) {
1401
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, index);
1402
float old_duration = frames->get_frame_duration(edited_anim, index);
1403
1404
undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, index, texture, p_value);
1405
undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, index, texture, old_duration);
1406
}
1407
1408
undo_redo->add_do_method(this, "_update_library");
1409
undo_redo->add_undo_method(this, "_update_library");
1410
undo_redo->commit_action();
1411
}
1412
1413
void SpriteFramesEditor::_zoom_in() {
1414
// Do not zoom in or out with no visible frames
1415
if (frames->get_frame_count(edited_anim) <= 0) {
1416
return;
1417
}
1418
if (thumbnail_zoom < max_thumbnail_zoom) {
1419
thumbnail_zoom *= scale_ratio;
1420
int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);
1421
frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);
1422
frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
1423
}
1424
}
1425
1426
void SpriteFramesEditor::_zoom_out() {
1427
// Do not zoom in or out with no visible frames
1428
if (frames->get_frame_count(edited_anim) <= 0) {
1429
return;
1430
}
1431
if (thumbnail_zoom > min_thumbnail_zoom) {
1432
thumbnail_zoom /= scale_ratio;
1433
int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);
1434
frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);
1435
frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));
1436
}
1437
}
1438
1439
void SpriteFramesEditor::_zoom_reset() {
1440
thumbnail_zoom = MAX(1.0f, EDSCALE);
1441
frame_list->set_fixed_column_width(thumbnail_default_size * 3 / 2);
1442
frame_list->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size));
1443
}
1444
1445
void SpriteFramesEditor::_update_library(bool p_skip_selector) {
1446
if (!p_skip_selector) {
1447
animations_dirty = true;
1448
}
1449
1450
if (pending_update) {
1451
return;
1452
}
1453
pending_update = true;
1454
callable_mp(this, &SpriteFramesEditor::_update_library_impl).call_deferred();
1455
}
1456
1457
void SpriteFramesEditor::_update_library_impl() {
1458
pending_update = false;
1459
1460
if (frames.is_null()) {
1461
return;
1462
}
1463
1464
updating = true;
1465
1466
frame_duration->set_value_no_signal(1.0); // Default.
1467
1468
if (animations_dirty) {
1469
animations_dirty = false;
1470
animations->clear();
1471
1472
TreeItem *anim_root = animations->create_item();
1473
1474
List<StringName> anim_names;
1475
frames->get_animation_list(&anim_names);
1476
anim_names.sort_custom<StringName::AlphCompare>();
1477
if (!anim_names.size()) {
1478
missing_anim_label->show();
1479
anim_frames_vb->hide();
1480
return;
1481
}
1482
missing_anim_label->hide();
1483
anim_frames_vb->show();
1484
bool searching = anim_search_box->get_text().size();
1485
String searched_string = searching ? anim_search_box->get_text().to_lower() : String();
1486
1487
TreeItem *selected = nullptr;
1488
for (const StringName &E : anim_names) {
1489
String name = E;
1490
if (searching && !name.to_lower().contains(searched_string)) {
1491
continue;
1492
}
1493
TreeItem *it = animations->create_item(anim_root);
1494
it->set_metadata(0, name);
1495
it->set_text(0, name);
1496
it->set_editable(0, true);
1497
if (animated_sprite) {
1498
if (name == String(animated_sprite->call("get_autoplay"))) {
1499
it->set_icon(0, autoplay_icon);
1500
}
1501
}
1502
if (E == edited_anim) {
1503
it->select(0);
1504
selected = it;
1505
}
1506
}
1507
if (selected) {
1508
animations->scroll_to_item(selected);
1509
}
1510
}
1511
1512
if (animated_sprite) {
1513
String autoplay_name = animated_sprite->call("get_autoplay");
1514
if (autoplay_name.is_empty()) {
1515
autoplay->set_pressed_no_signal(false);
1516
} else {
1517
autoplay->set_pressed_no_signal(String(edited_anim) == autoplay_name);
1518
}
1519
}
1520
1521
frame_list->clear();
1522
1523
if (!frames->has_animation(edited_anim)) {
1524
updating = false;
1525
return;
1526
}
1527
1528
int anim_frame_count = frames->get_frame_count(edited_anim);
1529
if (anim_frame_count == 0) {
1530
selection.clear();
1531
}
1532
1533
for (int index = 0; index < selection.size(); index++) {
1534
int sel = selection[index];
1535
if (sel == -1) {
1536
selection.remove_at(index);
1537
index--;
1538
}
1539
if (sel >= anim_frame_count) {
1540
selection.set(index, anim_frame_count - 1);
1541
// Since selection is ordered, if we get a frame that is outside of the range
1542
// we can clip all the other one.
1543
selection.resize(index + 1);
1544
break;
1545
}
1546
}
1547
1548
if (selection.is_empty() && frames->get_frame_count(edited_anim)) {
1549
selection.push_back(0);
1550
}
1551
1552
bool is_first_selection = true;
1553
for (int i = 0; i < frames->get_frame_count(edited_anim); i++) {
1554
String name = itos(i);
1555
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);
1556
float duration = frames->get_frame_duration(edited_anim, i);
1557
1558
if (texture.is_null()) {
1559
texture = empty_icon;
1560
name += ": " + TTR("(empty)");
1561
} else if (!texture->get_name().is_empty()) {
1562
name += ": " + texture->get_name();
1563
}
1564
1565
if (duration != 1.0f) {
1566
name += String::utf8(" [× ") + String::num(duration, 2) + "]";
1567
}
1568
1569
frame_list->add_item(name, texture);
1570
if (texture.is_valid()) {
1571
String tooltip = texture->get_path();
1572
1573
// Frame is often saved as an AtlasTexture subresource within a scene/resource file,
1574
// thus its path might be not what the user is looking for. So we're also showing
1575
// subsequent source texture paths.
1576
String prefix = U"┖╴";
1577
Ref<AtlasTexture> at = texture;
1578
while (at.is_valid() && at->get_atlas().is_valid()) {
1579
tooltip += "\n" + prefix + at->get_atlas()->get_path();
1580
prefix = " " + prefix;
1581
at = at->get_atlas();
1582
}
1583
1584
frame_list->set_item_tooltip(-1, tooltip);
1585
}
1586
if (selection.has(i)) {
1587
frame_list->select(frame_list->get_item_count() - 1, is_first_selection);
1588
if (is_first_selection) {
1589
frame_duration->set_value_no_signal(frames->get_frame_duration(edited_anim, i));
1590
}
1591
is_first_selection = false;
1592
}
1593
}
1594
1595
anim_speed->set_value_no_signal(frames->get_animation_speed(edited_anim));
1596
anim_loop->set_pressed_no_signal(frames->get_animation_loop(edited_anim));
1597
1598
updating = false;
1599
}
1600
1601
void SpriteFramesEditor::_edit() {
1602
if (!animated_sprite) {
1603
return;
1604
}
1605
edit(animated_sprite->call("get_sprite_frames"));
1606
}
1607
1608
void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {
1609
_update_stop_icon();
1610
1611
if (p_frames.is_null()) {
1612
frames.unref();
1613
_remove_sprite_node();
1614
hide();
1615
return;
1616
}
1617
1618
frames = p_frames;
1619
read_only = EditorNode::get_singleton()->is_resource_read_only(p_frames);
1620
1621
if (!p_frames->has_animation(edited_anim)) {
1622
List<StringName> anim_names;
1623
frames->get_animation_list(&anim_names);
1624
anim_names.sort_custom<StringName::AlphCompare>();
1625
if (anim_names.size()) {
1626
edited_anim = anim_names.front()->get();
1627
} else {
1628
edited_anim = StringName();
1629
}
1630
}
1631
1632
_update_library();
1633
// Clear zoom and split sheet texture
1634
split_sheet_preview->set_texture(Ref<Texture2D>());
1635
_zoom_reset();
1636
1637
add_anim->set_disabled(read_only);
1638
duplicate_anim->set_disabled(read_only);
1639
delete_anim->set_disabled(read_only);
1640
anim_speed->set_editable(!read_only);
1641
anim_loop->set_disabled(read_only);
1642
load->set_disabled(read_only);
1643
load_sheet->set_disabled(read_only);
1644
copy->set_disabled(read_only);
1645
paste->set_disabled(read_only);
1646
empty_before->set_disabled(read_only);
1647
empty_after->set_disabled(read_only);
1648
move_up->set_disabled(read_only);
1649
move_down->set_disabled(read_only);
1650
delete_frame->set_disabled(read_only);
1651
1652
_fetch_sprite_node(); // Fetch node after set frames.
1653
}
1654
1655
Ref<SpriteFrames> SpriteFramesEditor::get_sprite_frames() const {
1656
return frames;
1657
}
1658
1659
Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
1660
if (read_only) {
1661
return false;
1662
}
1663
1664
if (!frames->has_animation(edited_anim)) {
1665
return false;
1666
}
1667
1668
int idx = -1;
1669
if (p_point == Vector2(Math::INF, Math::INF)) {
1670
if (frame_list->is_anything_selected()) {
1671
idx = frame_list->get_selected_items()[0];
1672
}
1673
} else {
1674
idx = frame_list->get_item_at_position(p_point, true);
1675
}
1676
1677
if (idx < 0 || idx >= frames->get_frame_count(edited_anim)) {
1678
return Variant();
1679
}
1680
1681
Ref<Resource> frame = frames->get_frame_texture(edited_anim, idx);
1682
1683
if (frame.is_null()) {
1684
return Variant();
1685
}
1686
1687
Dictionary drag_data = EditorNode::get_singleton()->drag_resource(frame, p_from);
1688
drag_data["frame"] = idx; // store the frame, in case we want to reorder frames inside 'drop_data_fw'
1689
return drag_data;
1690
}
1691
1692
bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
1693
if (read_only) {
1694
return false;
1695
}
1696
1697
Dictionary d = p_data;
1698
1699
if (!d.has("type")) {
1700
return false;
1701
}
1702
1703
// reordering frames
1704
if (d.has("from") && (Object *)(d["from"]) == frame_list) {
1705
return true;
1706
}
1707
1708
if (String(d["type"]) == "resource" && d.has("resource")) {
1709
Ref<Resource> r = d["resource"];
1710
1711
Ref<Texture2D> texture = r;
1712
1713
if (texture.is_valid()) {
1714
return true;
1715
}
1716
}
1717
1718
if (String(d["type"]) == "files") {
1719
Vector<String> files = d["files"];
1720
1721
if (files.is_empty()) {
1722
return false;
1723
}
1724
1725
for (int i = 0; i < files.size(); i++) {
1726
const String &f = files[i];
1727
String ftype = EditorFileSystem::get_singleton()->get_file_type(f);
1728
1729
if (!ClassDB::is_parent_class(ftype, "Texture2D")) {
1730
return false;
1731
}
1732
}
1733
1734
return true;
1735
}
1736
return false;
1737
}
1738
1739
void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
1740
if (!can_drop_data_fw(p_point, p_data, p_from)) {
1741
return;
1742
}
1743
1744
Dictionary d = p_data;
1745
1746
if (!d.has("type")) {
1747
return;
1748
}
1749
1750
int at_pos = -1;
1751
if (p_point == Vector2(Math::INF, Math::INF)) {
1752
if (frame_list->is_anything_selected()) {
1753
at_pos = frame_list->get_selected_items()[0];
1754
}
1755
} else {
1756
at_pos = frame_list->get_item_at_position(p_point, true);
1757
}
1758
1759
if (String(d["type"]) == "resource" && d.has("resource")) {
1760
Ref<Resource> r = d["resource"];
1761
1762
Ref<Texture2D> texture = r;
1763
1764
if (texture.is_valid()) {
1765
bool reorder = false;
1766
if (d.has("from") && (Object *)(d["from"]) == frame_list) {
1767
reorder = true;
1768
}
1769
1770
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1771
if (reorder) { //drop is from reordering frames
1772
int from_frame = -1;
1773
float duration = 1.0;
1774
if (d.has("frame")) {
1775
from_frame = d["frame"];
1776
duration = frames->get_frame_duration(edited_anim, from_frame);
1777
}
1778
1779
undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());
1780
undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame);
1781
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration, at_pos == -1 ? -1 : at_pos);
1782
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos);
1783
undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration, from_frame);
1784
undo_redo->add_do_method(this, "_update_library");
1785
undo_redo->add_undo_method(this, "_update_library");
1786
undo_redo->commit_action();
1787
} else {
1788
undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());
1789
undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, at_pos == -1 ? -1 : at_pos);
1790
undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos);
1791
undo_redo->add_do_method(this, "_update_library");
1792
undo_redo->add_undo_method(this, "_update_library");
1793
undo_redo->commit_action();
1794
}
1795
}
1796
}
1797
1798
if (String(d["type"]) == "files") {
1799
Vector<String> files = d["files"];
1800
1801
if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
1802
_prepare_sprite_sheet(files[0]);
1803
} else {
1804
_file_load_request(files, at_pos);
1805
}
1806
}
1807
}
1808
1809
void SpriteFramesEditor::_update_stop_icon() {
1810
bool is_playing = false;
1811
if (animated_sprite) {
1812
is_playing = animated_sprite->call("is_playing");
1813
}
1814
if (is_playing) {
1815
stop->set_button_icon(pause_icon);
1816
} else {
1817
stop->set_button_icon(stop_icon);
1818
}
1819
}
1820
1821
void SpriteFramesEditor::_remove_sprite_node() {
1822
if (!animated_sprite) {
1823
return;
1824
}
1825
if (animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {
1826
animated_sprite->disconnect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));
1827
}
1828
if (animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {
1829
animated_sprite->disconnect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation));
1830
}
1831
if (animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {
1832
animated_sprite->disconnect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));
1833
}
1834
animated_sprite = nullptr;
1835
}
1836
1837
void SpriteFramesEditor::_fetch_sprite_node() {
1838
Node *selected = nullptr;
1839
EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
1840
const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();
1841
if (top_node_list.size() == 1) {
1842
selected = top_node_list.front()->get();
1843
}
1844
1845
bool show_node_edit = false;
1846
AnimatedSprite2D *as2d = Object::cast_to<AnimatedSprite2D>(selected);
1847
AnimatedSprite3D *as3d = Object::cast_to<AnimatedSprite3D>(selected);
1848
if (as2d || as3d) {
1849
if (frames != selected->call("get_sprite_frames")) {
1850
_remove_sprite_node();
1851
} else {
1852
animated_sprite = selected;
1853
if (!animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {
1854
animated_sprite->connect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));
1855
}
1856
if (!animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {
1857
animated_sprite->connect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation), CONNECT_DEFERRED);
1858
}
1859
if (!animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {
1860
animated_sprite->connect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));
1861
}
1862
show_node_edit = true;
1863
}
1864
} else {
1865
_remove_sprite_node();
1866
}
1867
1868
if (show_node_edit) {
1869
_sync_animation();
1870
autoplay_container->show();
1871
playback_container->show();
1872
} else {
1873
_update_library(); // To init autoplay icon.
1874
autoplay_container->hide();
1875
playback_container->hide();
1876
}
1877
}
1878
1879
void SpriteFramesEditor::_play_pressed() {
1880
if (animated_sprite) {
1881
animated_sprite->call("stop");
1882
animated_sprite->call("play", animated_sprite->call("get_animation"));
1883
}
1884
_update_stop_icon();
1885
}
1886
1887
void SpriteFramesEditor::_play_from_pressed() {
1888
if (animated_sprite) {
1889
animated_sprite->call("play", animated_sprite->call("get_animation"));
1890
}
1891
_update_stop_icon();
1892
}
1893
1894
void SpriteFramesEditor::_play_bw_pressed() {
1895
if (animated_sprite) {
1896
animated_sprite->call("stop");
1897
animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));
1898
}
1899
_update_stop_icon();
1900
}
1901
1902
void SpriteFramesEditor::_play_bw_from_pressed() {
1903
if (animated_sprite) {
1904
animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));
1905
}
1906
_update_stop_icon();
1907
}
1908
1909
void SpriteFramesEditor::_stop_pressed() {
1910
if (animated_sprite) {
1911
if (animated_sprite->call("is_playing")) {
1912
animated_sprite->call("pause");
1913
} else {
1914
animated_sprite->call("stop");
1915
}
1916
}
1917
_update_stop_icon();
1918
}
1919
1920
void SpriteFramesEditor::_autoplay_pressed() {
1921
if (updating) {
1922
return;
1923
}
1924
1925
if (animated_sprite) {
1926
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1927
undo_redo->create_action(TTR("Toggle Autoplay"), UndoRedo::MERGE_DISABLE, animated_sprite);
1928
String current = animated_sprite->call("get_animation");
1929
String current_auto = animated_sprite->call("get_autoplay");
1930
if (current == current_auto) {
1931
//unset
1932
undo_redo->add_do_method(animated_sprite, "set_autoplay", "");
1933
undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);
1934
} else {
1935
//set
1936
undo_redo->add_do_method(animated_sprite, "set_autoplay", current);
1937
undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);
1938
}
1939
undo_redo->add_do_method(this, "_update_library");
1940
undo_redo->add_undo_method(this, "_update_library");
1941
undo_redo->commit_action();
1942
}
1943
1944
_update_library();
1945
}
1946
1947
void SpriteFramesEditor::_bind_methods() {
1948
ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false));
1949
ClassDB::bind_method(D_METHOD("_select_animation", "name", "update_node"), &SpriteFramesEditor::_select_animation, DEFVAL(true));
1950
}
1951
1952
void SpriteFramesEditor::_node_removed(Node *p_node) {
1953
if (animated_sprite) {
1954
if (animated_sprite != p_node) {
1955
return;
1956
}
1957
_remove_sprite_node();
1958
}
1959
}
1960
1961
SpriteFramesEditor::SpriteFramesEditor() {
1962
VBoxContainer *vbc_animlist = memnew(VBoxContainer);
1963
add_child(vbc_animlist);
1964
vbc_animlist->set_custom_minimum_size(Size2(150, 0) * EDSCALE);
1965
1966
VBoxContainer *sub_vb = memnew(VBoxContainer);
1967
vbc_animlist->add_margin_child(TTR("Animations:"), sub_vb, true);
1968
sub_vb->set_v_size_flags(SIZE_EXPAND_FILL);
1969
1970
HBoxContainer *hbc_animlist = memnew(HBoxContainer);
1971
sub_vb->add_child(hbc_animlist);
1972
1973
add_anim = memnew(Button);
1974
add_anim->set_theme_type_variation(SceneStringName(FlatButton));
1975
add_anim->set_accessibility_name(TTRC("Add Animation"));
1976
hbc_animlist->add_child(add_anim);
1977
add_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_add));
1978
1979
duplicate_anim = memnew(Button);
1980
duplicate_anim->set_theme_type_variation(SceneStringName(FlatButton));
1981
duplicate_anim->set_accessibility_name(TTRC("Duplicate Animation"));
1982
hbc_animlist->add_child(duplicate_anim);
1983
duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate));
1984
1985
delete_anim = memnew(Button);
1986
delete_anim->set_theme_type_variation(SceneStringName(FlatButton));
1987
delete_anim->set_accessibility_name(TTRC("Delete Animation"));
1988
hbc_animlist->add_child(delete_anim);
1989
delete_anim->set_disabled(true);
1990
delete_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_remove));
1991
1992
autoplay_container = memnew(HBoxContainer);
1993
hbc_animlist->add_child(autoplay_container);
1994
1995
autoplay_container->add_child(memnew(VSeparator));
1996
1997
autoplay = memnew(Button);
1998
autoplay->set_theme_type_variation(SceneStringName(FlatButton));
1999
autoplay->set_tooltip_text(TTR("Autoplay on Load"));
2000
autoplay_container->add_child(autoplay);
2001
2002
hbc_animlist->add_child(memnew(VSeparator));
2003
2004
anim_loop = memnew(Button);
2005
anim_loop->set_toggle_mode(true);
2006
anim_loop->set_theme_type_variation(SceneStringName(FlatButton));
2007
anim_loop->set_tooltip_text(TTR("Animation Looping"));
2008
anim_loop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_loop_changed));
2009
hbc_animlist->add_child(anim_loop);
2010
2011
anim_speed = memnew(SpinBox);
2012
anim_speed->set_suffix(TTR("FPS"));
2013
anim_speed->set_min(0);
2014
anim_speed->set_max(120);
2015
anim_speed->set_step(0.01);
2016
anim_speed->set_custom_arrow_step(1);
2017
anim_speed->set_tooltip_text(TTR("Animation Speed"));
2018
anim_speed->get_line_edit()->set_expand_to_text_length_enabled(true);
2019
anim_speed->get_line_edit()->connect(SceneStringName(resized), callable_mp(this, &SpriteFramesEditor::_animation_speed_resized));
2020
anim_speed->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_animation_speed_changed));
2021
hbc_animlist->add_child(anim_speed);
2022
2023
anim_search_box = memnew(LineEdit);
2024
sub_vb->add_child(anim_search_box);
2025
anim_search_box->set_h_size_flags(SIZE_EXPAND_FILL);
2026
anim_search_box->set_placeholder(TTR("Filter Animations"));
2027
anim_search_box->set_clear_button_enabled(true);
2028
anim_search_box->connect(SceneStringName(text_changed), callable_mp(this, &SpriteFramesEditor::_animation_search_text_changed));
2029
2030
animations = memnew(Tree);
2031
sub_vb->add_child(animations);
2032
animations->set_v_size_flags(SIZE_EXPAND_FILL);
2033
animations->set_hide_root(true);
2034
// HACK: The cell_selected signal is emitted before the FPS spinbox loses focus and applies the change.
2035
animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_selected), CONNECT_DEFERRED);
2036
animations->connect("item_edited", callable_mp(this, &SpriteFramesEditor::_animation_name_edited));
2037
animations->set_theme_type_variation("TreeSecondary");
2038
animations->set_allow_reselect(true);
2039
2040
add_anim->set_shortcut_context(animations);
2041
add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTRC("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));
2042
duplicate_anim->set_shortcut_context(animations);
2043
duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTRC("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D));
2044
delete_anim->set_shortcut_context(animations);
2045
delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTRC("Delete Animation"), Key::KEY_DELETE));
2046
2047
missing_anim_label = memnew(Label);
2048
missing_anim_label->set_focus_mode(FOCUS_ACCESSIBILITY);
2049
missing_anim_label->set_text(TTR("This resource does not have any animations."));
2050
missing_anim_label->set_h_size_flags(SIZE_EXPAND_FILL);
2051
missing_anim_label->set_v_size_flags(SIZE_EXPAND_FILL);
2052
missing_anim_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
2053
missing_anim_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
2054
missing_anim_label->hide();
2055
add_child(missing_anim_label);
2056
2057
anim_frames_vb = memnew(VBoxContainer);
2058
add_child(anim_frames_vb);
2059
anim_frames_vb->set_h_size_flags(SIZE_EXPAND_FILL);
2060
anim_frames_vb->hide();
2061
2062
sub_vb = memnew(VBoxContainer);
2063
anim_frames_vb->add_margin_child(TTR("Animation Frames:"), sub_vb, true);
2064
2065
HFlowContainer *hfc = memnew(HFlowContainer);
2066
sub_vb->add_child(hfc);
2067
2068
playback_container = memnew(HBoxContainer);
2069
playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);
2070
hfc->add_child(playback_container);
2071
2072
play_bw_from = memnew(Button);
2073
play_bw_from->set_theme_type_variation(SceneStringName(FlatButton));
2074
play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));
2075
playback_container->add_child(play_bw_from);
2076
2077
play_bw = memnew(Button);
2078
play_bw->set_theme_type_variation(SceneStringName(FlatButton));
2079
play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));
2080
playback_container->add_child(play_bw);
2081
2082
stop = memnew(Button);
2083
stop->set_theme_type_variation(SceneStringName(FlatButton));
2084
stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));
2085
playback_container->add_child(stop);
2086
2087
play = memnew(Button);
2088
play->set_theme_type_variation(SceneStringName(FlatButton));
2089
play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));
2090
playback_container->add_child(play);
2091
2092
play_from = memnew(Button);
2093
play_from->set_theme_type_variation(SceneStringName(FlatButton));
2094
play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));
2095
playback_container->add_child(play_from);
2096
2097
hfc->add_child(memnew(VSeparator));
2098
2099
autoplay->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_autoplay_pressed));
2100
autoplay->set_toggle_mode(true);
2101
play->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_pressed));
2102
play_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_from_pressed));
2103
play_bw->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_pressed));
2104
play_bw_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_from_pressed));
2105
stop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_stop_pressed));
2106
2107
HBoxContainer *hbc_actions = memnew(HBoxContainer);
2108
hfc->add_child(hbc_actions);
2109
2110
load = memnew(Button);
2111
load->set_accessibility_name(TTRC("Load"));
2112
load->set_theme_type_variation(SceneStringName(FlatButton));
2113
hbc_actions->add_child(load);
2114
2115
load_sheet = memnew(Button);
2116
load_sheet->set_accessibility_name(TTRC("Load Sheet"));
2117
load_sheet->set_theme_type_variation(SceneStringName(FlatButton));
2118
hbc_actions->add_child(load_sheet);
2119
2120
hbc_actions->add_child(memnew(VSeparator));
2121
2122
copy = memnew(Button);
2123
copy->set_accessibility_name(TTRC("Copy"));
2124
copy->set_theme_type_variation(SceneStringName(FlatButton));
2125
hbc_actions->add_child(copy);
2126
2127
paste = memnew(Button);
2128
paste->set_accessibility_name(TTRC("Paste"));
2129
paste->set_theme_type_variation(SceneStringName(FlatButton));
2130
hbc_actions->add_child(paste);
2131
2132
hbc_actions->add_child(memnew(VSeparator));
2133
2134
empty_before = memnew(Button);
2135
empty_before->set_accessibility_name(TTRC("Empty Before"));
2136
empty_before->set_theme_type_variation(SceneStringName(FlatButton));
2137
hbc_actions->add_child(empty_before);
2138
2139
empty_after = memnew(Button);
2140
empty_after->set_accessibility_name(TTRC("Empty After"));
2141
empty_after->set_theme_type_variation(SceneStringName(FlatButton));
2142
hbc_actions->add_child(empty_after);
2143
2144
hbc_actions->add_child(memnew(VSeparator));
2145
2146
move_up = memnew(Button);
2147
move_up->set_accessibility_name(TTRC("Move Up"));
2148
move_up->set_theme_type_variation(SceneStringName(FlatButton));
2149
hbc_actions->add_child(move_up);
2150
2151
move_down = memnew(Button);
2152
move_down->set_accessibility_name(TTRC("Move Down"));
2153
move_down->set_theme_type_variation(SceneStringName(FlatButton));
2154
hbc_actions->add_child(move_down);
2155
2156
delete_frame = memnew(Button);
2157
delete_frame->set_accessibility_name(TTRC("Delete Frame"));
2158
delete_frame->set_theme_type_variation(SceneStringName(FlatButton));
2159
hbc_actions->add_child(delete_frame);
2160
2161
hbc_actions->add_child(memnew(VSeparator));
2162
2163
HBoxContainer *hbc_frame_duration = memnew(HBoxContainer);
2164
hfc->add_child(hbc_frame_duration);
2165
2166
Label *label = memnew(Label);
2167
label->set_text(TTR("Frame Duration:"));
2168
hbc_frame_duration->add_child(label);
2169
2170
frame_duration = memnew(SpinBox);
2171
frame_duration->set_prefix(String::utf8("×"));
2172
frame_duration->set_min(SPRITE_FRAME_MINIMUM_DURATION); // Avoid zero div.
2173
frame_duration->set_max(10);
2174
frame_duration->set_step(0.01);
2175
frame_duration->set_custom_arrow_step(0.1);
2176
frame_duration->set_allow_lesser(false);
2177
frame_duration->set_allow_greater(true);
2178
frame_duration->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_frame_duration_changed));
2179
frame_duration->set_accessibility_name(TTRC("Frame Duration:"));
2180
hbc_frame_duration->add_child(frame_duration);
2181
2182
// Wide empty separation control. (like BoxContainer::add_spacer())
2183
Control *c = memnew(Control);
2184
c->set_mouse_filter(MOUSE_FILTER_PASS);
2185
c->set_h_size_flags(SIZE_EXPAND_FILL);
2186
hfc->add_child(c);
2187
2188
HBoxContainer *hbc_zoom = memnew(HBoxContainer);
2189
hfc->add_child(hbc_zoom);
2190
2191
zoom_out = memnew(Button);
2192
zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_out));
2193
zoom_out->set_flat(true);
2194
zoom_out->set_tooltip_text(TTRC("Zoom Out"));
2195
hbc_zoom->add_child(zoom_out);
2196
2197
zoom_reset = memnew(Button);
2198
zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_reset));
2199
zoom_reset->set_flat(true);
2200
zoom_reset->set_tooltip_text(TTRC("Zoom Reset"));
2201
hbc_zoom->add_child(zoom_reset);
2202
2203
zoom_in = memnew(Button);
2204
zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_in));
2205
zoom_in->set_flat(true);
2206
zoom_in->set_tooltip_text(TTRC("Zoom In"));
2207
hbc_zoom->add_child(zoom_in);
2208
2209
file = memnew(EditorFileDialog);
2210
file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1));
2211
add_child(file);
2212
2213
frame_list = memnew(ItemList);
2214
frame_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
2215
frame_list->set_v_size_flags(SIZE_EXPAND_FILL);
2216
frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);
2217
frame_list->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
2218
frame_list->set_select_mode(ItemList::SELECT_MULTI);
2219
2220
frame_list->set_max_columns(0);
2221
frame_list->set_max_text_lines(2);
2222
SET_DRAG_FORWARDING_GCD(frame_list, SpriteFramesEditor);
2223
frame_list->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_frame_list_gui_input));
2224
// HACK: The item_selected signal is emitted before the Frame Duration spinbox loses focus and applies the change.
2225
frame_list->connect("multi_selected", callable_mp(this, &SpriteFramesEditor::_frame_list_item_selected), CONNECT_DEFERRED);
2226
2227
sub_vb->add_child(frame_list);
2228
2229
dialog = memnew(AcceptDialog);
2230
add_child(dialog);
2231
2232
load->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_load_pressed));
2233
load_sheet->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_open_sprite_sheet));
2234
delete_frame->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_delete_pressed));
2235
copy->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_copy_pressed));
2236
paste->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_paste_pressed));
2237
empty_before->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty_pressed));
2238
empty_after->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty2_pressed));
2239
move_up->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_up_pressed));
2240
move_down->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_down_pressed));
2241
2242
load->set_shortcut_context(frame_list);
2243
load->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_file", TTRC("Add frame from file"), KeyModifierMask::CMD_OR_CTRL | Key::O));
2244
load_sheet->set_shortcut_context(frame_list);
2245
load_sheet->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_sheet", TTRC("Add frames from sprite sheet"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O));
2246
delete_frame->set_shortcut_context(frame_list);
2247
delete_frame->set_shortcut(ED_SHORTCUT("sprite_frames/delete", TTRC("Delete Frame"), Key::KEY_DELETE));
2248
copy->set_shortcut_context(frame_list);
2249
copy->set_shortcut(ED_SHORTCUT("sprite_frames/copy", TTRC("Copy Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::C));
2250
paste->set_shortcut_context(frame_list);
2251
paste->set_shortcut(ED_SHORTCUT("sprite_frames/paste", TTRC("Paste Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::V));
2252
empty_before->set_shortcut_context(frame_list);
2253
empty_before->set_shortcut(ED_SHORTCUT("sprite_frames/empty_before", TTRC("Insert Empty (Before Selected)"), KeyModifierMask::ALT | Key::LEFT));
2254
empty_after->set_shortcut_context(frame_list);
2255
empty_after->set_shortcut(ED_SHORTCUT("sprite_frames/empty_after", TTRC("Insert Empty (After Selected)"), KeyModifierMask::ALT | Key::RIGHT));
2256
move_up->set_shortcut_context(frame_list);
2257
move_up->set_shortcut(ED_SHORTCUT("sprite_frames/move_left", TTRC("Move Frame Left"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT));
2258
move_down->set_shortcut_context(frame_list);
2259
move_down->set_shortcut(ED_SHORTCUT("sprite_frames/move_right", TTRC("Move Frame Right"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT));
2260
2261
zoom_out->set_shortcut_context(frame_list);
2262
zoom_out->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_out", TTRC("Zoom Out"),
2263
{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));
2264
zoom_in->set_shortcut_context(frame_list);
2265
zoom_in->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_in", TTRC("Zoom In"),
2266
{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));
2267
2268
loading_scene = false;
2269
2270
updating = false;
2271
2272
edited_anim = SceneStringName(default_);
2273
2274
delete_dialog = memnew(ConfirmationDialog);
2275
add_child(delete_dialog);
2276
delete_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_animation_remove_confirmed));
2277
2278
split_sheet_dialog = memnew(ConfirmationDialog);
2279
add_child(split_sheet_dialog);
2280
split_sheet_dialog->set_title(TTR("Select Frames"));
2281
split_sheet_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_sheet_add_frames));
2282
2283
HBoxContainer *split_sheet_hb = memnew(HBoxContainer);
2284
split_sheet_dialog->add_child(split_sheet_hb);
2285
split_sheet_hb->set_h_size_flags(SIZE_EXPAND_FILL);
2286
split_sheet_hb->set_v_size_flags(SIZE_EXPAND_FILL);
2287
2288
VBoxContainer *split_sheet_vb = memnew(VBoxContainer);
2289
split_sheet_hb->add_child(split_sheet_vb);
2290
split_sheet_vb->set_h_size_flags(SIZE_EXPAND_FILL);
2291
split_sheet_vb->set_v_size_flags(SIZE_EXPAND_FILL);
2292
2293
HBoxContainer *split_sheet_menu_hb = memnew(HBoxContainer);
2294
2295
split_sheet_menu_hb->add_child(memnew(Label(TTR("Frame Order"))));
2296
2297
split_sheet_order = memnew(OptionButton);
2298
split_sheet_order->add_item(TTR("As Selected"), FRAME_ORDER_SELECTION);
2299
split_sheet_order->add_separator(TTR("By Row"));
2300
split_sheet_order->add_item(TTR("Left to Right, Top to Bottom"), FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM);
2301
split_sheet_order->add_item(TTR("Left to Right, Bottom to Top"), FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP);
2302
split_sheet_order->add_item(TTR("Right to Left, Top to Bottom"), FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM);
2303
split_sheet_order->add_item(TTR("Right to Left, Bottom to Top"), FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP);
2304
split_sheet_order->add_separator(TTR("By Column"));
2305
split_sheet_order->add_item(TTR("Top to Bottom, Left to Right"), FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT);
2306
split_sheet_order->add_item(TTR("Top to Bottom, Right to Left"), FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT);
2307
split_sheet_order->add_item(TTR("Bottom to Top, Left to Right"), FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT);
2308
split_sheet_order->add_item(TTR("Bottom to Top, Right to Left"), FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT);
2309
split_sheet_order->connect(SceneStringName(item_selected), callable_mp(this, &SpriteFramesEditor::_sheet_order_selected));
2310
split_sheet_menu_hb->add_child(split_sheet_order);
2311
2312
Button *select_all = memnew(Button);
2313
select_all->set_text(TTR("Select All"));
2314
select_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_select_all_frames));
2315
split_sheet_menu_hb->add_child(select_all);
2316
2317
Button *clear_all = memnew(Button);
2318
clear_all->set_text(TTR("Select None"));
2319
clear_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_clear_all_frames));
2320
split_sheet_menu_hb->add_child(clear_all);
2321
2322
split_sheet_menu_hb->add_spacer();
2323
2324
toggle_settings_button = memnew(Button);
2325
toggle_settings_button->set_h_size_flags(SIZE_SHRINK_END);
2326
toggle_settings_button->set_theme_type_variation(SceneStringName(FlatButton));
2327
toggle_settings_button->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_toggle_show_settings));
2328
toggle_settings_button->set_tooltip_text(TTR("Toggle Settings Panel"));
2329
split_sheet_menu_hb->add_child(toggle_settings_button);
2330
2331
split_sheet_vb->add_child(split_sheet_menu_hb);
2332
2333
PanelContainer *split_sheet_panel = memnew(PanelContainer);
2334
split_sheet_panel->set_h_size_flags(SIZE_EXPAND_FILL);
2335
split_sheet_panel->set_v_size_flags(SIZE_EXPAND_FILL);
2336
split_sheet_vb->add_child(split_sheet_panel);
2337
2338
split_sheet_preview = memnew(TextureRect);
2339
split_sheet_preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
2340
split_sheet_preview->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
2341
split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS);
2342
split_sheet_preview->connect(SceneStringName(draw), callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw));
2343
split_sheet_preview->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_preview_input));
2344
2345
split_sheet_scroll = memnew(ScrollContainer);
2346
split_sheet_scroll->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input));
2347
split_sheet_panel->add_child(split_sheet_scroll);
2348
CenterContainer *cc = memnew(CenterContainer);
2349
cc->add_child(split_sheet_preview);
2350
cc->set_h_size_flags(SIZE_EXPAND_FILL);
2351
cc->set_v_size_flags(SIZE_EXPAND_FILL);
2352
split_sheet_scroll->add_child(cc);
2353
2354
MarginContainer *split_sheet_zoom_margin = memnew(MarginContainer);
2355
split_sheet_panel->add_child(split_sheet_zoom_margin);
2356
split_sheet_zoom_margin->set_h_size_flags(0);
2357
split_sheet_zoom_margin->set_v_size_flags(0);
2358
split_sheet_zoom_margin->add_theme_constant_override("margin_top", 5);
2359
split_sheet_zoom_margin->add_theme_constant_override("margin_left", 5);
2360
HBoxContainer *split_sheet_zoom_hb = memnew(HBoxContainer);
2361
split_sheet_zoom_margin->add_child(split_sheet_zoom_hb);
2362
2363
split_sheet_zoom_out = memnew(Button);
2364
split_sheet_zoom_out->set_theme_type_variation(SceneStringName(FlatButton));
2365
split_sheet_zoom_out->set_focus_mode(FOCUS_ACCESSIBILITY);
2366
split_sheet_zoom_out->set_tooltip_text(TTR("Zoom Out"));
2367
split_sheet_zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out));
2368
split_sheet_zoom_hb->add_child(split_sheet_zoom_out);
2369
2370
split_sheet_zoom_reset = memnew(Button);
2371
split_sheet_zoom_reset->set_theme_type_variation(SceneStringName(FlatButton));
2372
split_sheet_zoom_reset->set_focus_mode(FOCUS_ACCESSIBILITY);
2373
split_sheet_zoom_reset->set_tooltip_text(TTR("Zoom Reset"));
2374
split_sheet_zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset));
2375
split_sheet_zoom_hb->add_child(split_sheet_zoom_reset);
2376
2377
split_sheet_zoom_in = memnew(Button);
2378
split_sheet_zoom_in->set_theme_type_variation(SceneStringName(FlatButton));
2379
split_sheet_zoom_in->set_focus_mode(FOCUS_ACCESSIBILITY);
2380
split_sheet_zoom_in->set_tooltip_text(TTR("Zoom In"));
2381
split_sheet_zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in));
2382
split_sheet_zoom_hb->add_child(split_sheet_zoom_in);
2383
2384
split_sheet_settings_vb = memnew(VBoxContainer);
2385
split_sheet_settings_vb->set_v_size_flags(SIZE_EXPAND_FILL);
2386
2387
HBoxContainer *split_sheet_h_hb = memnew(HBoxContainer);
2388
split_sheet_h_hb->set_h_size_flags(SIZE_EXPAND_FILL);
2389
2390
Label *split_sheet_h_label = memnew(Label(TTR("Horizontal")));
2391
split_sheet_h_label->set_h_size_flags(SIZE_EXPAND_FILL);
2392
split_sheet_h_hb->add_child(split_sheet_h_label);
2393
2394
split_sheet_h = memnew(SpinBox);
2395
split_sheet_h->set_h_size_flags(SIZE_EXPAND_FILL);
2396
split_sheet_h->set_min(1);
2397
split_sheet_h->set_max(128);
2398
split_sheet_h->set_step(1);
2399
split_sheet_h->set_select_all_on_focus(true);
2400
split_sheet_h->set_accessibility_name(TTRC("Horizontal"));
2401
split_sheet_h_hb->add_child(split_sheet_h);
2402
split_sheet_h->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
2403
split_sheet_settings_vb->add_child(split_sheet_h_hb);
2404
2405
HBoxContainer *split_sheet_v_hb = memnew(HBoxContainer);
2406
split_sheet_v_hb->set_h_size_flags(SIZE_EXPAND_FILL);
2407
2408
Label *split_sheet_v_label = memnew(Label(TTR("Vertical")));
2409
split_sheet_v_label->set_h_size_flags(SIZE_EXPAND_FILL);
2410
split_sheet_v_hb->add_child(split_sheet_v_label);
2411
2412
split_sheet_v = memnew(SpinBox);
2413
split_sheet_v->set_h_size_flags(SIZE_EXPAND_FILL);
2414
split_sheet_v->set_min(1);
2415
split_sheet_v->set_max(128);
2416
split_sheet_v->set_step(1);
2417
split_sheet_v->set_select_all_on_focus(true);
2418
split_sheet_v->set_accessibility_name(TTRC("Vertical"));
2419
split_sheet_v_hb->add_child(split_sheet_v);
2420
split_sheet_v->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
2421
split_sheet_settings_vb->add_child(split_sheet_v_hb);
2422
2423
HBoxContainer *split_sheet_size_hb = memnew(HBoxContainer);
2424
split_sheet_size_hb->set_h_size_flags(SIZE_EXPAND_FILL);
2425
2426
Label *split_sheet_size_label = memnew(Label(TTR("Size")));
2427
split_sheet_size_label->set_h_size_flags(SIZE_EXPAND_FILL);
2428
split_sheet_size_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
2429
split_sheet_size_hb->add_child(split_sheet_size_label);
2430
2431
VBoxContainer *split_sheet_size_vb = memnew(VBoxContainer);
2432
split_sheet_size_vb->set_h_size_flags(SIZE_EXPAND_FILL);
2433
split_sheet_size_x = memnew(SpinBox);
2434
split_sheet_size_x->set_h_size_flags(SIZE_EXPAND_FILL);
2435
split_sheet_size_x->set_min(1);
2436
split_sheet_size_x->set_step(1);
2437
split_sheet_size_x->set_suffix("px");
2438
split_sheet_size_x->set_select_all_on_focus(true);
2439
split_sheet_size_x->set_accessibility_name(TTRC("X Size"));
2440
split_sheet_size_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
2441
split_sheet_size_vb->add_child(split_sheet_size_x);
2442
split_sheet_size_y = memnew(SpinBox);
2443
split_sheet_size_y->set_h_size_flags(SIZE_EXPAND_FILL);
2444
split_sheet_size_y->set_min(1);
2445
split_sheet_size_y->set_step(1);
2446
split_sheet_size_y->set_suffix("px");
2447
split_sheet_size_y->set_select_all_on_focus(true);
2448
split_sheet_size_y->set_accessibility_name(TTRC("Y Size"));
2449
split_sheet_size_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
2450
split_sheet_size_vb->add_child(split_sheet_size_y);
2451
split_sheet_size_hb->add_child(split_sheet_size_vb);
2452
split_sheet_settings_vb->add_child(split_sheet_size_hb);
2453
2454
HBoxContainer *split_sheet_sep_hb = memnew(HBoxContainer);
2455
split_sheet_sep_hb->set_h_size_flags(SIZE_EXPAND_FILL);
2456
2457
Label *split_sheet_sep_label = memnew(Label(TTR("Separation")));
2458
split_sheet_sep_label->set_h_size_flags(SIZE_EXPAND_FILL);
2459
split_sheet_sep_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
2460
split_sheet_sep_hb->add_child(split_sheet_sep_label);
2461
2462
VBoxContainer *split_sheet_sep_vb = memnew(VBoxContainer);
2463
split_sheet_sep_vb->set_h_size_flags(SIZE_EXPAND_FILL);
2464
split_sheet_sep_x = memnew(SpinBox);
2465
split_sheet_sep_x->set_min(0);
2466
split_sheet_sep_x->set_step(1);
2467
split_sheet_sep_x->set_suffix("px");
2468
split_sheet_sep_x->set_select_all_on_focus(true);
2469
split_sheet_sep_x->set_accessibility_name(TTRC("X Separation"));
2470
split_sheet_sep_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
2471
split_sheet_sep_vb->add_child(split_sheet_sep_x);
2472
split_sheet_sep_y = memnew(SpinBox);
2473
split_sheet_sep_y->set_min(0);
2474
split_sheet_sep_y->set_step(1);
2475
split_sheet_sep_y->set_suffix("px");
2476
split_sheet_sep_y->set_select_all_on_focus(true);
2477
split_sheet_sep_y->set_accessibility_name(TTRC("Y Separation"));
2478
split_sheet_sep_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
2479
split_sheet_sep_vb->add_child(split_sheet_sep_y);
2480
split_sheet_sep_hb->add_child(split_sheet_sep_vb);
2481
split_sheet_settings_vb->add_child(split_sheet_sep_hb);
2482
2483
HBoxContainer *split_sheet_offset_hb = memnew(HBoxContainer);
2484
split_sheet_offset_hb->set_h_size_flags(SIZE_EXPAND_FILL);
2485
2486
Label *split_sheet_offset_label = memnew(Label(TTR("Offset")));
2487
split_sheet_offset_label->set_h_size_flags(SIZE_EXPAND_FILL);
2488
split_sheet_offset_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
2489
split_sheet_offset_hb->add_child(split_sheet_offset_label);
2490
2491
VBoxContainer *split_sheet_offset_vb = memnew(VBoxContainer);
2492
split_sheet_offset_vb->set_h_size_flags(SIZE_EXPAND_FILL);
2493
split_sheet_offset_x = memnew(SpinBox);
2494
split_sheet_offset_x->set_min(0);
2495
split_sheet_offset_x->set_step(1);
2496
split_sheet_offset_x->set_suffix("px");
2497
split_sheet_offset_x->set_select_all_on_focus(true);
2498
split_sheet_offset_x->set_accessibility_name(TTRC("X Offset"));
2499
split_sheet_offset_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
2500
split_sheet_offset_vb->add_child(split_sheet_offset_x);
2501
split_sheet_offset_y = memnew(SpinBox);
2502
split_sheet_offset_y->set_min(0);
2503
split_sheet_offset_y->set_step(1);
2504
split_sheet_offset_y->set_suffix("px");
2505
split_sheet_offset_y->set_select_all_on_focus(true);
2506
split_sheet_offset_y->set_accessibility_name(TTRC("Y Offset"));
2507
split_sheet_offset_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
2508
split_sheet_offset_vb->add_child(split_sheet_offset_y);
2509
split_sheet_offset_hb->add_child(split_sheet_offset_vb);
2510
split_sheet_settings_vb->add_child(split_sheet_offset_hb);
2511
2512
Button *auto_slice = memnew(Button);
2513
auto_slice->set_text(TTR("Auto Slice"));
2514
auto_slice->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_auto_slice_sprite_sheet));
2515
split_sheet_settings_vb->add_child(auto_slice);
2516
2517
split_sheet_hb->add_child(split_sheet_settings_vb);
2518
2519
file_split_sheet = memnew(EditorFileDialog);
2520
file_split_sheet->set_title(TTR("Create Frames from Sprite Sheet"));
2521
file_split_sheet->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
2522
add_child(file_split_sheet);
2523
file_split_sheet->connect("file_selected", callable_mp(this, &SpriteFramesEditor::_prepare_sprite_sheet));
2524
2525
// Config scale.
2526
scale_ratio = 1.2f;
2527
thumbnail_default_size = 96 * MAX(1, EDSCALE);
2528
thumbnail_zoom = MAX(1.0f, EDSCALE);
2529
max_thumbnail_zoom = 8.0f * MAX(1.0f, EDSCALE);
2530
min_thumbnail_zoom = 0.1f * MAX(1.0f, EDSCALE);
2531
// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.
2532
sheet_zoom = MAX(1.0f, EDSCALE);
2533
max_sheet_zoom = 128.0f * MAX(1.0f, EDSCALE);
2534
min_sheet_zoom = 0.01f * MAX(1.0f, EDSCALE);
2535
_zoom_reset();
2536
2537
// Ensure the anim search box is wide enough by default.
2538
// Not by setting its minimum size so it can still be shrunk if desired.
2539
set_split_offset(56 * EDSCALE);
2540
}
2541
2542
void SpriteFramesEditorPlugin::edit(Object *p_object) {
2543
Ref<SpriteFrames> s;
2544
AnimatedSprite2D *animated_sprite = Object::cast_to<AnimatedSprite2D>(p_object);
2545
if (animated_sprite) {
2546
s = animated_sprite->get_sprite_frames();
2547
} else {
2548
AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);
2549
if (animated_sprite_3d) {
2550
s = animated_sprite_3d->get_sprite_frames();
2551
} else {
2552
s = p_object;
2553
}
2554
}
2555
2556
frames_editor->edit(s);
2557
}
2558
2559
bool SpriteFramesEditorPlugin::handles(Object *p_object) const {
2560
AnimatedSprite2D *animated_sprite_2d = Object::cast_to<AnimatedSprite2D>(p_object);
2561
if (animated_sprite_2d && *animated_sprite_2d->get_sprite_frames()) {
2562
return true;
2563
}
2564
AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);
2565
if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) {
2566
return true;
2567
}
2568
SpriteFrames *frames = Object::cast_to<SpriteFrames>(p_object);
2569
if (frames && (frames_editor->get_sprite_frames().is_null() || frames_editor->get_sprite_frames() == frames)) {
2570
return true;
2571
}
2572
return false;
2573
}
2574
2575
void SpriteFramesEditorPlugin::make_visible(bool p_visible) {
2576
if (p_visible) {
2577
button->show();
2578
EditorNode::get_bottom_panel()->make_item_visible(frames_editor);
2579
} else {
2580
button->hide();
2581
if (frames_editor->is_visible_in_tree()) {
2582
EditorNode::get_bottom_panel()->hide_bottom_panel();
2583
}
2584
}
2585
}
2586
2587
SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() {
2588
frames_editor = memnew(SpriteFramesEditor);
2589
frames_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
2590
button = EditorNode::get_bottom_panel()->add_item(TTRC("SpriteFrames"), frames_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_sprite_frames_bottom_panel", TTRC("Toggle SpriteFrames Bottom Panel")));
2591
button->hide();
2592
}
2593
2594