1 | /**************************************************************************/ |
2 | /* editor_scene_tabs.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 "editor_scene_tabs.h" |
32 | |
33 | #include "editor/editor_node.h" |
34 | #include "editor/editor_resource_preview.h" |
35 | #include "editor/editor_scale.h" |
36 | #include "editor/editor_settings.h" |
37 | #include "editor/editor_string_names.h" |
38 | #include "editor/editor_undo_redo_manager.h" |
39 | #include "editor/inspector_dock.h" |
40 | #include "scene/gui/box_container.h" |
41 | #include "scene/gui/button.h" |
42 | #include "scene/gui/panel.h" |
43 | #include "scene/gui/panel_container.h" |
44 | #include "scene/gui/popup_menu.h" |
45 | #include "scene/gui/tab_bar.h" |
46 | #include "scene/gui/texture_rect.h" |
47 | |
48 | EditorSceneTabs *EditorSceneTabs::singleton = nullptr; |
49 | |
50 | void EditorSceneTabs::_notification(int p_what) { |
51 | switch (p_what) { |
52 | case NOTIFICATION_THEME_CHANGED: { |
53 | tabbar_panel->add_theme_style_override("panel" , get_theme_stylebox(SNAME("tabbar_background" ), SNAME("TabContainer" ))); |
54 | scene_tabs->add_theme_constant_override("icon_max_width" , get_theme_constant(SNAME("class_icon_size" ), EditorStringName(Editor))); |
55 | |
56 | scene_tab_add->set_icon(get_editor_theme_icon(SNAME("Add" ))); |
57 | scene_tab_add->add_theme_color_override("icon_normal_color" , Color(0.6f, 0.6f, 0.6f, 0.8f)); |
58 | |
59 | scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size()); |
60 | } break; |
61 | |
62 | case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { |
63 | scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button" ).operator int()); |
64 | scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width" )) * EDSCALE); |
65 | } break; |
66 | } |
67 | } |
68 | |
69 | void EditorSceneTabs::_scene_tab_changed(int p_tab) { |
70 | tab_preview_panel->hide(); |
71 | |
72 | emit_signal("tab_changed" , p_tab); |
73 | } |
74 | |
75 | void EditorSceneTabs::_scene_tab_script_edited(int p_tab) { |
76 | Ref<Script> scr = EditorNode::get_editor_data().get_scene_root_script(p_tab); |
77 | if (scr.is_valid()) { |
78 | InspectorDock::get_singleton()->edit_resource(scr); |
79 | } |
80 | } |
81 | |
82 | void EditorSceneTabs::_scene_tab_closed(int p_tab) { |
83 | emit_signal("tab_closed" , p_tab); |
84 | } |
85 | |
86 | void EditorSceneTabs::_scene_tab_hovered(int p_tab) { |
87 | if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover" ))) { |
88 | return; |
89 | } |
90 | int current_tab = scene_tabs->get_current_tab(); |
91 | |
92 | if (p_tab == current_tab || p_tab < 0) { |
93 | tab_preview_panel->hide(); |
94 | } else { |
95 | String path = EditorNode::get_editor_data().get_scene_path(p_tab); |
96 | if (!path.is_empty()) { |
97 | EditorResourcePreview::get_singleton()->queue_resource_preview(path, this, "_tab_preview_done" , p_tab); |
98 | } |
99 | } |
100 | } |
101 | |
102 | void EditorSceneTabs::_scene_tab_exit() { |
103 | tab_preview_panel->hide(); |
104 | } |
105 | |
106 | void EditorSceneTabs::_scene_tab_input(const Ref<InputEvent> &p_input) { |
107 | int tab_id = scene_tabs->get_hovered_tab(); |
108 | Ref<InputEventMouseButton> mb = p_input; |
109 | |
110 | if (mb.is_valid()) { |
111 | if (tab_id >= 0) { |
112 | if (mb->get_button_index() == MouseButton::MIDDLE && mb->is_pressed()) { |
113 | _scene_tab_closed(tab_id); |
114 | } |
115 | } else if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) { |
116 | int tab_buttons = 0; |
117 | if (scene_tabs->get_offset_buttons_visible()) { |
118 | tab_buttons = get_theme_icon(SNAME("increment" ), SNAME("TabBar" ))->get_width() + get_theme_icon(SNAME("decrement" ), SNAME("TabBar" ))->get_width(); |
119 | } |
120 | |
121 | if ((is_layout_rtl() && mb->get_position().x > tab_buttons) || (!is_layout_rtl() && mb->get_position().x < scene_tabs->get_size().width - tab_buttons)) { |
122 | EditorNode::get_singleton()->trigger_menu_option(EditorNode::FILE_NEW_SCENE, true); |
123 | } |
124 | } |
125 | if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { |
126 | // Context menu. |
127 | _update_context_menu(); |
128 | |
129 | scene_tabs_context_menu->set_position(scene_tabs->get_screen_position() + mb->get_position()); |
130 | scene_tabs_context_menu->reset_size(); |
131 | scene_tabs_context_menu->popup(); |
132 | } |
133 | } |
134 | } |
135 | |
136 | void EditorSceneTabs::_reposition_active_tab(int p_to_index) { |
137 | EditorNode::get_editor_data().move_edited_scene_to_index(p_to_index); |
138 | update_scene_tabs(); |
139 | } |
140 | |
141 | void EditorSceneTabs::() { |
142 | scene_tabs_context_menu->clear(); |
143 | scene_tabs_context_menu->reset_size(); |
144 | |
145 | int tab_id = scene_tabs->get_hovered_tab(); |
146 | bool no_root_node = !EditorNode::get_editor_data().get_edited_scene_root(tab_id); |
147 | |
148 | scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/new_scene" ), EditorNode::FILE_NEW_SCENE); |
149 | if (tab_id >= 0) { |
150 | scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene" ), EditorNode::FILE_SAVE_SCENE); |
151 | _disable_menu_option_if(EditorNode::FILE_SAVE_SCENE, no_root_node); |
152 | scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as" ), EditorNode::FILE_SAVE_AS_SCENE); |
153 | _disable_menu_option_if(EditorNode::FILE_SAVE_AS_SCENE, no_root_node); |
154 | } |
155 | |
156 | scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes" ), EditorNode::FILE_SAVE_ALL_SCENES); |
157 | bool can_save_all_scenes = false; |
158 | for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { |
159 | if (!EditorNode::get_editor_data().get_scene_path(i).is_empty() && EditorNode::get_editor_data().get_edited_scene_root(i)) { |
160 | can_save_all_scenes = true; |
161 | break; |
162 | } |
163 | } |
164 | _disable_menu_option_if(EditorNode::FILE_SAVE_ALL_SCENES, !can_save_all_scenes); |
165 | |
166 | if (tab_id >= 0) { |
167 | scene_tabs_context_menu->add_separator(); |
168 | scene_tabs_context_menu->add_item(TTR("Show in FileSystem" ), EditorNode::FILE_SHOW_IN_FILESYSTEM); |
169 | _disable_menu_option_if(EditorNode::FILE_SHOW_IN_FILESYSTEM, !ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id))); |
170 | scene_tabs_context_menu->add_item(TTR("Play This Scene" ), EditorNode::FILE_RUN_SCENE); |
171 | _disable_menu_option_if(EditorNode::FILE_RUN_SCENE, no_root_node); |
172 | |
173 | scene_tabs_context_menu->add_separator(); |
174 | scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/close_scene" ), EditorNode::FILE_CLOSE); |
175 | scene_tabs_context_menu->set_item_text(scene_tabs_context_menu->get_item_index(EditorNode::FILE_CLOSE), TTR("Close Tab" )); |
176 | scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/reopen_closed_scene" ), EditorNode::FILE_OPEN_PREV); |
177 | scene_tabs_context_menu->set_item_text(scene_tabs_context_menu->get_item_index(EditorNode::FILE_OPEN_PREV), TTR("Undo Close Tab" )); |
178 | _disable_menu_option_if(EditorNode::FILE_OPEN_PREV, !EditorNode::get_singleton()->has_previous_scenes()); |
179 | scene_tabs_context_menu->add_item(TTR("Close Other Tabs" ), EditorNode::FILE_CLOSE_OTHERS); |
180 | _disable_menu_option_if(EditorNode::FILE_CLOSE_OTHERS, EditorNode::get_editor_data().get_edited_scene_count() <= 1); |
181 | scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right" ), EditorNode::FILE_CLOSE_RIGHT); |
182 | _disable_menu_option_if(EditorNode::FILE_CLOSE_RIGHT, EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1); |
183 | scene_tabs_context_menu->add_item(TTR("Close All Tabs" ), EditorNode::FILE_CLOSE_ALL); |
184 | } |
185 | } |
186 | |
187 | void EditorSceneTabs::(int p_option, bool p_condition) { |
188 | if (p_condition) { |
189 | scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(p_option), true); |
190 | } |
191 | } |
192 | |
193 | // TODO: This REALLY should be done in a better way than replacing all tabs after almost EVERY action. |
194 | void EditorSceneTabs::update_scene_tabs() { |
195 | tab_preview_panel->hide(); |
196 | |
197 | bool show_rb = EDITOR_GET("interface/scene_tabs/show_script_button" ); |
198 | |
199 | if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { |
200 | DisplayServer::get_singleton()->global_menu_clear("_dock" ); |
201 | } |
202 | |
203 | // Get all scene names, which may be ambiguous. |
204 | Vector<String> disambiguated_scene_names; |
205 | Vector<String> full_path_names; |
206 | for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { |
207 | disambiguated_scene_names.append(EditorNode::get_editor_data().get_scene_title(i)); |
208 | full_path_names.append(EditorNode::get_editor_data().get_scene_path(i)); |
209 | } |
210 | |
211 | EditorNode::disambiguate_filenames(full_path_names, disambiguated_scene_names); |
212 | |
213 | // Workaround to ignore the tab_changed signal from the first added tab. |
214 | scene_tabs->disconnect("tab_changed" , callable_mp(this, &EditorSceneTabs::_scene_tab_changed)); |
215 | |
216 | scene_tabs->clear_tabs(); |
217 | Ref<Texture2D> script_icon = get_editor_theme_icon(SNAME("Script" )); |
218 | for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { |
219 | Node *type_node = EditorNode::get_editor_data().get_edited_scene_root(i); |
220 | Ref<Texture2D> icon; |
221 | if (type_node) { |
222 | icon = EditorNode::get_singleton()->get_object_icon(type_node, "Node" ); |
223 | } |
224 | |
225 | bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(EditorNode::get_editor_data().get_scene_history_id(i)); |
226 | scene_tabs->add_tab(disambiguated_scene_names[i] + (unsaved ? "(*)" : "" ), icon); |
227 | |
228 | if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { |
229 | DisplayServer::get_singleton()->global_menu_add_item("_dock" , EditorNode::get_editor_data().get_scene_title(i) + (unsaved ? "(*)" : "" ), callable_mp(this, &EditorSceneTabs::_global_menu_scene), Callable(), i); |
230 | } |
231 | |
232 | if (show_rb && EditorNode::get_editor_data().get_scene_root_script(i).is_valid()) { |
233 | scene_tabs->set_tab_button_icon(i, script_icon); |
234 | } |
235 | } |
236 | |
237 | if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { |
238 | DisplayServer::get_singleton()->global_menu_add_separator("_dock" ); |
239 | DisplayServer::get_singleton()->global_menu_add_item("_dock" , TTR("New Window" ), callable_mp(this, &EditorSceneTabs::_global_menu_new_window)); |
240 | } |
241 | |
242 | if (scene_tabs->get_tab_count() > 0) { |
243 | scene_tabs->set_current_tab(EditorNode::get_editor_data().get_edited_scene()); |
244 | } |
245 | |
246 | const Size2 add_button_size = Size2(scene_tab_add->get_size().x, scene_tabs->get_size().y); |
247 | if (scene_tabs->get_offset_buttons_visible()) { |
248 | // Move the add button to a fixed position. |
249 | if (scene_tab_add->get_parent() == scene_tabs) { |
250 | scene_tabs->remove_child(scene_tab_add); |
251 | scene_tab_add_ph->add_child(scene_tab_add); |
252 | scene_tab_add->set_rect(Rect2(Point2(), add_button_size)); |
253 | } |
254 | } else { |
255 | // Move the add button to be after the last tab. |
256 | if (scene_tab_add->get_parent() == scene_tab_add_ph) { |
257 | scene_tab_add_ph->remove_child(scene_tab_add); |
258 | scene_tabs->add_child(scene_tab_add); |
259 | } |
260 | |
261 | if (scene_tabs->get_tab_count() == 0) { |
262 | scene_tab_add->set_rect(Rect2(Point2(), add_button_size)); |
263 | return; |
264 | } |
265 | |
266 | Rect2 last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1); |
267 | int hsep = scene_tabs->get_theme_constant(SNAME("h_separation" )); |
268 | if (scene_tabs->is_layout_rtl()) { |
269 | scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x - add_button_size.x - hsep, last_tab.position.y), add_button_size)); |
270 | } else { |
271 | scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x + last_tab.size.width + hsep, last_tab.position.y), add_button_size)); |
272 | } |
273 | } |
274 | |
275 | // Reconnect after everything is done. |
276 | scene_tabs->connect("tab_changed" , callable_mp(this, &EditorSceneTabs::_scene_tab_changed)); |
277 | } |
278 | |
279 | void EditorSceneTabs::_tab_preview_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) { |
280 | int p_tab = p_udata; |
281 | if (p_preview.is_valid()) { |
282 | tab_preview->set_texture(p_preview); |
283 | |
284 | Rect2 rect = scene_tabs->get_tab_rect(p_tab); |
285 | rect.position += scene_tabs->get_global_position(); |
286 | tab_preview_panel->set_global_position(rect.position + Vector2(0, rect.size.height)); |
287 | tab_preview_panel->show(); |
288 | } |
289 | } |
290 | |
291 | void EditorSceneTabs::(const Variant &p_tag) { |
292 | int idx = (int)p_tag; |
293 | scene_tabs->set_current_tab(idx); |
294 | } |
295 | |
296 | void EditorSceneTabs::(const Variant &p_tag) { |
297 | if (OS::get_singleton()->get_main_loop()) { |
298 | List<String> args; |
299 | args.push_back("-p" ); |
300 | OS::get_singleton()->create_instance(args); |
301 | } |
302 | } |
303 | |
304 | void EditorSceneTabs::shortcut_input(const Ref<InputEvent> &p_event) { |
305 | ERR_FAIL_COND(p_event.is_null()); |
306 | |
307 | Ref<InputEventKey> k = p_event; |
308 | if ((k.is_valid() && k->is_pressed() && !k->is_echo()) || Object::cast_to<InputEventShortcut>(*p_event)) { |
309 | if (ED_IS_SHORTCUT("editor/next_tab" , p_event)) { |
310 | int next_tab = EditorNode::get_editor_data().get_edited_scene() + 1; |
311 | next_tab %= EditorNode::get_editor_data().get_edited_scene_count(); |
312 | _scene_tab_changed(next_tab); |
313 | } |
314 | if (ED_IS_SHORTCUT("editor/prev_tab" , p_event)) { |
315 | int next_tab = EditorNode::get_editor_data().get_edited_scene() - 1; |
316 | next_tab = next_tab >= 0 ? next_tab : EditorNode::get_editor_data().get_edited_scene_count() - 1; |
317 | _scene_tab_changed(next_tab); |
318 | } |
319 | } |
320 | } |
321 | |
322 | void EditorSceneTabs::(Button *p_button) { |
323 | tabbar_container->add_child(p_button); |
324 | } |
325 | |
326 | void EditorSceneTabs::set_current_tab(int p_tab) { |
327 | scene_tabs->set_current_tab(p_tab); |
328 | } |
329 | |
330 | int EditorSceneTabs::get_current_tab() const { |
331 | return scene_tabs->get_current_tab(); |
332 | } |
333 | |
334 | void EditorSceneTabs::_bind_methods() { |
335 | ADD_SIGNAL(MethodInfo("tab_changed" , PropertyInfo(Variant::INT, "tab_index" ))); |
336 | ADD_SIGNAL(MethodInfo("tab_closed" , PropertyInfo(Variant::INT, "tab_index" ))); |
337 | |
338 | ClassDB::bind_method("_tab_preview_done" , &EditorSceneTabs::_tab_preview_done); |
339 | } |
340 | |
341 | EditorSceneTabs::EditorSceneTabs() { |
342 | singleton = this; |
343 | |
344 | tabbar_panel = memnew(PanelContainer); |
345 | add_child(tabbar_panel); |
346 | tabbar_container = memnew(HBoxContainer); |
347 | tabbar_panel->add_child(tabbar_container); |
348 | |
349 | scene_tabs = memnew(TabBar); |
350 | scene_tabs->set_select_with_rmb(true); |
351 | scene_tabs->add_tab("unsaved" ); |
352 | scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button" ).operator int()); |
353 | scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width" )) * EDSCALE); |
354 | scene_tabs->set_drag_to_rearrange_enabled(true); |
355 | scene_tabs->set_auto_translate(false); |
356 | scene_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
357 | tabbar_container->add_child(scene_tabs); |
358 | |
359 | scene_tabs->connect("tab_changed" , callable_mp(this, &EditorSceneTabs::_scene_tab_changed)); |
360 | scene_tabs->connect("tab_button_pressed" , callable_mp(this, &EditorSceneTabs::_scene_tab_script_edited)); |
361 | scene_tabs->connect("tab_close_pressed" , callable_mp(this, &EditorSceneTabs::_scene_tab_closed)); |
362 | scene_tabs->connect("tab_hovered" , callable_mp(this, &EditorSceneTabs::_scene_tab_hovered)); |
363 | scene_tabs->connect("mouse_exited" , callable_mp(this, &EditorSceneTabs::_scene_tab_exit)); |
364 | scene_tabs->connect("gui_input" , callable_mp(this, &EditorSceneTabs::_scene_tab_input)); |
365 | scene_tabs->connect("active_tab_rearranged" , callable_mp(this, &EditorSceneTabs::_reposition_active_tab)); |
366 | scene_tabs->connect("resized" , callable_mp(this, &EditorSceneTabs::update_scene_tabs)); |
367 | |
368 | scene_tabs_context_menu = memnew(PopupMenu); |
369 | tabbar_container->add_child(scene_tabs_context_menu); |
370 | scene_tabs_context_menu->connect("id_pressed" , callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(false)); |
371 | |
372 | scene_tab_add = memnew(Button); |
373 | scene_tab_add->set_flat(true); |
374 | scene_tab_add->set_tooltip_text(TTR("Add a new scene." )); |
375 | scene_tabs->add_child(scene_tab_add); |
376 | scene_tab_add->connect("pressed" , callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(EditorNode::FILE_NEW_SCENE, false)); |
377 | |
378 | scene_tab_add_ph = memnew(Control); |
379 | scene_tab_add_ph->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); |
380 | scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size()); |
381 | tabbar_container->add_child(scene_tab_add_ph); |
382 | |
383 | // On-hover tab preview. |
384 | |
385 | Control *tab_preview_anchor = memnew(Control); |
386 | tab_preview_anchor->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); |
387 | add_child(tab_preview_anchor); |
388 | |
389 | tab_preview_panel = memnew(Panel); |
390 | tab_preview_panel->set_size(Size2(100, 100) * EDSCALE); |
391 | tab_preview_panel->hide(); |
392 | tab_preview_panel->set_self_modulate(Color(1, 1, 1, 0.7)); |
393 | tab_preview_anchor->add_child(tab_preview_panel); |
394 | |
395 | tab_preview = memnew(TextureRect); |
396 | tab_preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); |
397 | tab_preview->set_size(Size2(96, 96) * EDSCALE); |
398 | tab_preview->set_position(Point2(2, 2) * EDSCALE); |
399 | tab_preview_panel->add_child(tab_preview); |
400 | } |
401 | |