| 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 | |