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