diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 90805e0b51..3922c81788 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -603,6 +603,10 @@ void Control::_validate_property(PropertyInfo &p_property) const { // Use the layout mode to display or hide advanced anchoring properties. LayoutMode _layout = _get_layout_mode(); bool use_anchors = (_layout == LayoutMode::LAYOUT_MODE_ANCHORS || _layout == LayoutMode::LAYOUT_MODE_UNCONTROLLED); + // If using anchors, the position is, well...anchored based on the layout, so manipulating the position doesn't make sense + if (use_anchors && p_property.name == "position") { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } if (!use_anchors && p_property.name == "anchors_preset") { p_property.usage ^= PROPERTY_USAGE_EDITOR; } @@ -785,6 +789,9 @@ void Control::set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset, bool queue_redraw(); } +/// @brief Get a particular side (LEFT, TOP, RIGHT or BOTTOM) of an anchor preset +/// @param `p_side` here is just 0, 1, 2 or 3, based on the Side enum (math_defs.h) +/// @return The value of the anchor at the passed-in side, typically 0.0, 0.5 or 1.0 real_t Control::get_anchor(Side p_side) const { ERR_READ_THREAD_GUARD_V(0); ERR_FAIL_INDEX_V(int(p_side), 4, 0.0); @@ -925,6 +932,7 @@ void Control::_set_layout_mode(LayoutMode p_mode) { data.stored_use_custom_anchors = false; set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); set_grow_direction_preset(LayoutPreset::PRESET_TOP_LEFT); + data.anchor_preset_active = false; } if (list_changed) { @@ -1034,203 +1042,106 @@ void Control::_set_anchors_layout_preset(int p_preset) { } int Control::_get_anchors_layout_preset() const { - // If this is a layout mode that doesn't rely on anchors, avoid excessive checks. - if (data.stored_layout_mode != LayoutMode::LAYOUT_MODE_UNCONTROLLED && data.stored_layout_mode != LayoutMode::LAYOUT_MODE_ANCHORS) { + // Early exit for non-anchor layout modes + if (data.stored_layout_mode != LayoutMode::LAYOUT_MODE_UNCONTROLLED && + data.stored_layout_mode != LayoutMode::LAYOUT_MODE_ANCHORS) { return LayoutPreset::PRESET_TOP_LEFT; } - - // If the custom preset was selected by user, use it. if (data.stored_use_custom_anchors) { return -1; } - // Check anchors to determine if the current state matches a preset, or not. - - float left = get_anchor(SIDE_LEFT); - float right = get_anchor(SIDE_RIGHT); - float top = get_anchor(SIDE_TOP); - float bottom = get_anchor(SIDE_BOTTOM); - - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_BEGIN && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_BEGIN) { - return (int)LayoutPreset::PRESET_TOP_LEFT; - } - if (left == (float)ANCHOR_END && right == (float)ANCHOR_END && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_BEGIN) { - return (int)LayoutPreset::PRESET_TOP_RIGHT; - } - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_BEGIN && top == (float)ANCHOR_END && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_BOTTOM_LEFT; - } - if (left == (float)ANCHOR_END && right == (float)ANCHOR_END && top == (float)ANCHOR_END && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_BOTTOM_RIGHT; - } - - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_BEGIN && top == 0.5 && bottom == 0.5) { - return (int)LayoutPreset::PRESET_CENTER_LEFT; - } - if (left == (float)ANCHOR_END && right == (float)ANCHOR_END && top == 0.5 && bottom == 0.5) { - return (int)LayoutPreset::PRESET_CENTER_RIGHT; - } - if (left == 0.5 && right == 0.5 && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_BEGIN) { - return (int)LayoutPreset::PRESET_CENTER_TOP; - } - if (left == 0.5 && right == 0.5 && top == (float)ANCHOR_END && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_CENTER_BOTTOM; - } - if (left == 0.5 && right == 0.5 && top == 0.5 && bottom == 0.5) { - return (int)LayoutPreset::PRESET_CENTER; - } - - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_BEGIN && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_LEFT_WIDE; - } - if (left == (float)ANCHOR_END && right == (float)ANCHOR_END && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_RIGHT_WIDE; - } - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_END && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_BEGIN) { - return (int)LayoutPreset::PRESET_TOP_WIDE; - } - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_END && top == (float)ANCHOR_END && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_BOTTOM_WIDE; - } - - if (left == 0.5 && right == 0.5 && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_VCENTER_WIDE; - } - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_END && top == 0.5 && bottom == 0.5) { - return (int)LayoutPreset::PRESET_HCENTER_WIDE; - } - - if (left == (float)ANCHOR_BEGIN && right == (float)ANCHOR_END && top == (float)ANCHOR_BEGIN && bottom == (float)ANCHOR_END) { - return (int)LayoutPreset::PRESET_FULL_RECT; - } + // Pack anchor values (each 0.0, 0.5, or 1.0) into a single byte + // 0 stays the same, we'll map 0.5 to 1 and 1 to 2, so we can store them in a bit. + std::function encode = [](real_t val) -> int { + if (val == 0.0f) { + return 0; + } + if (val == 0.5f) { + return 1; + } + if (val == 1.0f) { + return 2; + } + return -1; // Handle unexpected values + }; - // Does not match any preset, return "Custom". - return -1; + uint8_t key = (encode(get_anchor(SIDE_LEFT)) << 6) | (encode(get_anchor(SIDE_TOP)) << 4) | + (encode(get_anchor(SIDE_RIGHT)) << 2) | encode(get_anchor(SIDE_BOTTOM)); + + // Static lookup table with dynamic initialization + static const int *PRESET_LOOKUP = []() -> const int * { + static int table[256]; + // Initialize all entries to -1 (invalid combinations) + // We're storing all possible values in an int using a bitmask. + // Even though we only need 16 for the presets, all other combinations are invalid and need to be populated to return -1 + for (int i = 0; i < 256; ++i) { + table[i] = -1; + } + // Map valid combinations // LEFT | TOP | RIGHT | BOTTOM + table[0x00] = LayoutPreset::PRESET_TOP_LEFT; // 0.0, 0.0, 0.0, 0.0 + table[0x88] = LayoutPreset::PRESET_TOP_RIGHT; // 1.0, 0.0, 1.0, 0.0 + table[0x22] = LayoutPreset::PRESET_BOTTOM_LEFT; // 0.0, 1.0, 0.0, 1.0 + table[0xAA] = LayoutPreset::PRESET_BOTTOM_RIGHT; // 1.0, 1.0, 1.0, 1.0 + table[0x11] = LayoutPreset::PRESET_CENTER_LEFT; // 0.0, 0.5, 0.0, 0.5 + table[0x99] = LayoutPreset::PRESET_CENTER_RIGHT; // 1.0, 0.5, 1.0, 0.5 + table[0x44] = LayoutPreset::PRESET_CENTER_TOP; // 0.5, 0.0, 0.5, 0.0 + table[0x66] = LayoutPreset::PRESET_CENTER_BOTTOM; // 0.5, 1.0, 0.5, 1.0 + table[0x55] = LayoutPreset::PRESET_CENTER; // 0.5, 0.5, 0.5, 0.5 + table[0x02] = LayoutPreset::PRESET_LEFT_WIDE; // 0.0, 0.0, 0.0, 1.0 + table[0x8A] = LayoutPreset::PRESET_RIGHT_WIDE; // 1.0, 0.0, 1.0, 1.0 + table[0x08] = LayoutPreset::PRESET_TOP_WIDE; // 0.0, 0.0, 1.0, 0.0 + table[0x2A] = LayoutPreset::PRESET_BOTTOM_WIDE; // 0.0, 1.0, 1.0, 1.0 + table[0x46] = LayoutPreset::PRESET_VCENTER_WIDE; // 0.5, 0.0, 0.5, 1.0 + table[0x19] = LayoutPreset::PRESET_HCENTER_WIDE; // 0.0, 0.5, 1.0, 0.5 + table[0xA] = LayoutPreset::PRESET_FULL_RECT; // 0.0, 0.0, 1.0, 1.0 + return table; + }(); + + return PRESET_LOOKUP[key]; // Will return -1 if an invalid or "custom" preset } void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_INDEX((int)p_preset, 16); - //Left - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_BOTTOM_LEFT: - case PRESET_CENTER_LEFT: - case PRESET_TOP_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_LEFT_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_FULL_RECT: - set_anchor(SIDE_LEFT, ANCHOR_BEGIN, p_keep_offsets); - break; - - case PRESET_CENTER_TOP: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - case PRESET_VCENTER_WIDE: - set_anchor(SIDE_LEFT, 0.5, p_keep_offsets); - break; - - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_RIGHT: - case PRESET_RIGHT_WIDE: - set_anchor(SIDE_LEFT, ANCHOR_END, p_keep_offsets); - break; - } - - // Top - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_CENTER_TOP: - case PRESET_LEFT_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_TOP_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_FULL_RECT: - set_anchor(SIDE_TOP, ANCHOR_BEGIN, p_keep_offsets); - break; - - case PRESET_CENTER_LEFT: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER: - case PRESET_HCENTER_WIDE: - set_anchor(SIDE_TOP, 0.5, p_keep_offsets); - break; - - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_BOTTOM_WIDE: - set_anchor(SIDE_TOP, ANCHOR_END, p_keep_offsets); - break; - } - - // Right - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_BOTTOM_LEFT: - case PRESET_CENTER_LEFT: - case PRESET_LEFT_WIDE: - set_anchor(SIDE_RIGHT, ANCHOR_BEGIN, p_keep_offsets); - break; - - case PRESET_CENTER_TOP: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - case PRESET_VCENTER_WIDE: - set_anchor(SIDE_RIGHT, 0.5, p_keep_offsets); - break; - - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_RIGHT: - case PRESET_TOP_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_FULL_RECT: - set_anchor(SIDE_RIGHT, ANCHOR_END, p_keep_offsets); - break; - } - - // Bottom - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_CENTER_TOP: - case PRESET_TOP_WIDE: - set_anchor(SIDE_BOTTOM, ANCHOR_BEGIN, p_keep_offsets); - break; - - case PRESET_CENTER_LEFT: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER: - case PRESET_HCENTER_WIDE: - set_anchor(SIDE_BOTTOM, 0.5, p_keep_offsets); - break; + // Lookup table: [preset][side] = anchor_value + // Sides: 0=LEFT, 1=TOP, 2=RIGHT, 3=BOTTOM + // KEEP IN SAME ORDER AS LayoutPreset enum! + static constexpr float ANCHOR_TABLE[16][4] = { + { 0.0f, 0.0f, 0.0f, 0.0f }, // PRESET_TOP_LEFT + { 1.0f, 0.0f, 1.0f, 0.0f }, // PRESET_TOP_RIGHT + { 0.0f, 1.0f, 0.0f, 1.0f }, // PRESET_BOTTOM_LEFT + { 1.0f, 1.0f, 1.0f, 1.0f }, // PRESET_BOTTOM_RIGHT + { 0.0f, 0.5f, 0.0f, 0.5f }, // PRESET_CENTER_LEFT + { 0.5f, 0.0f, 0.5f, 0.0f }, // PRESET_CENTER_TOP + { 1.0f, 0.5f, 1.0f, 0.5f }, // PRESET_CENTER_RIGHT + { 0.5f, 1.0f, 0.5f, 1.0f }, // PRESET_CENTER_BOTTOM + { 0.5f, 0.5f, 0.5f, 0.5f }, // PRESET_CENTER + { 0.0f, 0.0f, 0.0f, 1.0f }, // PRESET_LEFT_WIDE + { 0.0f, 0.0f, 1.0f, 0.0f }, // PRESET_TOP_WIDE + { 1.0f, 0.0f, 1.0f, 1.0f }, // PRESET_RIGHT_WIDE + { 0.0f, 1.0f, 1.0f, 1.0f }, // PRESET_BOTTOM_WIDE + { 0.5f, 0.0f, 0.5f, 1.0f }, // PRESET_VCENTER_WIDE + { 0.0f, 0.5f, 1.0f, 0.5f }, // PRESET_HCENTER_WIDE + { 0.0f, 0.0f, 1.0f, 1.0f }, // PRESET_FULL_RECT + }; - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_LEFT_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_FULL_RECT: - set_anchor(SIDE_BOTTOM, ANCHOR_END, p_keep_offsets); - break; - } + const auto &row = ANCHOR_TABLE[p_preset]; + set_anchor(SIDE_LEFT, row[0], p_keep_offsets); + set_anchor(SIDE_TOP, row[1], p_keep_offsets); + set_anchor(SIDE_RIGHT, row[2], p_keep_offsets); + set_anchor(SIDE_BOTTOM, row[3], p_keep_offsets); } -void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode, int p_margin) { +void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode, int p_preset_margin) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_INDEX((int)p_preset, 16); ERR_FAIL_INDEX((int)p_resize_mode, 4); - // Calculate the size if the node is not resized + data.anchor_preset_active = true; + data.anchor_preset_margin = p_preset_margin; + Size2 min_size = get_minimum_size(); Size2 new_size = get_size(); if (p_resize_mode == PRESET_MODE_MINSIZE || p_resize_mode == PRESET_MODE_KEEP_HEIGHT) { @@ -1240,125 +1151,31 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz new_size.y = min_size.y; } - Rect2 parent_rect = get_parent_anchorable_rect(); - - real_t x = parent_rect.size.x; - if (is_layout_rtl()) { - x = parent_rect.size.x - x - new_size.x; - } - //Left - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_BOTTOM_LEFT: - case PRESET_CENTER_LEFT: - case PRESET_TOP_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_LEFT_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_FULL_RECT: - data.offset[0] = x * (0.0 - data.anchor[0]) + p_margin + parent_rect.position.x; - break; - - case PRESET_CENTER_TOP: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - case PRESET_VCENTER_WIDE: - data.offset[0] = x * (0.5 - data.anchor[0]) - new_size.x / 2 + parent_rect.position.x; - break; - - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_RIGHT: - case PRESET_RIGHT_WIDE: - data.offset[0] = x * (1.0 - data.anchor[0]) - new_size.x - p_margin + parent_rect.position.x; - break; - } - - // Top - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_CENTER_TOP: - case PRESET_LEFT_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_TOP_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_FULL_RECT: - data.offset[1] = parent_rect.size.y * (0.0 - data.anchor[1]) + p_margin + parent_rect.position.y; - break; - - case PRESET_CENTER_LEFT: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER: - case PRESET_HCENTER_WIDE: - data.offset[1] = parent_rect.size.y * (0.5 - data.anchor[1]) - new_size.y / 2 + parent_rect.position.y; - break; - - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_BOTTOM_WIDE: - data.offset[1] = parent_rect.size.y * (1.0 - data.anchor[1]) - new_size.y - p_margin + parent_rect.position.y; - break; - } - - // Right - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_BOTTOM_LEFT: - case PRESET_CENTER_LEFT: - case PRESET_LEFT_WIDE: - data.offset[2] = x * (0.0 - data.anchor[2]) + new_size.x + p_margin + parent_rect.position.x; - break; - - case PRESET_CENTER_TOP: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - case PRESET_VCENTER_WIDE: - data.offset[2] = x * (0.5 - data.anchor[2]) + new_size.x / 2 + parent_rect.position.x; - break; - - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_RIGHT: - case PRESET_TOP_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_FULL_RECT: - data.offset[2] = x * (1.0 - data.anchor[2]) - p_margin + parent_rect.position.x; - break; - } - - // Bottom - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_CENTER_TOP: - case PRESET_TOP_WIDE: - data.offset[3] = parent_rect.size.y * (0.0 - data.anchor[3]) + new_size.y + p_margin + parent_rect.position.y; - break; + _compute_preset_offsets(p_preset, new_size, p_preset_margin); + _size_changed(); +} - case PRESET_CENTER_LEFT: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER: - case PRESET_HCENTER_WIDE: - data.offset[3] = parent_rect.size.y * (0.5 - data.anchor[3]) + new_size.y / 2 + parent_rect.position.y; - break; +void Control::_compute_preset_offsets(LayoutPreset p_preset, const Size2 &p_size, int p_preset_margin) { + Rect2 parent_rect = get_parent_anchorable_rect(); - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_LEFT_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_FULL_RECT: - data.offset[3] = parent_rect.size.y * (1.0 - data.anchor[3]) - p_margin + parent_rect.position.y; - break; - } + OffsetCalcParams params{ + .parent_x_size = parent_rect.size.x, + .parent_y_size = parent_rect.size.y, + .pivot_x_correction = data.pivot_offset.x * (data.scale.x - 1.0f), + .pivot_y_correction = data.pivot_offset.y * (data.scale.y - 1.0f), + .scaled_width = p_size.x * data.scale.x, + .scaled_height = p_size.y * data.scale.y, + .size_x = p_size.x, + .size_y = p_size.y, + .scale_x = data.scale.x, + .scale_y = data.scale.y, + .preset_margin = p_preset_margin, + .parent_x_position = parent_rect.position.x, + .parent_y_position = parent_rect.position.y + }; - _size_changed(); + std::copy(std::begin(data.anchor), std::end(data.anchor), std::begin(params.anchor_position)); + apply_anchor_preset_offset(p_preset, params); } void Control::set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode, int p_margin) { @@ -1441,6 +1258,9 @@ void Control::set_position(const Point2 &p_point, bool p_keep_offsets) { } #endif // TOOLS_ENABLED + ERR_FAIL_COND_MSG(_get_layout_mode() == LayoutMode::LAYOUT_MODE_ANCHORS, + "Cannot set position on a Control whose Layout Mode is Anchors. Set anchor offsets instead."); + if (p_keep_offsets) { _compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor); } else { @@ -1507,10 +1327,23 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) { } #endif // TOOLS_ENABLED + // For corner/center anchor presets, re-align to the preset using the new size. if (p_keep_offsets) { _compute_anchors(Rect2(data.pos_cache, new_size), data.offset, data.anchor); } else { - _compute_offsets(Rect2(data.pos_cache, new_size), data.anchor, data.offset); + bool preset_applied = false; + LayoutMode layout = _get_layout_mode(); + if (is_inside_tree() && data.anchor_preset_active && + (layout == LayoutMode::LAYOUT_MODE_ANCHORS || layout == LayoutMode::LAYOUT_MODE_UNCONTROLLED)) { + int current_preset = _get_anchors_layout_preset(); + if (current_preset != -1) { + _compute_preset_offsets((LayoutPreset)current_preset, new_size, data.anchor_preset_margin); + preset_applied = true; + } + } + if (!preset_applied) { + _compute_offsets(Rect2(data.pos_cache, new_size), data.anchor, data.offset); + } } _size_changed(); } @@ -1576,6 +1409,18 @@ void Control::set_scale(const Vector2 &p_scale) { if (data.scale.y == 0) { data.scale.y = CMP_EPSILON; } + + // Re-apply the anchor preset offsets so that visual alignment (center, right-edge, etc.) stays correct for the new scale value. + LayoutMode layout = _get_layout_mode(); + if (is_inside_tree() && data.anchor_preset_active && + (layout == LayoutMode::LAYOUT_MODE_ANCHORS || layout == LayoutMode::LAYOUT_MODE_UNCONTROLLED)) { + int current_preset = _get_anchors_layout_preset(); + if (current_preset != -1) { + _compute_preset_offsets((LayoutPreset)current_preset, get_size(), data.anchor_preset_margin); + _size_changed(); + } + } + queue_redraw(); _notify_transform(); queue_accessibility_update(); @@ -1620,6 +1465,18 @@ void Control::set_pivot_offset(const Vector2 &p_pivot) { } data.pivot_offset = p_pivot; + + // Re-apply the anchor preset offsets so that visual alignment (center, right-edge, etc.) stays correct for the new pivot value. + LayoutMode layout = _get_layout_mode(); + if (is_inside_tree() && data.anchor_preset_active && + (layout == LayoutMode::LAYOUT_MODE_ANCHORS || layout == LayoutMode::LAYOUT_MODE_UNCONTROLLED)) { + int current_preset = _get_anchors_layout_preset(); + if (current_preset != -1) { + _compute_preset_offsets((LayoutPreset)current_preset, get_size(), data.anchor_preset_margin); + _size_changed(); + } + } + queue_redraw(); _notify_transform(); queue_accessibility_update(); @@ -1757,6 +1614,9 @@ void Control::_size_changed() { new_size_cache.width = minimum_size.width; } + /// @todo This "mirrors" a Control node horizontally, presumably to account for languages like Arabic & Hebrew + /// However, this doesn't discriminate. It should not affect anchor presets (left & right presets, specifically) + /// Need to make this a bit conditional to exclude those. A part of me thinks it might not be right here alone :') if (is_layout_rtl()) { new_pos_cache.x = parent_rect.size.x + 2 * parent_rect.position.x - new_pos_cache.x - new_size_cache.x; } diff --git a/scene/gui/control.h b/scene/gui/control.h index 71459d88b1..c7ce47b7ba 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -208,7 +208,10 @@ class Control : public CanvasItem { LayoutMode stored_layout_mode = LayoutMode::LAYOUT_MODE_POSITION; bool stored_use_custom_anchors = false; + /// Offsets from anchor points for each side: [SIDE_LEFT, SIDE_TOP, SIDE_RIGHT, SIDE_BOTTOM] + /// Distance in pixels from the anchor point to the control's edge. real_t offset[4] = { 0.0, 0.0, 0.0, 0.0 }; + /// Normalized 0-1 values: real_t anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN }; FocusMode focus_mode = FOCUS_NONE; FocusBehaviorRecursive focus_behavior_recursive = FOCUS_BEHAVIOR_INHERITED; @@ -230,6 +233,8 @@ class Control : public CanvasItem { bool block_minimum_size_adjust = false; bool size_warning = true; + int anchor_preset_margin = 0; + bool anchor_preset_active = false; /// @} /// Container Sizing /// @{ @@ -306,6 +311,132 @@ class Control : public CanvasItem { } data; + /// @brief All the parameters needed to calculate offsets when using anchor presets + struct OffsetCalcParams { + real_t anchor_position[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; ///< 0.0 - 1.0 + real_t parent_x_size = 0.0f; + real_t parent_y_size = 0.0f; + real_t pivot_x_correction = 0.0f; + real_t pivot_y_correction = 0.0f; + real_t scaled_width = 0.0f; + real_t scaled_height = 0.0f; + real_t size_x = 0.0f; + real_t size_y = 0.0f; + real_t scale_x = 0.0f; + real_t scale_y = 0.0f; + int preset_margin = 0; + real_t parent_x_position = 0.0f; + real_t parent_y_position = 0.0f; + }; + + /// @name Left offset calculation functions + /// @{ + static real_t _calculate_left_offset_left_justified(const OffsetCalcParams &p) { + return p.parent_x_size * (0.0 - p.anchor_position[0]) + p.pivot_x_correction + p.preset_margin + p.parent_x_position; + } + + static real_t _calculate_left_offset_right_justified(const OffsetCalcParams &p) { + return p.parent_x_size * (1.0 - p.anchor_position[0]) + p.pivot_x_correction - p.scaled_width - p.preset_margin + p.parent_x_position; + } + + static real_t _calculate_left_offset_center_justified(const OffsetCalcParams &p) { + return p.parent_x_size * (0.5 - p.anchor_position[0]) + p.pivot_x_correction - p.scaled_width / 2.0f + p.parent_x_position; + } + /// @} + /// @name Top offset calculation functions + /// @{ + static real_t _calculate_top_offset_top_justified(const OffsetCalcParams &p) { + return p.parent_y_size * (0.0 - p.anchor_position[1]) + p.pivot_y_correction + p.preset_margin + p.parent_y_position; + } + + static real_t _calculate_top_offset_bottom_justified(const OffsetCalcParams &p) { + return p.parent_y_size * (1.0 - p.anchor_position[1]) + p.pivot_y_correction - p.scaled_height - p.preset_margin + p.parent_y_position; + } + + static real_t _calculate_top_offset_center_justified(const OffsetCalcParams &p) { + return p.parent_y_size * (0.5 - p.anchor_position[1]) + p.pivot_y_correction - p.scaled_height / 2.0f + p.parent_y_position; + } + /// @} + + /// @name Right offset calculation functions + /// @{ + static real_t _calculate_right_offset_left_justified(const OffsetCalcParams &p) { + return p.parent_x_size * (0.0 - p.anchor_position[2]) + p.pivot_x_correction + p.size_x + p.preset_margin + p.parent_x_position; + } + + static real_t _calculate_right_offset_center_justified(const OffsetCalcParams &p) { + return p.parent_x_size * (0.5 - p.anchor_position[2]) + p.pivot_x_correction + p.size_x - p.scaled_width / 2.0f + p.parent_x_position; + } + + static real_t _calculate_right_offset_right_justified(const OffsetCalcParams &p) { + return p.parent_x_size * (1.0 - p.anchor_position[2]) + p.pivot_x_correction + p.size_x * (1.0f - p.scale_x) - p.preset_margin + p.parent_x_position; + } + + static real_t _calculate_right_offset_wide(const OffsetCalcParams &p) { + return p.pivot_x_correction + p.preset_margin + (p.parent_x_size - 2.0f * p.preset_margin) / p.scale_x - p.parent_x_size + p.parent_x_position; + } + /// @} + + /// @name Bottom offset calculation functions + /// @{ + static real_t _calculate_bottom_offset_top_justified(const OffsetCalcParams &p) { + return p.parent_y_size * (0.0 - p.anchor_position[3]) + p.pivot_y_correction + p.size_y + p.preset_margin + p.parent_y_position; + } + + static real_t _calculate_bottom_offset_center_justified(const OffsetCalcParams &p) { + return p.parent_y_size * (0.5 - p.anchor_position[3]) + p.pivot_y_correction + p.size_y - p.scaled_height / 2.0f + p.parent_y_position; + } + + static real_t _calculate_bottom_offset_bottom_justified(const OffsetCalcParams &p) { + return p.parent_y_size * (1.0 - p.anchor_position[3]) + p.pivot_y_correction + p.size_y * (1.0f - p.scale_y) - p.preset_margin + p.parent_y_position; + } + + static real_t _calculate_bottom_offset_wide(const OffsetCalcParams &p) { + return p.pivot_y_correction + p.preset_margin + (p.parent_y_size - 2.0f * p.preset_margin) / p.scale_y - p.parent_y_size + p.parent_y_position; + } +/// @} + +/// @name Anchor Preset - Actual Offset Calculation +/// { +// Preset list: name, left, top, right, bottom +#define LAYOUT_PRESET_LIST \ + X(LayoutPreset::PRESET_TOP_LEFT, _calculate_left_offset_left_justified, _calculate_top_offset_top_justified, _calculate_right_offset_left_justified, _calculate_bottom_offset_top_justified) \ + X(LayoutPreset::PRESET_TOP_RIGHT, _calculate_left_offset_right_justified, _calculate_top_offset_top_justified, _calculate_right_offset_right_justified, _calculate_bottom_offset_top_justified) \ + X(LayoutPreset::PRESET_BOTTOM_LEFT, _calculate_left_offset_left_justified, _calculate_top_offset_bottom_justified, _calculate_right_offset_left_justified, _calculate_bottom_offset_bottom_justified) \ + X(LayoutPreset::PRESET_BOTTOM_RIGHT, _calculate_left_offset_right_justified, _calculate_top_offset_bottom_justified, _calculate_right_offset_right_justified, _calculate_bottom_offset_bottom_justified) \ + X(LayoutPreset::PRESET_CENTER_LEFT, _calculate_left_offset_left_justified, _calculate_top_offset_center_justified, _calculate_right_offset_left_justified, _calculate_bottom_offset_center_justified) \ + X(LayoutPreset::PRESET_CENTER_TOP, _calculate_left_offset_center_justified, _calculate_top_offset_top_justified, _calculate_right_offset_center_justified, _calculate_bottom_offset_top_justified) \ + X(LayoutPreset::PRESET_CENTER_RIGHT, _calculate_left_offset_right_justified, _calculate_top_offset_center_justified, _calculate_right_offset_right_justified, _calculate_bottom_offset_center_justified) \ + X(LayoutPreset::PRESET_CENTER_BOTTOM, _calculate_left_offset_center_justified, _calculate_top_offset_bottom_justified, _calculate_right_offset_center_justified, _calculate_bottom_offset_bottom_justified) \ + X(LayoutPreset::PRESET_CENTER, _calculate_left_offset_center_justified, _calculate_top_offset_center_justified, _calculate_right_offset_center_justified, _calculate_bottom_offset_center_justified) \ + X(LayoutPreset::PRESET_LEFT_WIDE, _calculate_left_offset_left_justified, _calculate_top_offset_top_justified, _calculate_right_offset_left_justified, _calculate_bottom_offset_wide) \ + X(LayoutPreset::PRESET_TOP_WIDE, _calculate_left_offset_left_justified, _calculate_top_offset_top_justified, _calculate_right_offset_wide, _calculate_bottom_offset_top_justified) \ + X(LayoutPreset::PRESET_RIGHT_WIDE, _calculate_left_offset_right_justified, _calculate_top_offset_top_justified, _calculate_right_offset_right_justified, _calculate_bottom_offset_wide) \ + X(LayoutPreset::PRESET_BOTTOM_WIDE, _calculate_left_offset_left_justified, _calculate_top_offset_bottom_justified, _calculate_right_offset_wide, _calculate_bottom_offset_bottom_justified) \ + X(LayoutPreset::PRESET_VCENTER_WIDE, _calculate_left_offset_center_justified, _calculate_top_offset_top_justified, _calculate_right_offset_center_justified, _calculate_bottom_offset_wide) \ + X(LayoutPreset::PRESET_HCENTER_WIDE, _calculate_left_offset_left_justified, _calculate_top_offset_center_justified, _calculate_right_offset_wide, _calculate_bottom_offset_center_justified) \ + X(LayoutPreset::PRESET_FULL_RECT, _calculate_left_offset_left_justified, _calculate_top_offset_top_justified, _calculate_right_offset_wide, _calculate_bottom_offset_wide) + + // switch seems to be the way to have a jump table without templates that I'm not smart enough to make :') + // (Custom anchor presets mean even the values passed into the function aren't known at compile time) + // This (and the above LAYOUT_PRESET_LIST) will at least keep the code from looking like a violent sneeze and keep the calculations readable + void apply_anchor_preset_offset(LayoutPreset preset, OffsetCalcParams ¶ms) { + switch (preset) { +#define X(preset_enum, left_fn, top_fn, right_fn, bottom_fn) \ + case preset_enum: \ + data.offset[0] = left_fn(params); \ + data.offset[1] = top_fn(params); \ + data.offset[2] = right_fn(params); \ + data.offset[3] = bottom_fn(params); \ + break; + LAYOUT_PRESET_LIST +#undef X + } + } +#undef LAYOUT_PRESET_LIST + + /// @} + /// @name Dynamic Properties /// @{ static constexpr unsigned properties_managed_by_container_count = 12; @@ -335,6 +466,8 @@ class Control : public CanvasItem { void _set_anchors_layout_preset(int p_preset); int _get_anchors_layout_preset() const; + void _compute_preset_offsets(LayoutPreset p_preset, const Size2 &p_size, int p_margin = 0); + void _update_minimum_size_cache() const; void _update_minimum_size(); void _size_changed(); @@ -512,6 +645,10 @@ class Control : public CanvasItem { GrowDirection get_v_grow_direction() const; void set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets = true); + /// Sets the control's offsets to align with a layout preset. + /// @param p_preset: The layout preset from LayoutPreset enum. + /// @param p_resize_mode: How to determine the resulting size (PRESET_MODE_MINSIZE, PRESET_MODE_KEEP_SIZE, etc.). + /// @param p_margin: Gap between the control and parent edges. void set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); void set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); void set_grow_direction_preset(LayoutPreset p_preset); @@ -721,6 +858,7 @@ class Control : public CanvasItem { /// @{ void set_layout_direction(LayoutDirection p_direction); LayoutDirection get_layout_direction() const; + /// Whether a control should render right-to-left virtual bool is_layout_rtl() const; void set_localize_numeral_system(bool p_enable);