1 | /**************************************************************************/ |
2 | /* control_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 "control_editor_plugin.h" |
32 | |
33 | #include "editor/editor_node.h" |
34 | #include "editor/editor_scale.h" |
35 | #include "editor/editor_settings.h" |
36 | #include "editor/editor_string_names.h" |
37 | #include "editor/editor_undo_redo_manager.h" |
38 | #include "editor/plugins/canvas_item_editor_plugin.h" |
39 | #include "scene/gui/grid_container.h" |
40 | #include "scene/gui/separator.h" |
41 | |
42 | // Inspector controls. |
43 | |
44 | void ControlPositioningWarning::_update_warning() { |
45 | if (!control_node) { |
46 | title_icon->set_texture(nullptr); |
47 | title_label->set_text("" ); |
48 | hint_label->set_text("" ); |
49 | return; |
50 | } |
51 | |
52 | Node *parent_node = control_node->get_parent_control(); |
53 | if (!parent_node) { |
54 | title_icon->set_texture(get_editor_theme_icon(SNAME("SubViewport" ))); |
55 | title_label->set_text(TTR("This node doesn't have a control parent." )); |
56 | hint_label->set_text(TTR("Use the appropriate layout properties depending on where you are going to put it." )); |
57 | } else if (Object::cast_to<Container>(parent_node)) { |
58 | title_icon->set_texture(get_editor_theme_icon(SNAME("ContainerLayout" ))); |
59 | title_label->set_text(TTR("This node is a child of a container." )); |
60 | hint_label->set_text(TTR("Use container properties for positioning." )); |
61 | } else { |
62 | title_icon->set_texture(get_editor_theme_icon(SNAME("ControlLayout" ))); |
63 | title_label->set_text(TTR("This node is a child of a regular control." )); |
64 | hint_label->set_text(TTR("Use anchors and the rectangle for positioning." )); |
65 | } |
66 | |
67 | bg_panel->add_theme_style_override("panel" , get_theme_stylebox(SNAME("bg_group_note" ), SNAME("EditorProperty" ))); |
68 | } |
69 | |
70 | void ControlPositioningWarning::_update_toggler() { |
71 | Ref<Texture2D> arrow; |
72 | if (hint_label->is_visible()) { |
73 | arrow = get_theme_icon(SNAME("arrow" ), SNAME("Tree" )); |
74 | set_tooltip_text(TTR("Collapse positioning hint." )); |
75 | } else { |
76 | if (is_layout_rtl()) { |
77 | arrow = get_theme_icon(SNAME("arrow_collapsed" ), SNAME("Tree" )); |
78 | } else { |
79 | arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored" ), SNAME("Tree" )); |
80 | } |
81 | set_tooltip_text(TTR("Expand positioning hint." )); |
82 | } |
83 | |
84 | hint_icon->set_texture(arrow); |
85 | } |
86 | |
87 | void ControlPositioningWarning::set_control(Control *p_node) { |
88 | control_node = p_node; |
89 | _update_warning(); |
90 | } |
91 | |
92 | void ControlPositioningWarning::gui_input(const Ref<InputEvent> &p_event) { |
93 | Ref<InputEventMouseButton> mb = p_event; |
94 | if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { |
95 | bool state = !hint_label->is_visible(); |
96 | |
97 | hint_filler_left->set_visible(state); |
98 | hint_label->set_visible(state); |
99 | hint_filler_right->set_visible(state); |
100 | |
101 | _update_toggler(); |
102 | } |
103 | } |
104 | |
105 | void ControlPositioningWarning::_notification(int p_notification) { |
106 | switch (p_notification) { |
107 | case NOTIFICATION_ENTER_TREE: |
108 | case NOTIFICATION_THEME_CHANGED: |
109 | _update_warning(); |
110 | _update_toggler(); |
111 | break; |
112 | } |
113 | } |
114 | |
115 | ControlPositioningWarning::ControlPositioningWarning() { |
116 | set_mouse_filter(MOUSE_FILTER_STOP); |
117 | |
118 | bg_panel = memnew(PanelContainer); |
119 | bg_panel->set_mouse_filter(MOUSE_FILTER_IGNORE); |
120 | add_child(bg_panel); |
121 | |
122 | grid = memnew(GridContainer); |
123 | grid->set_columns(3); |
124 | bg_panel->add_child(grid); |
125 | |
126 | title_icon = memnew(TextureRect); |
127 | title_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); |
128 | grid->add_child(title_icon); |
129 | |
130 | title_label = memnew(Label); |
131 | title_label->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD); |
132 | title_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
133 | title_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); |
134 | grid->add_child(title_label); |
135 | |
136 | hint_icon = memnew(TextureRect); |
137 | hint_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); |
138 | grid->add_child(hint_icon); |
139 | |
140 | // Filler. |
141 | hint_filler_left = memnew(Control); |
142 | hint_filler_left->hide(); |
143 | grid->add_child(hint_filler_left); |
144 | |
145 | hint_label = memnew(Label); |
146 | hint_label->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD); |
147 | hint_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
148 | hint_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); |
149 | hint_label->hide(); |
150 | grid->add_child(hint_label); |
151 | |
152 | // Filler. |
153 | hint_filler_right = memnew(Control); |
154 | hint_filler_right->hide(); |
155 | grid->add_child(hint_filler_right); |
156 | } |
157 | |
158 | void EditorPropertyAnchorsPreset::_set_read_only(bool p_read_only) { |
159 | options->set_disabled(p_read_only); |
160 | }; |
161 | |
162 | void EditorPropertyAnchorsPreset::_option_selected(int p_which) { |
163 | int64_t val = options->get_item_metadata(p_which); |
164 | emit_changed(get_edited_property(), val); |
165 | } |
166 | |
167 | void EditorPropertyAnchorsPreset::update_property() { |
168 | int64_t which = get_edited_property_value(); |
169 | |
170 | for (int i = 0; i < options->get_item_count(); i++) { |
171 | Variant val = options->get_item_metadata(i); |
172 | if (val != Variant() && which == (int64_t)val) { |
173 | options->select(i); |
174 | return; |
175 | } |
176 | } |
177 | } |
178 | |
179 | void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { |
180 | options->clear(); |
181 | |
182 | Vector<String> split_after; |
183 | split_after.append("Custom" ); |
184 | split_after.append("PresetFullRect" ); |
185 | split_after.append("PresetBottomLeft" ); |
186 | split_after.append("PresetCenter" ); |
187 | |
188 | for (int i = 0, j = 0; i < p_options.size(); i++, j++) { |
189 | Vector<String> text_split = p_options[i].split(":" ); |
190 | int64_t current_val = text_split[1].to_int(); |
191 | |
192 | String option_name = text_split[0]; |
193 | if (option_name.begins_with("Preset" )) { |
194 | String preset_name = option_name.trim_prefix("Preset" ); |
195 | String humanized_name = preset_name.capitalize(); |
196 | String icon_name = "ControlAlign" + preset_name; |
197 | options->add_icon_item(EditorNode::get_singleton()->get_editor_theme()->get_icon(icon_name, EditorStringName(EditorIcons)), humanized_name); |
198 | } else { |
199 | options->add_item(option_name); |
200 | } |
201 | |
202 | options->set_item_metadata(j, current_val); |
203 | if (split_after.has(option_name)) { |
204 | options->add_separator(); |
205 | j++; |
206 | } |
207 | } |
208 | } |
209 | |
210 | EditorPropertyAnchorsPreset::EditorPropertyAnchorsPreset() { |
211 | options = memnew(OptionButton); |
212 | options->set_clip_text(true); |
213 | options->set_flat(true); |
214 | add_child(options); |
215 | add_focusable(options); |
216 | options->connect("item_selected" , callable_mp(this, &EditorPropertyAnchorsPreset::_option_selected)); |
217 | } |
218 | |
219 | void EditorPropertySizeFlags::_set_read_only(bool p_read_only) { |
220 | for (CheckBox *check : flag_checks) { |
221 | check->set_disabled(p_read_only); |
222 | } |
223 | flag_presets->set_disabled(p_read_only); |
224 | }; |
225 | |
226 | void EditorPropertySizeFlags::_preset_selected(int p_which) { |
227 | int preset = flag_presets->get_item_id(p_which); |
228 | if (preset == SIZE_FLAGS_PRESET_CUSTOM) { |
229 | flag_options->set_visible(true); |
230 | return; |
231 | } |
232 | flag_options->set_visible(false); |
233 | |
234 | uint32_t value = 0; |
235 | switch (preset) { |
236 | case SIZE_FLAGS_PRESET_FILL: |
237 | value = Control::SIZE_FILL; |
238 | break; |
239 | case SIZE_FLAGS_PRESET_SHRINK_BEGIN: |
240 | value = Control::SIZE_SHRINK_BEGIN; |
241 | break; |
242 | case SIZE_FLAGS_PRESET_SHRINK_CENTER: |
243 | value = Control::SIZE_SHRINK_CENTER; |
244 | break; |
245 | case SIZE_FLAGS_PRESET_SHRINK_END: |
246 | value = Control::SIZE_SHRINK_END; |
247 | break; |
248 | } |
249 | |
250 | bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); |
251 | if (is_expand) { |
252 | value |= Control::SIZE_EXPAND; |
253 | } |
254 | |
255 | emit_changed(get_edited_property(), value); |
256 | } |
257 | |
258 | void EditorPropertySizeFlags::_expand_toggled() { |
259 | uint32_t value = get_edited_property_value(); |
260 | |
261 | if (flag_expand->is_visible() && flag_expand->is_pressed()) { |
262 | value |= Control::SIZE_EXPAND; |
263 | } else { |
264 | value ^= Control::SIZE_EXPAND; |
265 | } |
266 | |
267 | // Keep the custom preset selected as we toggle individual flags. |
268 | keep_selected_preset = true; |
269 | emit_changed(get_edited_property(), value); |
270 | } |
271 | |
272 | void EditorPropertySizeFlags::_flag_toggled() { |
273 | uint32_t value = 0; |
274 | for (int i = 0; i < flag_checks.size(); i++) { |
275 | if (flag_checks[i]->is_pressed()) { |
276 | int flag_value = flag_checks[i]->get_meta("_value" ); |
277 | value |= flag_value; |
278 | } |
279 | } |
280 | |
281 | bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); |
282 | if (is_expand) { |
283 | value |= Control::SIZE_EXPAND; |
284 | } |
285 | |
286 | // Keep the custom preset selected as we toggle individual flags. |
287 | keep_selected_preset = true; |
288 | emit_changed(get_edited_property(), value); |
289 | } |
290 | |
291 | void EditorPropertySizeFlags::update_property() { |
292 | uint32_t value = get_edited_property_value(); |
293 | |
294 | for (int i = 0; i < flag_checks.size(); i++) { |
295 | int flag_value = flag_checks[i]->get_meta("_value" ); |
296 | if (value & flag_value) { |
297 | flag_checks[i]->set_pressed(true); |
298 | } else { |
299 | flag_checks[i]->set_pressed(false); |
300 | } |
301 | } |
302 | |
303 | bool is_expand = value & Control::SIZE_EXPAND; |
304 | flag_expand->set_pressed(is_expand); |
305 | |
306 | if (keep_selected_preset) { |
307 | keep_selected_preset = false; |
308 | return; |
309 | } |
310 | |
311 | FlagPreset preset = SIZE_FLAGS_PRESET_CUSTOM; |
312 | if (value == Control::SIZE_FILL || value == (Control::SIZE_FILL | Control::SIZE_EXPAND)) { |
313 | preset = SIZE_FLAGS_PRESET_FILL; |
314 | } else if (value == Control::SIZE_SHRINK_BEGIN || value == (Control::SIZE_SHRINK_BEGIN | Control::SIZE_EXPAND)) { |
315 | preset = SIZE_FLAGS_PRESET_SHRINK_BEGIN; |
316 | } else if (value == Control::SIZE_SHRINK_CENTER || value == (Control::SIZE_SHRINK_CENTER | Control::SIZE_EXPAND)) { |
317 | preset = SIZE_FLAGS_PRESET_SHRINK_CENTER; |
318 | } else if (value == Control::SIZE_SHRINK_END || value == (Control::SIZE_SHRINK_END | Control::SIZE_EXPAND)) { |
319 | preset = SIZE_FLAGS_PRESET_SHRINK_END; |
320 | } |
321 | |
322 | int preset_idx = flag_presets->get_item_index(preset); |
323 | if (preset_idx >= 0) { |
324 | flag_presets->select(preset_idx); |
325 | } |
326 | flag_options->set_visible(preset == SIZE_FLAGS_PRESET_CUSTOM); |
327 | } |
328 | |
329 | void EditorPropertySizeFlags::setup(const Vector<String> &p_options, bool p_vertical) { |
330 | vertical = p_vertical; |
331 | |
332 | if (p_options.size() == 0) { |
333 | flag_presets->clear(); |
334 | flag_presets->add_item(TTR("Container Default" )); |
335 | flag_presets->set_disabled(true); |
336 | flag_expand->set_visible(false); |
337 | return; |
338 | } |
339 | |
340 | HashMap<int, String> flags; |
341 | for (int i = 0, j = 0; i < p_options.size(); i++, j++) { |
342 | Vector<String> text_split = p_options[i].split(":" ); |
343 | int64_t current_val = text_split[1].to_int(); |
344 | flags[current_val] = text_split[0]; |
345 | |
346 | if (current_val == SIZE_EXPAND) { |
347 | continue; |
348 | } |
349 | |
350 | CheckBox *cb = memnew(CheckBox); |
351 | cb->set_text(text_split[0]); |
352 | cb->set_clip_text(true); |
353 | cb->set_meta("_value" , current_val); |
354 | cb->connect("pressed" , callable_mp(this, &EditorPropertySizeFlags::_flag_toggled)); |
355 | add_focusable(cb); |
356 | |
357 | flag_options->add_child(cb); |
358 | flag_checks.append(cb); |
359 | } |
360 | |
361 | Control *gui_base = EditorNode::get_singleton()->get_gui_base(); |
362 | String wide_preset_icon = SNAME("ControlAlignHCenterWide" ); |
363 | String begin_preset_icon = SNAME("ControlAlignCenterLeft" ); |
364 | String end_preset_icon = SNAME("ControlAlignCenterRight" ); |
365 | if (vertical) { |
366 | wide_preset_icon = SNAME("ControlAlignVCenterWide" ); |
367 | begin_preset_icon = SNAME("ControlAlignCenterTop" ); |
368 | end_preset_icon = SNAME("ControlAlignCenterBottom" ); |
369 | } |
370 | |
371 | flag_presets->clear(); |
372 | if (flags.has(SIZE_FILL)) { |
373 | flag_presets->add_icon_item(gui_base->get_editor_theme_icon(wide_preset_icon), TTR("Fill" ), SIZE_FLAGS_PRESET_FILL); |
374 | } |
375 | // Shrink Begin is the same as no flags at all, as such it cannot be disabled. |
376 | flag_presets->add_icon_item(gui_base->get_editor_theme_icon(begin_preset_icon), TTR("Shrink Begin" ), SIZE_FLAGS_PRESET_SHRINK_BEGIN); |
377 | if (flags.has(SIZE_SHRINK_CENTER)) { |
378 | flag_presets->add_icon_item(gui_base->get_editor_theme_icon(SNAME("ControlAlignCenter" )), TTR("Shrink Center" ), SIZE_FLAGS_PRESET_SHRINK_CENTER); |
379 | } |
380 | if (flags.has(SIZE_SHRINK_END)) { |
381 | flag_presets->add_icon_item(gui_base->get_editor_theme_icon(end_preset_icon), TTR("Shrink End" ), SIZE_FLAGS_PRESET_SHRINK_END); |
382 | } |
383 | flag_presets->add_separator(); |
384 | flag_presets->add_item(TTR("Custom" ), SIZE_FLAGS_PRESET_CUSTOM); |
385 | |
386 | flag_expand->set_visible(flags.has(SIZE_EXPAND)); |
387 | } |
388 | |
389 | EditorPropertySizeFlags::EditorPropertySizeFlags() { |
390 | VBoxContainer *vb = memnew(VBoxContainer); |
391 | add_child(vb); |
392 | |
393 | flag_presets = memnew(OptionButton); |
394 | flag_presets->set_clip_text(true); |
395 | flag_presets->set_flat(true); |
396 | vb->add_child(flag_presets); |
397 | add_focusable(flag_presets); |
398 | set_label_reference(flag_presets); |
399 | flag_presets->connect("item_selected" , callable_mp(this, &EditorPropertySizeFlags::_preset_selected)); |
400 | |
401 | flag_options = memnew(VBoxContainer); |
402 | flag_options->hide(); |
403 | vb->add_child(flag_options); |
404 | |
405 | flag_expand = memnew(CheckBox); |
406 | flag_expand->set_text(TTR("Expand" )); |
407 | vb->add_child(flag_expand); |
408 | add_focusable(flag_expand); |
409 | flag_expand->connect("pressed" , callable_mp(this, &EditorPropertySizeFlags::_expand_toggled)); |
410 | } |
411 | |
412 | bool EditorInspectorPluginControl::can_handle(Object *p_object) { |
413 | return Object::cast_to<Control>(p_object) != nullptr; |
414 | } |
415 | |
416 | void EditorInspectorPluginControl::parse_group(Object *p_object, const String &p_group) { |
417 | Control *control = Object::cast_to<Control>(p_object); |
418 | if (!control || p_group != "Layout" ) { |
419 | return; |
420 | } |
421 | |
422 | ControlPositioningWarning *pos_warning = memnew(ControlPositioningWarning); |
423 | pos_warning->set_control(control); |
424 | add_custom_control(pos_warning); |
425 | } |
426 | |
427 | bool EditorInspectorPluginControl::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { |
428 | Control *control = Object::cast_to<Control>(p_object); |
429 | if (!control) { |
430 | return false; |
431 | } |
432 | |
433 | if (p_path == "anchors_preset" ) { |
434 | EditorPropertyAnchorsPreset *prop_editor = memnew(EditorPropertyAnchorsPreset); |
435 | Vector<String> options = p_hint_text.split("," ); |
436 | prop_editor->setup(options); |
437 | add_property_editor(p_path, prop_editor); |
438 | |
439 | return true; |
440 | } |
441 | |
442 | if (p_path == "size_flags_horizontal" || p_path == "size_flags_vertical" ) { |
443 | EditorPropertySizeFlags *prop_editor = memnew(EditorPropertySizeFlags); |
444 | Vector<String> options; |
445 | if (!p_hint_text.is_empty()) { |
446 | options = p_hint_text.split("," ); |
447 | } |
448 | prop_editor->setup(options, p_path == "size_flags_vertical" ); |
449 | add_property_editor(p_path, prop_editor); |
450 | |
451 | return true; |
452 | } |
453 | |
454 | return false; |
455 | } |
456 | |
457 | // Toolbars controls. |
458 | |
459 | Size2 ControlEditorPopupButton::() const { |
460 | Vector2 base_size = Vector2(26, 26) * EDSCALE; |
461 | |
462 | if (arrow_icon.is_null()) { |
463 | return base_size; |
464 | } |
465 | |
466 | Vector2 final_size; |
467 | final_size.x = base_size.x + arrow_icon->get_width(); |
468 | final_size.y = MAX(base_size.y, arrow_icon->get_height()); |
469 | |
470 | return final_size; |
471 | } |
472 | |
473 | void ControlEditorPopupButton::(bool p_pressed) { |
474 | if (!p_pressed) { |
475 | return; |
476 | } |
477 | |
478 | Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); |
479 | |
480 | popup_panel->set_size(Size2(size.width, 0)); |
481 | Point2 gp = get_screen_position(); |
482 | gp.y += size.y; |
483 | if (is_layout_rtl()) { |
484 | gp.x += size.width - popup_panel->get_size().width; |
485 | } |
486 | popup_panel->set_position(gp); |
487 | |
488 | popup_panel->popup(); |
489 | } |
490 | |
491 | void ControlEditorPopupButton::(bool p_visible) { |
492 | set_pressed(p_visible); |
493 | } |
494 | |
495 | void ControlEditorPopupButton::(int p_what) { |
496 | switch (p_what) { |
497 | case NOTIFICATION_ENTER_TREE: |
498 | case NOTIFICATION_THEME_CHANGED: { |
499 | arrow_icon = get_theme_icon("select_arrow" , "Tree" ); |
500 | } break; |
501 | |
502 | case NOTIFICATION_DRAW: { |
503 | if (arrow_icon.is_valid()) { |
504 | Vector2 arrow_pos = Point2(26, 0) * EDSCALE; |
505 | arrow_pos.y = get_size().y / 2 - arrow_icon->get_height() / 2; |
506 | draw_texture(arrow_icon, arrow_pos); |
507 | } |
508 | } break; |
509 | |
510 | case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { |
511 | popup_panel->set_layout_direction((Window::LayoutDirection)get_layout_direction()); |
512 | } break; |
513 | |
514 | case NOTIFICATION_VISIBILITY_CHANGED: { |
515 | if (!is_visible_in_tree()) { |
516 | popup_panel->hide(); |
517 | } |
518 | } break; |
519 | } |
520 | } |
521 | |
522 | ControlEditorPopupButton::() { |
523 | set_flat(true); |
524 | set_toggle_mode(true); |
525 | set_focus_mode(FOCUS_NONE); |
526 | |
527 | popup_panel = memnew(PopupPanel); |
528 | popup_panel->set_theme_type_variation("ControlEditorPopupPanel" ); |
529 | add_child(popup_panel); |
530 | popup_panel->connect("about_to_popup" , callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(true)); |
531 | popup_panel->connect("popup_hide" , callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(false)); |
532 | |
533 | popup_vbox = memnew(VBoxContainer); |
534 | popup_panel->add_child(popup_vbox); |
535 | } |
536 | |
537 | void ControlEditorPresetPicker::_add_row_button(HBoxContainer *p_row, const int p_preset, const String &p_name) { |
538 | ERR_FAIL_COND(preset_buttons.has(p_preset)); |
539 | |
540 | Button *b = memnew(Button); |
541 | b->set_custom_minimum_size(Size2i(36, 36) * EDSCALE); |
542 | b->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); |
543 | b->set_tooltip_text(p_name); |
544 | b->set_flat(true); |
545 | p_row->add_child(b); |
546 | b->connect("pressed" , callable_mp(this, &ControlEditorPresetPicker::_preset_button_pressed).bind(p_preset)); |
547 | |
548 | preset_buttons[p_preset] = b; |
549 | } |
550 | |
551 | void ControlEditorPresetPicker::_add_separator(BoxContainer *p_box, Separator *p_separator) { |
552 | p_separator->add_theme_constant_override("separation" , grid_separation); |
553 | p_separator->set_custom_minimum_size(Size2i(1, 1)); |
554 | p_box->add_child(p_separator); |
555 | } |
556 | |
557 | void AnchorPresetPicker::_preset_button_pressed(const int p_preset) { |
558 | emit_signal("anchors_preset_selected" , p_preset); |
559 | } |
560 | |
561 | void AnchorPresetPicker::_notification(int p_notification) { |
562 | switch (p_notification) { |
563 | case NOTIFICATION_ENTER_TREE: |
564 | case NOTIFICATION_THEME_CHANGED: { |
565 | preset_buttons[PRESET_TOP_LEFT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignTopLeft" ))); |
566 | preset_buttons[PRESET_CENTER_TOP]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterTop" ))); |
567 | preset_buttons[PRESET_TOP_RIGHT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignTopRight" ))); |
568 | |
569 | preset_buttons[PRESET_CENTER_LEFT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterLeft" ))); |
570 | preset_buttons[PRESET_CENTER]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenter" ))); |
571 | preset_buttons[PRESET_CENTER_RIGHT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterRight" ))); |
572 | |
573 | preset_buttons[PRESET_BOTTOM_LEFT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignBottomLeft" ))); |
574 | preset_buttons[PRESET_CENTER_BOTTOM]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterBottom" ))); |
575 | preset_buttons[PRESET_BOTTOM_RIGHT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignBottomRight" ))); |
576 | |
577 | preset_buttons[PRESET_TOP_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignTopWide" ))); |
578 | preset_buttons[PRESET_HCENTER_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignHCenterWide" ))); |
579 | preset_buttons[PRESET_BOTTOM_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignBottomWide" ))); |
580 | |
581 | preset_buttons[PRESET_LEFT_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignLeftWide" ))); |
582 | preset_buttons[PRESET_VCENTER_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignVCenterWide" ))); |
583 | preset_buttons[PRESET_RIGHT_WIDE]->set_icon(get_editor_theme_icon(SNAME("ControlAlignRightWide" ))); |
584 | |
585 | preset_buttons[PRESET_FULL_RECT]->set_icon(get_editor_theme_icon(SNAME("ControlAlignFullRect" ))); |
586 | } break; |
587 | } |
588 | } |
589 | |
590 | void AnchorPresetPicker::_bind_methods() { |
591 | ADD_SIGNAL(MethodInfo("anchors_preset_selected" , PropertyInfo(Variant::INT, "preset" ))); |
592 | } |
593 | |
594 | AnchorPresetPicker::AnchorPresetPicker() { |
595 | VBoxContainer *main_vb = memnew(VBoxContainer); |
596 | main_vb->add_theme_constant_override("separation" , grid_separation); |
597 | add_child(main_vb); |
598 | |
599 | HBoxContainer *top_row = memnew(HBoxContainer); |
600 | top_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); |
601 | top_row->add_theme_constant_override("separation" , grid_separation); |
602 | main_vb->add_child(top_row); |
603 | |
604 | _add_row_button(top_row, PRESET_TOP_LEFT, TTR("Top Left" )); |
605 | _add_row_button(top_row, PRESET_CENTER_TOP, TTR("Center Top" )); |
606 | _add_row_button(top_row, PRESET_TOP_RIGHT, TTR("Top Right" )); |
607 | _add_separator(top_row, memnew(VSeparator)); |
608 | _add_row_button(top_row, PRESET_TOP_WIDE, TTR("Top Wide" )); |
609 | |
610 | HBoxContainer *mid_row = memnew(HBoxContainer); |
611 | mid_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); |
612 | mid_row->add_theme_constant_override("separation" , grid_separation); |
613 | main_vb->add_child(mid_row); |
614 | |
615 | _add_row_button(mid_row, PRESET_CENTER_LEFT, TTR("Center Left" )); |
616 | _add_row_button(mid_row, PRESET_CENTER, TTR("Center" )); |
617 | _add_row_button(mid_row, PRESET_CENTER_RIGHT, TTR("Center Right" )); |
618 | _add_separator(mid_row, memnew(VSeparator)); |
619 | _add_row_button(mid_row, PRESET_HCENTER_WIDE, TTR("HCenter Wide" )); |
620 | |
621 | HBoxContainer *bot_row = memnew(HBoxContainer); |
622 | bot_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); |
623 | bot_row->add_theme_constant_override("separation" , grid_separation); |
624 | main_vb->add_child(bot_row); |
625 | |
626 | _add_row_button(bot_row, PRESET_BOTTOM_LEFT, TTR("Bottom Left" )); |
627 | _add_row_button(bot_row, PRESET_CENTER_BOTTOM, TTR("Center Bottom" )); |
628 | _add_row_button(bot_row, PRESET_BOTTOM_RIGHT, TTR("Bottom Right" )); |
629 | _add_separator(bot_row, memnew(VSeparator)); |
630 | _add_row_button(bot_row, PRESET_BOTTOM_WIDE, TTR("Bottom Wide" )); |
631 | |
632 | _add_separator(main_vb, memnew(HSeparator)); |
633 | |
634 | HBoxContainer * = memnew(HBoxContainer); |
635 | extra_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); |
636 | extra_row->add_theme_constant_override("separation" , grid_separation); |
637 | main_vb->add_child(extra_row); |
638 | |
639 | _add_row_button(extra_row, PRESET_LEFT_WIDE, TTR("Left Wide" )); |
640 | _add_row_button(extra_row, PRESET_VCENTER_WIDE, TTR("VCenter Wide" )); |
641 | _add_row_button(extra_row, PRESET_RIGHT_WIDE, TTR("Right Wide" )); |
642 | _add_separator(extra_row, memnew(VSeparator)); |
643 | _add_row_button(extra_row, PRESET_FULL_RECT, TTR("Full Rect" )); |
644 | } |
645 | |
646 | void SizeFlagPresetPicker::_preset_button_pressed(const int p_preset) { |
647 | int flags = (SizeFlags)p_preset; |
648 | if (expand_button->is_pressed()) { |
649 | flags |= SIZE_EXPAND; |
650 | } |
651 | |
652 | emit_signal("size_flags_selected" , flags); |
653 | } |
654 | |
655 | void SizeFlagPresetPicker::set_allowed_flags(Vector<SizeFlags> &p_flags) { |
656 | preset_buttons[SIZE_SHRINK_BEGIN]->set_disabled(!p_flags.has(SIZE_SHRINK_BEGIN)); |
657 | preset_buttons[SIZE_SHRINK_CENTER]->set_disabled(!p_flags.has(SIZE_SHRINK_CENTER)); |
658 | preset_buttons[SIZE_SHRINK_END]->set_disabled(!p_flags.has(SIZE_SHRINK_END)); |
659 | preset_buttons[SIZE_FILL]->set_disabled(!p_flags.has(SIZE_FILL)); |
660 | |
661 | expand_button->set_disabled(!p_flags.has(SIZE_EXPAND)); |
662 | if (p_flags.has(SIZE_EXPAND)) { |
663 | expand_button->set_tooltip_text(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags." )); |
664 | } else { |
665 | expand_button->set_pressed(false); |
666 | expand_button->set_tooltip_text(TTR("Some parents of the selected nodes do not support the Expand flag." )); |
667 | } |
668 | } |
669 | |
670 | void SizeFlagPresetPicker::_notification(int p_notification) { |
671 | switch (p_notification) { |
672 | case NOTIFICATION_ENTER_TREE: |
673 | case NOTIFICATION_THEME_CHANGED: { |
674 | if (vertical) { |
675 | preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterTop" ))); |
676 | preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenter" ))); |
677 | preset_buttons[SIZE_SHRINK_END]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterBottom" ))); |
678 | |
679 | preset_buttons[SIZE_FILL]->set_icon(get_editor_theme_icon(SNAME("ControlAlignVCenterWide" ))); |
680 | } else { |
681 | preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterLeft" ))); |
682 | preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenter" ))); |
683 | preset_buttons[SIZE_SHRINK_END]->set_icon(get_editor_theme_icon(SNAME("ControlAlignCenterRight" ))); |
684 | |
685 | preset_buttons[SIZE_FILL]->set_icon(get_editor_theme_icon(SNAME("ControlAlignHCenterWide" ))); |
686 | } |
687 | } break; |
688 | } |
689 | } |
690 | |
691 | void SizeFlagPresetPicker::_bind_methods() { |
692 | ADD_SIGNAL(MethodInfo("size_flags_selected" , PropertyInfo(Variant::INT, "size_flags" ))); |
693 | } |
694 | |
695 | SizeFlagPresetPicker::SizeFlagPresetPicker(bool p_vertical) { |
696 | vertical = p_vertical; |
697 | |
698 | VBoxContainer *main_vb = memnew(VBoxContainer); |
699 | add_child(main_vb); |
700 | |
701 | HBoxContainer *main_row = memnew(HBoxContainer); |
702 | main_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); |
703 | main_row->add_theme_constant_override("separation" , grid_separation); |
704 | main_vb->add_child(main_row); |
705 | |
706 | _add_row_button(main_row, SIZE_SHRINK_BEGIN, TTR("Shrink Begin" )); |
707 | _add_row_button(main_row, SIZE_SHRINK_CENTER, TTR("Shrink Center" )); |
708 | _add_row_button(main_row, SIZE_SHRINK_END, TTR("Shrink End" )); |
709 | _add_separator(main_row, memnew(VSeparator)); |
710 | _add_row_button(main_row, SIZE_FILL, TTR("Fill" )); |
711 | |
712 | expand_button = memnew(CheckBox); |
713 | expand_button->set_flat(true); |
714 | expand_button->set_text(TTR("Align with Expand" )); |
715 | expand_button->set_tooltip_text(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags." )); |
716 | main_vb->add_child(expand_button); |
717 | } |
718 | |
719 | // Toolbar. |
720 | |
721 | void ControlEditorToolbar::_anchors_preset_selected(int p_preset) { |
722 | LayoutPreset preset = (LayoutPreset)p_preset; |
723 | List<Node *> selection = editor_selection->get_selected_node_list(); |
724 | |
725 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
726 | undo_redo->create_action(TTR("Change Anchors, Offsets, Grow Direction" )); |
727 | |
728 | for (Node *E : selection) { |
729 | Control *control = Object::cast_to<Control>(E); |
730 | if (control) { |
731 | undo_redo->add_do_property(control, "layout_mode" , LayoutMode::LAYOUT_MODE_ANCHORS); |
732 | undo_redo->add_do_property(control, "anchors_preset" , preset); |
733 | undo_redo->add_undo_method(control, "_edit_set_state" , control->_edit_get_state()); |
734 | } |
735 | } |
736 | |
737 | undo_redo->commit_action(); |
738 | |
739 | anchors_mode = false; |
740 | anchor_mode_button->set_pressed(anchors_mode); |
741 | } |
742 | |
743 | void ControlEditorToolbar::_anchors_to_current_ratio() { |
744 | List<Node *> selection = editor_selection->get_selected_node_list(); |
745 | |
746 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
747 | undo_redo->create_action(TTR("Change Anchors, Offsets (Keep Ratio)" )); |
748 | |
749 | for (Node *E : selection) { |
750 | Control *control = Object::cast_to<Control>(E); |
751 | if (control) { |
752 | Point2 top_left_anchor = _position_to_anchor(control, Point2()); |
753 | Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); |
754 | undo_redo->add_do_method(control, "set_anchor" , SIDE_LEFT, top_left_anchor.x, false, true); |
755 | undo_redo->add_do_method(control, "set_anchor" , SIDE_RIGHT, bottom_right_anchor.x, false, true); |
756 | undo_redo->add_do_method(control, "set_anchor" , SIDE_TOP, top_left_anchor.y, false, true); |
757 | undo_redo->add_do_method(control, "set_anchor" , SIDE_BOTTOM, bottom_right_anchor.y, false, true); |
758 | undo_redo->add_do_method(control, "set_meta" , "_edit_use_anchors_" , true); |
759 | |
760 | const bool use_anchors = control->get_meta("_edit_use_anchors_" , false); |
761 | undo_redo->add_undo_method(control, "_edit_set_state" , control->_edit_get_state()); |
762 | if (use_anchors) { |
763 | undo_redo->add_undo_method(control, "set_meta" , "_edit_use_anchors_" , true); |
764 | } else { |
765 | undo_redo->add_undo_method(control, "remove_meta" , "_edit_use_anchors_" ); |
766 | } |
767 | |
768 | anchors_mode = true; |
769 | anchor_mode_button->set_pressed(anchors_mode); |
770 | } |
771 | } |
772 | |
773 | undo_redo->commit_action(); |
774 | } |
775 | |
776 | void ControlEditorToolbar::_anchor_mode_toggled(bool p_status) { |
777 | List<Control *> selection = _get_edited_controls(); |
778 | for (Control *E : selection) { |
779 | if (Object::cast_to<Container>(E->get_parent())) { |
780 | continue; |
781 | } |
782 | |
783 | if (p_status) { |
784 | E->set_meta("_edit_use_anchors_" , true); |
785 | } else { |
786 | E->remove_meta("_edit_use_anchors_" ); |
787 | } |
788 | } |
789 | |
790 | anchors_mode = p_status; |
791 | CanvasItemEditor::get_singleton()->update_viewport(); |
792 | } |
793 | |
794 | void ControlEditorToolbar::_container_flags_selected(int p_flags, bool p_vertical) { |
795 | List<Node *> selection = editor_selection->get_selected_node_list(); |
796 | |
797 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
798 | if (p_vertical) { |
799 | undo_redo->create_action(TTR("Change Vertical Size Flags" )); |
800 | } else { |
801 | undo_redo->create_action(TTR("Change Horizontal Size Flags" )); |
802 | } |
803 | |
804 | for (Node *E : selection) { |
805 | Control *control = Object::cast_to<Control>(E); |
806 | if (control) { |
807 | if (p_vertical) { |
808 | undo_redo->add_do_method(control, "set_v_size_flags" , p_flags); |
809 | } else { |
810 | undo_redo->add_do_method(control, "set_h_size_flags" , p_flags); |
811 | } |
812 | undo_redo->add_undo_method(control, "_edit_set_state" , control->_edit_get_state()); |
813 | } |
814 | } |
815 | |
816 | undo_redo->commit_action(); |
817 | } |
818 | |
819 | Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vector2 position) { |
820 | ERR_FAIL_NULL_V(p_control, Vector2()); |
821 | |
822 | Rect2 parent_rect = p_control->get_parent_anchorable_rect(); |
823 | |
824 | Vector2 output; |
825 | if (p_control->is_layout_rtl()) { |
826 | output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; |
827 | } else { |
828 | output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; |
829 | } |
830 | output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y; |
831 | return output; |
832 | } |
833 | |
834 | bool ControlEditorToolbar::_is_node_locked(const Node *p_node) { |
835 | return p_node->get_meta("_edit_lock_" , false); |
836 | } |
837 | |
838 | List<Control *> ControlEditorToolbar::_get_edited_controls() { |
839 | List<Control *> selection; |
840 | for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { |
841 | Control *control = Object::cast_to<Control>(E.key); |
842 | if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && !_is_node_locked(control)) { |
843 | selection.push_back(control); |
844 | } |
845 | } |
846 | |
847 | return selection; |
848 | } |
849 | |
850 | void ControlEditorToolbar::_selection_changed() { |
851 | // Update toolbar visibility. |
852 | bool has_controls = false; |
853 | bool has_control_parents = false; |
854 | bool has_container_parents = false; |
855 | |
856 | // Also update which size flags can be configured for the selected nodes. |
857 | Vector<SizeFlags> allowed_h_flags = { |
858 | SIZE_SHRINK_BEGIN, |
859 | SIZE_SHRINK_CENTER, |
860 | SIZE_SHRINK_END, |
861 | SIZE_FILL, |
862 | SIZE_EXPAND, |
863 | }; |
864 | Vector<SizeFlags> allowed_v_flags = { |
865 | SIZE_SHRINK_BEGIN, |
866 | SIZE_SHRINK_CENTER, |
867 | SIZE_SHRINK_END, |
868 | SIZE_FILL, |
869 | SIZE_EXPAND, |
870 | }; |
871 | |
872 | for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { |
873 | Control *control = Object::cast_to<Control>(E.key); |
874 | if (!control) { |
875 | continue; |
876 | } |
877 | has_controls = true; |
878 | |
879 | if (Object::cast_to<Control>(control->get_parent())) { |
880 | has_control_parents = true; |
881 | } |
882 | if (Object::cast_to<Container>(control->get_parent())) { |
883 | has_container_parents = true; |
884 | |
885 | Container *parent_container = Object::cast_to<Container>(control->get_parent()); |
886 | |
887 | Vector<int> container_h_flags = parent_container->get_allowed_size_flags_horizontal(); |
888 | Vector<SizeFlags> tmp_flags = allowed_h_flags.duplicate(); |
889 | for (int i = 0; i < allowed_h_flags.size(); i++) { |
890 | if (!container_h_flags.has((int)allowed_h_flags[i])) { |
891 | tmp_flags.erase(allowed_h_flags[i]); |
892 | } |
893 | } |
894 | allowed_h_flags = tmp_flags; |
895 | |
896 | Vector<int> container_v_flags = parent_container->get_allowed_size_flags_vertical(); |
897 | tmp_flags = allowed_v_flags.duplicate(); |
898 | for (int i = 0; i < allowed_v_flags.size(); i++) { |
899 | if (!container_v_flags.has((int)allowed_v_flags[i])) { |
900 | tmp_flags.erase(allowed_v_flags[i]); |
901 | } |
902 | } |
903 | allowed_v_flags = tmp_flags; |
904 | } |
905 | } |
906 | |
907 | // Set general toolbar visibility. |
908 | set_visible(has_controls); |
909 | |
910 | // Set anchor tools visibility. |
911 | if (has_controls && (!has_control_parents || !has_container_parents)) { |
912 | anchors_button->set_visible(true); |
913 | anchor_mode_button->set_visible(true); |
914 | |
915 | // Update anchor mode. |
916 | int nb_valid_controls = 0; |
917 | int nb_anchors_mode = 0; |
918 | |
919 | List<Node *> selection = editor_selection->get_selected_node_list(); |
920 | for (Node *E : selection) { |
921 | Control *control = Object::cast_to<Control>(E); |
922 | if (!control) { |
923 | continue; |
924 | } |
925 | if (Object::cast_to<Container>(control->get_parent())) { |
926 | continue; |
927 | } |
928 | |
929 | nb_valid_controls++; |
930 | if (control->get_meta("_edit_use_anchors_" , false)) { |
931 | nb_anchors_mode++; |
932 | } |
933 | } |
934 | |
935 | anchors_mode = (nb_valid_controls == nb_anchors_mode); |
936 | anchor_mode_button->set_pressed(anchors_mode); |
937 | } else { |
938 | anchors_button->set_visible(false); |
939 | anchor_mode_button->set_visible(false); |
940 | anchor_mode_button->set_pressed(false); |
941 | } |
942 | |
943 | // Set container tools visibility. |
944 | if (has_controls && (!has_control_parents || has_container_parents)) { |
945 | containers_button->set_visible(true); |
946 | |
947 | // Update allowed size flags. |
948 | if (has_container_parents) { |
949 | container_h_picker->set_allowed_flags(allowed_h_flags); |
950 | container_v_picker->set_allowed_flags(allowed_v_flags); |
951 | } else { |
952 | Vector<SizeFlags> allowed_all_flags = { |
953 | SIZE_SHRINK_BEGIN, |
954 | SIZE_SHRINK_CENTER, |
955 | SIZE_SHRINK_END, |
956 | SIZE_FILL, |
957 | SIZE_EXPAND, |
958 | }; |
959 | |
960 | container_h_picker->set_allowed_flags(allowed_all_flags); |
961 | container_v_picker->set_allowed_flags(allowed_all_flags); |
962 | } |
963 | } else { |
964 | containers_button->set_visible(false); |
965 | } |
966 | } |
967 | |
968 | void ControlEditorToolbar::_notification(int p_what) { |
969 | switch (p_what) { |
970 | case NOTIFICATION_ENTER_TREE: |
971 | case NOTIFICATION_THEME_CHANGED: { |
972 | anchors_button->set_icon(get_editor_theme_icon(SNAME("ControlLayout" ))); |
973 | anchor_mode_button->set_icon(get_editor_theme_icon(SNAME("Anchor" ))); |
974 | containers_button->set_icon(get_editor_theme_icon(SNAME("ContainerLayout" ))); |
975 | } break; |
976 | } |
977 | } |
978 | |
979 | ControlEditorToolbar::ControlEditorToolbar() { |
980 | // Anchor and offset tools. |
981 | anchors_button = memnew(ControlEditorPopupButton); |
982 | anchors_button->set_tooltip_text(TTR("Presets for the anchor and offset values of a Control node." )); |
983 | add_child(anchors_button); |
984 | |
985 | Label *anchors_label = memnew(Label); |
986 | anchors_label->set_text(TTR("Anchor preset" )); |
987 | anchors_button->get_popup_hbox()->add_child(anchors_label); |
988 | AnchorPresetPicker *anchors_picker = memnew(AnchorPresetPicker); |
989 | anchors_picker->set_h_size_flags(SIZE_SHRINK_CENTER); |
990 | anchors_button->get_popup_hbox()->add_child(anchors_picker); |
991 | anchors_picker->connect("anchors_preset_selected" , callable_mp(this, &ControlEditorToolbar::_anchors_preset_selected)); |
992 | |
993 | anchors_button->get_popup_hbox()->add_child(memnew(HSeparator)); |
994 | |
995 | Button *keep_ratio_button = memnew(Button); |
996 | keep_ratio_button->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); |
997 | keep_ratio_button->set_text(TTR("Set to Current Ratio" )); |
998 | keep_ratio_button->set_tooltip_text(TTR("Adjust anchors and offsets to match the current rect size." )); |
999 | anchors_button->get_popup_hbox()->add_child(keep_ratio_button); |
1000 | keep_ratio_button->connect("pressed" , callable_mp(this, &ControlEditorToolbar::_anchors_to_current_ratio)); |
1001 | |
1002 | anchor_mode_button = memnew(Button); |
1003 | anchor_mode_button->set_flat(true); |
1004 | anchor_mode_button->set_toggle_mode(true); |
1005 | anchor_mode_button->set_tooltip_text(TTR("When active, moving Control nodes changes their anchors instead of their offsets." )); |
1006 | add_child(anchor_mode_button); |
1007 | anchor_mode_button->connect("toggled" , callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled)); |
1008 | |
1009 | // Container tools. |
1010 | containers_button = memnew(ControlEditorPopupButton); |
1011 | containers_button->set_tooltip_text(TTR("Sizing settings for children of a Container node." )); |
1012 | add_child(containers_button); |
1013 | |
1014 | Label *container_h_label = memnew(Label); |
1015 | container_h_label->set_text(TTR("Horizontal alignment" )); |
1016 | containers_button->get_popup_hbox()->add_child(container_h_label); |
1017 | container_h_picker = memnew(SizeFlagPresetPicker(false)); |
1018 | containers_button->get_popup_hbox()->add_child(container_h_picker); |
1019 | container_h_picker->connect("size_flags_selected" , callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(false)); |
1020 | |
1021 | containers_button->get_popup_hbox()->add_child(memnew(HSeparator)); |
1022 | |
1023 | Label *container_v_label = memnew(Label); |
1024 | container_v_label->set_text(TTR("Vertical alignment" )); |
1025 | containers_button->get_popup_hbox()->add_child(container_v_label); |
1026 | container_v_picker = memnew(SizeFlagPresetPicker(true)); |
1027 | containers_button->get_popup_hbox()->add_child(container_v_picker); |
1028 | container_v_picker->connect("size_flags_selected" , callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(true)); |
1029 | |
1030 | // Editor connections. |
1031 | editor_selection = EditorNode::get_singleton()->get_editor_selection(); |
1032 | editor_selection->add_editor_plugin(this); |
1033 | editor_selection->connect("selection_changed" , callable_mp(this, &ControlEditorToolbar::_selection_changed)); |
1034 | |
1035 | singleton = this; |
1036 | } |
1037 | |
1038 | ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr; |
1039 | |
1040 | // Editor plugin. |
1041 | |
1042 | ControlEditorPlugin::ControlEditorPlugin() { |
1043 | toolbar = memnew(ControlEditorToolbar); |
1044 | toolbar->hide(); |
1045 | add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); |
1046 | |
1047 | Ref<EditorInspectorPluginControl> plugin; |
1048 | plugin.instantiate(); |
1049 | add_inspector_plugin(plugin); |
1050 | } |
1051 | |