From b32395ade9455756b20d55d6c69641b35ebb2812 Mon Sep 17 00:00:00 2001 From: mosquito chang Date: Sun, 18 Aug 2024 14:46:34 +0200 Subject: [PATCH 01/86] Fix `InputMap::event_get_index` to handle unmatched events correctly --- core/input/input_map.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index ddeee9d765a..5cd02cf39d8 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -253,8 +253,8 @@ bool InputMap::event_is_action(const Ref &p_event, const StringName int InputMap::event_get_index(const Ref &p_event, const StringName &p_action, bool p_exact_match) const { int index = -1; - event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index); - return index; + bool valid = event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index); + return valid ? index : -1; } bool InputMap::event_get_action_status(const Ref &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const { From 6a12fac44cf3bbc020689b4309841ee96ecd56c3 Mon Sep 17 00:00:00 2001 From: Mark Wilson <23439518+wlsnmrk@users.noreply.github.com> Date: Sun, 10 Sep 2023 22:13:04 -0400 Subject: [PATCH 02/86] Fix button up and down events with focus changes Adds a flag to guard button_up and button_down events based on whether button_down has been previously emitted. Buttons now emit button_up signals if they have emitted button_down and subsequently lose focus, do not emit button_up if they gain focus while ui_accept is still pressed, and do not emit multiple up/down signals if multiple ui_accept keys are pressed simultaneously. --- scene/gui/base_button.cpp | 11 +++++++++-- scene/gui/base_button.h | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 34f50954932..85aa90cfee3 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -115,6 +115,11 @@ void BaseButton::_notification(int p_what) { } else if (status.hovering) { queue_redraw(); } + + if (status.pressed_down_with_focus) { + status.pressed_down_with_focus = false; + emit_signal(SNAME("button_up")); + } } break; case NOTIFICATION_VISIBILITY_CHANGED: @@ -147,9 +152,10 @@ void BaseButton::_toggled(bool p_pressed) { void BaseButton::on_action_event(Ref p_event) { Ref mouse_button = p_event; - if (p_event->is_pressed() && (mouse_button.is_null() || status.hovering)) { + if (!status.pressed_down_with_focus && p_event->is_pressed() && (mouse_button.is_null() || status.hovering)) { status.press_attempt = true; status.pressing_inside = true; + status.pressed_down_with_focus = true; emit_signal(SNAME("button_down")); } @@ -176,9 +182,10 @@ void BaseButton::on_action_event(Ref p_event) { } } - if (!p_event->is_pressed()) { + if (status.pressed_down_with_focus && !p_event->is_pressed()) { status.press_attempt = false; status.pressing_inside = false; + status.pressed_down_with_focus = false; emit_signal(SNAME("button_up")); } diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index 8405acb21db..e15539208b4 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -61,7 +61,7 @@ class BaseButton : public Control { bool hovering = false; bool press_attempt = false; bool pressing_inside = false; - + bool pressed_down_with_focus = false; bool disabled = false; } status; From db70cf2585c8dc3f33e95bbe6aa5a6bf709af4d3 Mon Sep 17 00:00:00 2001 From: kobewi Date: Thu, 17 Oct 2024 14:30:32 +0200 Subject: [PATCH 03/86] Mention native file dialogs editor setting in EditorFileDialog description --- doc/classes/EditorFileDialog.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/classes/EditorFileDialog.xml b/doc/classes/EditorFileDialog.xml index d5c2ed55d75..a3a320c2240 100644 --- a/doc/classes/EditorFileDialog.xml +++ b/doc/classes/EditorFileDialog.xml @@ -5,6 +5,7 @@ [EditorFileDialog] is an enhanced version of [FileDialog] available only to editor plugins. Additional features include list of favorited/recent files and the ability to see files as thumbnails grid instead of list. + Unlike [FileDialog], [EditorFileDialog] does not have a property for using native dialogs. Instead, native dialogs can be enabled globally via the [member EditorSettings.interface/editor/use_native_file_dialogs] editor setting. They are also enabled automatically when running in sandbox (e.g. on macOS). From 3cc43abab07971349c8f6c7b40179485cf430442 Mon Sep 17 00:00:00 2001 From: Florent Guiocheau Date: Mon, 2 Dec 2024 12:11:20 +0100 Subject: [PATCH 04/86] Document inverse aspect ratio convention of `Projection::get_fovy()` --- doc/classes/Projection.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/classes/Projection.xml b/doc/classes/Projection.xml index 091e0bf54f4..d32fdc9bb8f 100644 --- a/doc/classes/Projection.xml +++ b/doc/classes/Projection.xml @@ -194,6 +194,7 @@ Returns the vertical field of view of the projection (in degrees) associated with the given horizontal field of view (in degrees) and aspect ratio. + [b]Note:[/b] Unlike most methods of [Projection], [param aspect] is expected to be 1 divided by the X:Y aspect ratio. From f7f6432af6a8f323363bc4a72f83b1038731500b Mon Sep 17 00:00:00 2001 From: Michael Alexsander Date: Mon, 29 Apr 2024 01:09:58 -0300 Subject: [PATCH 05/86] Make `PopupMenu/Panel` shadows properly visible again --- doc/classes/PopupMenu.xml | 2 + doc/classes/PopupPanel.xml | 4 + scene/gui/popup.cpp | 150 ++++++++++++++++++++++--- scene/gui/popup.h | 9 +- scene/gui/popup_menu.cpp | 221 +++++++++++++++++++++++++++++-------- scene/gui/popup_menu.h | 8 ++ scene/main/viewport.cpp | 11 +- scene/main/window.h | 2 +- 8 files changed, 336 insertions(+), 71 deletions(-) diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 9fd47cf7f5d..8c14081366c 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -649,6 +649,8 @@ If set to one of the values of [enum NativeMenu.SystemMenus], this [PopupMenu] is bound to the special system menu. Only one [PopupMenu] can be bound to each special menu at a time. + + diff --git a/doc/classes/PopupPanel.xml b/doc/classes/PopupPanel.xml index b581f32686c..6967ed882ff 100644 --- a/doc/classes/PopupPanel.xml +++ b/doc/classes/PopupPanel.xml @@ -8,6 +8,10 @@ + + + + [StyleBox] for the background panel. diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 10fadeeba1e..4697fb6be98 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -30,9 +30,8 @@ #include "popup.h" -#include "core/config/engine.h" -#include "core/os/keyboard.h" #include "scene/gui/panel.h" +#include "scene/resources/style_box_flat.h" #include "scene/theme/theme_db.h" void Popup::_input_from_window(const Ref &p_event) { @@ -222,6 +221,24 @@ Popup::Popup() { Popup::~Popup() { } +void PopupPanel::_input_from_window(const Ref &p_event) { + if (p_event.is_valid()) { + if (!get_flag(FLAG_POPUP)) { + return; + } + + Ref b = p_event; + // Hide it if the shadows have been clicked. + if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && !panel->get_global_rect().has_point(b->get_position())) { + _close_pressed(); + } + } else { + WARN_PRINT_ONCE("PopupPanel has received an invalid InputEvent. Consider filtering out invalid events."); + } + + Popup::_input_from_window(p_event); +} + Size2 PopupPanel::_get_contents_minimum_size() const { Size2 ms; @@ -239,17 +256,77 @@ Size2 PopupPanel::_get_contents_minimum_size() const { ms = cms.max(ms); } + // Take shadows into account. + ms.width += panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT); + ms.height += panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM); + return ms + theme_cache.panel_style->get_minimum_size(); } -void PopupPanel::_update_child_rects() { +Rect2i PopupPanel::_popup_adjust_rect() const { + Rect2i current = Popup::_popup_adjust_rect(); + if (current == Rect2i()) { + return current; + } + + pre_popup_rect = current; + + _update_shadow_offsets(); + _update_child_rects(); + + if (is_layout_rtl()) { + current.position -= Vector2(ABS(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } else { + current.position -= Vector2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } + current.size += Vector2(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)) * get_content_scale_factor(); + + return current; +} + +void PopupPanel::_update_shadow_offsets() const { + if (!DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + const Ref sb = theme_cache.panel_style; + if (sb.is_null()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + const int shadow_size = sb->get_shadow_size(); + if (shadow_size == 0) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + // Offset the background panel so it leaves space inside the window for the shadows to be drawn. + const Point2 shadow_offset = sb->get_shadow_offset(); + if (is_layout_rtl()) { + panel->set_offset(SIDE_LEFT, shadow_size + shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size + shadow_offset.x); + } else { + panel->set_offset(SIDE_LEFT, shadow_size - shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size - shadow_offset.x); + } + panel->set_offset(SIDE_TOP, shadow_size - shadow_offset.y); + panel->set_offset(SIDE_BOTTOM, -shadow_size - shadow_offset.y); +} + +void PopupPanel::_update_child_rects() const { Vector2 cpos(theme_cache.panel_style->get_offset()); - Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor(); - Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size(); + cpos += Vector2(is_layout_rtl() ? -panel->get_offset(SIDE_RIGHT) : panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)); + + Vector2 csize = Vector2(get_size()) / get_content_scale_factor() - theme_cache.panel_style->get_minimum_size(); + // Trim shadows. + csize.width -= panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT); + csize.height -= panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to(get_child(i)); - if (!c) { + if (!c || c == panel) { continue; } @@ -257,26 +334,68 @@ void PopupPanel::_update_child_rects() { continue; } - if (c == panel) { - c->set_position(Vector2()); - c->set_size(panel_size); - } else { - c->set_position(cpos); - c->set_size(csize); - } + c->set_position(cpos); + c->set_size(csize); } } void PopupPanel::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: + case NOTIFICATION_ENTER_TREE: { + if (!Engine::get_singleton()->is_editor_hint() && !DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + Ref sb = theme_cache.panel_style; + if (sb.is_valid() && (sb->get_shadow_size() > 0 || sb->get_corner_radius(CORNER_TOP_LEFT) > 0 || sb->get_corner_radius(CORNER_TOP_RIGHT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_LEFT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_RIGHT) > 0)) { + WARN_PRINT_ONCE("The current theme styles PopupPanel to have shadows and/or rounded corners, but those won't display correctly if 'display/window/per_pixel_transparency/allowed' isn't enabled in the Project Settings, nor if it isn't supported."); + } + } + } break; + case NOTIFICATION_THEME_CHANGED: { panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style); + + if (is_visible()) { + _update_shadow_offsets(); + } + _update_child_rects(); } break; + case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + if (is_visible()) { + _update_shadow_offsets(); + } + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + // Remove the extra space used by the shadows, so they can be ignored when the popup is hidden. + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + _update_child_rects(); + + if (pre_popup_rect != Rect2i()) { + set_position(pre_popup_rect.position); + set_size(pre_popup_rect.size); + + pre_popup_rect = Rect2i(); + } + } else if (pre_popup_rect == Rect2i()) { + // The popup was made visible directly (without `popup_*()`), so just update the offsets without touching the rect. + _update_shadow_offsets(); + _update_child_rects(); + } + } break; + case NOTIFICATION_WM_SIZE_CHANGED: { _update_child_rects(); + + if (is_visible()) { + const Vector2i offsets = Vector2i(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)); + // Check if the size actually changed. + if (pre_popup_rect.size + offsets != get_size()) { + // Play safe, and stick with the new size. + pre_popup_rect = Rect2i(); + } + } } break; } } @@ -286,6 +405,9 @@ void PopupPanel::_bind_methods() { } PopupPanel::PopupPanel() { + set_flag(FLAG_TRANSPARENT, true); + panel = memnew(Panel); + panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); add_child(panel, false, INTERNAL_MODE_FRONT); } diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 69a81ad98c7..cfebdcb0722 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -85,8 +85,15 @@ class PopupPanel : public Popup { Ref panel_style; } theme_cache; + mutable Rect2i pre_popup_rect; + protected: - void _update_child_rects(); + virtual void _input_from_window(const Ref &p_event) override; + + virtual Rect2i _popup_adjust_rect() const override; + + void _update_shadow_offsets() const; + void _update_child_rects() const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 13cfa3d2304..1f3991998a1 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -35,9 +35,9 @@ #include "core/input/input.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/string/translation.h" #include "scene/gui/menu_bar.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/style_box_flat.h" #include "scene/theme/theme_db.h" HashMap PopupMenu::system_menus; @@ -223,6 +223,9 @@ Size2 PopupMenu::_get_item_icon_size(int p_idx) const { Size2 PopupMenu::_get_contents_minimum_size() const { Size2 minsize = theme_cache.panel_style->get_minimum_size(); minsize.width += scroll_container->get_v_scroll_bar()->get_size().width; + // Take shadows into account. + minsize.width += panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT); + minsize.height += panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM); float max_w = 0.0; float icon_w = 0.0; @@ -313,17 +316,31 @@ int PopupMenu::_get_items_total_height() const { } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { + // Make the item area exclude shadows and the vertical margins and scrollbar. + Rect2 item_clickable_area = panel->get_global_rect(); + if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) { + const int scroll_width = scroll_container->get_v_scroll_bar()->get_size().width; + if (is_layout_rtl()) { + item_clickable_area.position.x += scroll_width; + item_clickable_area.size.width -= scroll_width; + } + item_clickable_area.size.width -= scroll_width; + } float win_scale = get_content_scale_factor(); - if (p_over.x < 0 || p_over.x >= get_size().width * win_scale || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP) * win_scale) { + item_clickable_area.position.y = (item_clickable_area.position.y + theme_cache.panel_style->get_margin(SIDE_TOP)) * win_scale; + item_clickable_area.size.y -= theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); + item_clickable_area.size *= win_scale; + + if (p_over.x < item_clickable_area.position.x || p_over.x >= item_clickable_area.position.x + item_clickable_area.size.width || + p_over.y < item_clickable_area.position.y || p_over.y >= item_clickable_area.position.y + item_clickable_area.size.height) { return -1; } - Point2 ofs = Point2(0, theme_cache.v_separation * 0.5) * win_scale; - + float ofs = item_clickable_area.position.y + theme_cache.v_separation * 0.5; for (int i = 0; i < items.size(); i++) { - ofs.y += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; - ofs.y += _get_item_height(i) * win_scale; - if (p_over.y - control->get_position().y * win_scale < ofs.y) { + ofs += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; + ofs += _get_item_height(i) * win_scale; + if (p_over.y - control->get_position().y * win_scale < ofs) { return i; } } @@ -332,35 +349,43 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { } void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { - Popup *submenu_popup = items[p_over].submenu; + PopupMenu *submenu_popup = items[p_over].submenu; if (submenu_popup->is_visible()) { return; // Already visible. } - Point2 this_pos = get_position(); + const float win_scale = get_content_scale_factor(); + + const Point2 panel_ofs_start = Point2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * win_scale; + const Point2 panel_ofs_end = Point2(panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_BOTTOM)).abs() * win_scale; + + const Point2 this_pos = get_position() + Point2(0, panel_ofs_start.y + theme_cache.panel_style->get_margin(SIDE_TOP) * win_scale); Rect2 this_rect(this_pos, get_size()); - float scroll_offset = control->get_position().y; - float scaled_ofs_cache = items[p_over]._ofs_cache * get_content_scale_factor(); - float scaled_height_cache = items[p_over]._height_cache * get_content_scale_factor(); + const float scroll_offset = control->get_position().y; + const float scaled_ofs_cache = items[p_over]._ofs_cache * win_scale; + const float scaled_height_cache = items[p_over]._height_cache * win_scale; submenu_popup->reset_size(); // Shrink the popup size to its contents. - Size2 submenu_size = submenu_popup->get_size(); + const Size2 submenu_size = submenu_popup->get_size(); - Point2 submenu_pos; - if (control->is_layout_rtl()) { - submenu_pos = this_pos + Point2(-submenu_size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); - } else { - submenu_pos = this_pos + Point2(this_rect.size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); - } + // Calculate the submenu's position. + Point2 submenu_pos(0, -submenu_popup->get_theme_stylebox(SceneStringName(panel))->get_margin(SIDE_TOP) * submenu_popup->get_content_scale_factor()); + Rect2i screen_rect = is_embedded() ? Rect2i(get_embedder()->get_visible_rect()) : get_parent_rect(); + if (is_layout_rtl()) { + submenu_pos += this_pos + Point2(-submenu_size.width + panel_ofs_end.x, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); + if (submenu_pos.x < screen_rect.position.x) { + submenu_pos.x = this_pos.x + this_rect.size.width - panel_ofs_start.x; + } - // Fix pos if going outside parent rect. - if (submenu_pos.x < get_parent_rect().position.x) { - submenu_pos.x = this_pos.x + submenu_size.width; - } + this_rect.position.x += panel_ofs_end.x; + } else { + submenu_pos += this_pos + Point2(this_rect.size.width - panel_ofs_end.x, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); + if (submenu_pos.x + submenu_size.width > screen_rect.position.x + screen_rect.size.width) { + submenu_pos.x = this_pos.x - submenu_size.width + panel_ofs_start.x; + } - if (submenu_pos.x + submenu_size.width > get_parent_rect().position.x + get_parent_rect().size.width) { - submenu_pos.x = this_pos.x - submenu_size.width; + this_rect.position.x += panel_ofs_start.x; } submenu_popup->set_position(submenu_pos); @@ -387,9 +412,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. - Rect2 safe_area = this_rect; - safe_area.position.y += scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = scaled_height_cache + theme_cache.v_separation; + const Rect2 safe_area(get_position(), get_size()); Viewport *vp = submenu_popup->get_embedder(); if (vp) { vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area); @@ -397,16 +420,18 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area); } - // Make the position of the parent popup relative to submenu popup. - this_rect.position = this_rect.position - submenu_pum->get_position(); + this_rect.position -= submenu_pum->get_position(); // Make the position of the parent popup relative to submenu popup. + this_rect.size.width -= panel_ofs_start.x + panel_ofs_end.x; + this_rect.size.height -= panel_ofs_end.y + (theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM)) * win_scale; // Autohide area above the submenu item. submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y - theme_cache.panel_style->get_margin(SIDE_TOP) * win_scale, + this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_margin(SIDE_TOP) * win_scale - theme_cache.v_separation / 2)); // If there is an area below the submenu item, add an autohide area there. if (scaled_ofs_cache + scaled_height_cache + scroll_offset <= control->get_size().height) { - int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; + const int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2; submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -446,7 +471,7 @@ void PopupMenu::_input_from_window(const Ref &p_event) { if (p_event.is_valid()) { _input_from_window_internal(p_event); } else { - WARN_PRINT_ONCE("PopupMenu has received an invalid InputEvent. Consider filtering invalid events out."); + WARN_PRINT_ONCE("PopupMenu has received an invalid InputEvent. Consider filtering out invalid events."); } Popup::_input_from_window(p_event); } @@ -570,15 +595,19 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { } } - // Make an area which does not include v scrollbar, so that items are not activated when dragging scrollbar. - Rect2 item_clickable_area = scroll_container->get_rect(); + // Make the item area exclude shadows and the vertical margins and scrollbar. + Rect2 item_clickable_area = panel->get_global_rect(); if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) { + int scroll_width = scroll_container->get_v_scroll_bar()->get_size().width; if (is_layout_rtl()) { - item_clickable_area.position.x += scroll_container->get_v_scroll_bar()->get_size().width; + item_clickable_area.position.x += scroll_width; + item_clickable_area.size.width -= scroll_width; } - item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width; + item_clickable_area.size.width -= scroll_width; } - item_clickable_area.size = item_clickable_area.size * get_content_scale_factor(); + item_clickable_area.position.y = (item_clickable_area.position.y + theme_cache.panel_style->get_margin(SIDE_TOP)) * get_content_scale_factor(); + item_clickable_area.size.y -= theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); + item_clickable_area.size *= get_content_scale_factor(); Ref b = p_event; @@ -592,9 +621,16 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { during_grabbed_click = false; is_scrolling = is_layout_rtl() ? b->get_position().x < item_clickable_area.position.x : b->get_position().x > item_clickable_area.size.width; + // Hide it if the shadows have been clicked. + if (get_flag(FLAG_POPUP) && !panel->get_global_rect().has_point(b->get_position())) { + _close_pressed(); + return; + } + if (!item_clickable_area.has_point(b->get_position())) { return; } + _mouse_over_update(b->get_position()); } else { if (is_scrolling) { @@ -608,6 +644,7 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { if (!item_clickable_area.has_point(b->get_position())) { return; } + // Disable clicks under a time threshold to avoid selection right when opening the popup. if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 400) { return; @@ -643,7 +680,7 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { activated_by_keyboard = false; for (const Rect2 &E : autohide_areas) { - if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) { + if (!scroll_container->get_global_rect().has_point(m->get_position()) && E.has_point(m->get_position())) { // The mouse left the safe area, prepare to close. _close_pressed(); return; @@ -655,7 +692,7 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { minimum_lifetime_timer->stop(); } - if (!item_clickable_area.has_point(m->get_position())) { + if (mouse_over == -1 && !item_clickable_area.has_point(m->get_position())) { return; } _mouse_over_update(m->get_position()); @@ -971,6 +1008,57 @@ void PopupMenu::_menu_changed() { emit_signal(SNAME("menu_changed")); } +void PopupMenu::_update_shadow_offsets() const { + if (!DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + Ref sb = theme_cache.panel_style; + if (sb.is_null()) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + const int shadow_size = sb->get_shadow_size(); + if (shadow_size == 0) { + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + return; + } + + // Offset the background panel so it leaves space inside the window for the shadows to be drawn. + const Point2 shadow_offset = sb->get_shadow_offset(); + if (is_layout_rtl()) { + panel->set_offset(SIDE_LEFT, shadow_size + shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size + shadow_offset.x); + } else { + panel->set_offset(SIDE_LEFT, shadow_size - shadow_offset.x); + panel->set_offset(SIDE_RIGHT, -shadow_size - shadow_offset.x); + } + panel->set_offset(SIDE_TOP, shadow_size - shadow_offset.y); + panel->set_offset(SIDE_BOTTOM, -shadow_size - shadow_offset.y); +} + +Rect2i PopupMenu::_popup_adjust_rect() const { + Rect2i current = Popup::_popup_adjust_rect(); + if (current == Rect2i()) { + return current; + } + + pre_popup_rect = current; + + _update_shadow_offsets(); + + if (is_layout_rtl()) { + current.position -= Vector2(ABS(panel->get_offset(SIDE_RIGHT)), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } else { + current.position -= Vector2(panel->get_offset(SIDE_LEFT), panel->get_offset(SIDE_TOP)) * get_content_scale_factor(); + } + current.size += Vector2(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)) * get_content_scale_factor(); + + return current; +} + void PopupMenu::add_child_notify(Node *p_child) { Window::add_child_notify(p_child); @@ -1018,6 +1106,13 @@ void PopupMenu::_notification(int p_what) { if (system_menu_id != NativeMenu::INVALID_MENU_ID) { bind_global_menu(); } + + if (!Engine::get_singleton()->is_editor_hint() && !DisplayServer::get_singleton()->is_window_transparency_available() && !is_embedded()) { + Ref sb = theme_cache.panel_style; + if (sb.is_valid() && (sb->get_shadow_size() > 0 || sb->get_corner_radius(CORNER_TOP_LEFT) > 0 || sb->get_corner_radius(CORNER_TOP_RIGHT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_LEFT) > 0 || sb->get_corner_radius(CORNER_BOTTOM_RIGHT) > 0)) { + WARN_PRINT_ONCE("The current theme styles PopupMenu to have shadows and/or rounded corners, but those won't display correctly if 'display/window/per_pixel_transparency/allowed' isn't enabled in the Project Settings, nor if it isn't supported."); + } + } } break; case NOTIFICATION_EXIT_TREE: { @@ -1026,12 +1121,16 @@ void PopupMenu::_notification(int p_what) { } } break; + case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - scroll_container->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style); + panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style); + + if (is_visible()) { + _update_shadow_offsets(); + } [[fallthrough]]; } - case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { NativeMenu *nmenu = NativeMenu::get_singleton(); bool is_global = global_menu.is_valid(); @@ -1064,6 +1163,17 @@ void PopupMenu::_notification(int p_what) { } } break; + case NOTIFICATION_WM_SIZE_CHANGED: { + if (is_visible()) { + const Vector2i offsets = Vector2i(panel->get_offset(SIDE_LEFT) - panel->get_offset(SIDE_RIGHT), panel->get_offset(SIDE_TOP) - panel->get_offset(SIDE_BOTTOM)); + // Check if the size actually changed. + if (pre_popup_rect.size + offsets != get_size()) { + // Play safe, and stick with the new size. + pre_popup_rect = Rect2i(); + } + } + } break; + case NOTIFICATION_POST_POPUP: { popup_time_msec = OS::get_singleton()->get_ticks_msec(); initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); @@ -1176,10 +1286,25 @@ void PopupMenu::_notification(int p_what) { } set_process_internal(false); + + // Remove the extra space used by the shadows, so they can be ignored when the popup is hidden. + panel->set_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 0); + + if (pre_popup_rect != Rect2i()) { + set_position(pre_popup_rect.position); + set_size(pre_popup_rect.size); + + pre_popup_rect = Rect2i(); + } } else { if (!is_embedded()) { set_process_internal(true); } + + // The popup was made visible directly (without `popup_*()`), so just update the offsets without touching the rect. + if (pre_popup_rect == Rect2i()) { + _update_shadow_offsets(); + } } } break; } @@ -2868,11 +2993,17 @@ void PopupMenu::set_visible(bool p_visible) { } PopupMenu::PopupMenu() { + set_flag(FLAG_TRANSPARENT, true); + + // The panel used to draw the panel style. + panel = memnew(PanelContainer); + panel->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + add_child(panel, false, INTERNAL_MODE_FRONT); + // Scroll Container scroll_container = memnew(ScrollContainer); scroll_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - scroll_container->set_clip_contents(true); - add_child(scroll_container, false, INTERNAL_MODE_FRONT); + panel->add_child(scroll_container, false, INTERNAL_MODE_FRONT); // The control which will display the items control = memnew(Control); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 442ffa2ebc9..2a5537e7f97 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -37,6 +37,8 @@ #include "scene/property_list_helper.h" #include "scene/resources/text_line.h" +class PanelContainer; + class PopupMenu : public Popup { GDCLASS(PopupMenu, Popup); @@ -94,6 +96,9 @@ class PopupMenu : public Popup { Item(bool p_dummy) {} }; + mutable Rect2i pre_popup_rect; + void _update_shadow_offsets() const; + static inline PropertyListHelper base_property_helper; PropertyListHelper property_helper; @@ -149,6 +154,7 @@ class PopupMenu : public Popup { uint64_t search_time_msec = 0; String search_string = ""; + PanelContainer *panel = nullptr; ScrollContainer *scroll_container = nullptr; Control *control = nullptr; @@ -211,6 +217,8 @@ class PopupMenu : public Popup { int _get_item_checkable_type(int p_index) const; protected: + virtual Rect2i _popup_adjust_rect() const override; + virtual void add_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; virtual void _input_from_window(const Ref &p_event) override; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 76a449c77d5..da955e3cc7d 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1475,9 +1475,6 @@ void Viewport::_gui_show_tooltip() { PopupPanel *panel = memnew(PopupPanel); panel->set_theme_type_variation(SNAME("TooltipPanel")); - // Ensure no opaque background behind the panel as its StyleBox can be partially transparent (e.g. corners). - panel->set_transparent_background(true); - // If no custom tooltip is given, use a default implementation. if (!base_tooltip) { gui.tooltip_label = memnew(Label); @@ -1490,12 +1487,9 @@ void Viewport::_gui_show_tooltip() { base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - panel->set_transient(true); panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_flag(Window::FLAG_POPUP, false); panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true); - // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport. - panel->set_flag(Window::FLAG_TRANSPARENT, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); panel->gui_parent = this; @@ -1547,12 +1541,9 @@ void Viewport::_gui_show_tooltip() { r.position.y = vr.position.y; } - gui.tooltip_popup->set_position(r.position); - gui.tooltip_popup->set_size(r.size); - DisplayServer::WindowID active_popup = DisplayServer::get_singleton()->window_get_active_popup(); if (active_popup == DisplayServer::INVALID_WINDOW_ID || active_popup == window->get_window_id()) { - gui.tooltip_popup->show(); + gui.tooltip_popup->popup(r); } gui.tooltip_popup->child_controls_changed(); } diff --git a/scene/main/window.h b/scene/main/window.h index 6c82efcc3f9..649e079c467 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -118,7 +118,7 @@ class Window : public Viewport { String title; String tr_title; mutable int current_screen = 0; - mutable Vector2i position; + mutable Point2i position; mutable Size2i size = Size2i(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE); mutable Size2i min_size; mutable Size2i max_size; From b8b1584f5a59e1096b97a26c093543ab023e27d7 Mon Sep 17 00:00:00 2001 From: kobewi Date: Wed, 11 Dec 2024 14:16:36 +0100 Subject: [PATCH 06/86] Don't emit text_changed signal when clearing empty LineEdit --- scene/gui/line_edit.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index b92cdf2b353..935f26c0470 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1853,8 +1853,12 @@ Array LineEdit::get_structured_text_bidi_override_options() const { } void LineEdit::clear() { + bool was_empty = text.is_empty(); clear_internal(); - _text_changed(); + _clear_redo(); + if (!was_empty) { + _emit_text_change(); + } // This should reset virtual keyboard state if needed. if (editing) { From b3e970dde8c07168ef3927b3cd5dd157b403b281 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 12 Dec 2024 12:06:09 -0500 Subject: [PATCH 07/86] Adds `get_selection_line_offset` to `RichTextLabel` This new method allow you to get the line offset of the current selection (returns -1 if nothing is selected.) This is useful if you want to pop up a control or menu above the currently selected text. Previously there was no accurate way to get this information. The logic is moved from the implementation of `scroll_to_selection` verbatim, and that method has been adjusted to avoid repetition. --- doc/classes/RichTextLabel.xml | 6 +++++ scene/gui/rich_text_label.cpp | 48 +++++++++++++++++++++-------------- scene/gui/rich_text_label.h | 1 + 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index d75d3043e02..b982808ff0e 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -190,6 +190,12 @@ Returns the current selection first character index if a selection is active, [code]-1[/code] otherwise. Does not include BBCodes. + + + + Returns the current selection vertical line offset if a selection is active, [code]-1.0[/code] otherwise. + + diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index be54ef7c94e..99a95910475 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -5449,25 +5449,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { } void RichTextLabel::scroll_to_selection() { - if (selection.active && selection.from_frame && selection.from_line >= 0 && selection.from_line < (int)selection.from_frame->lines.size()) { - // Selected frame paragraph offset. - float line_offset = selection.from_frame->lines[selection.from_line].offset.y; - - // Add wrapped line offset. - for (int i = 0; i < selection.from_frame->lines[selection.from_line].text_buf->get_line_count(); i++) { - Vector2i range = selection.from_frame->lines[selection.from_line].text_buf->get_line_range(i); - if (range.x <= selection.from_char && range.y >= selection.from_char) { - break; - } - line_offset += selection.from_frame->lines[selection.from_line].text_buf->get_line_ascent(i) + selection.from_frame->lines[selection.from_line].text_buf->get_line_descent(i) + theme_cache.line_separation; - } - - // Add nested frame (e.g. table cell) offset. - ItemFrame *it = selection.from_frame; - while (it->parent_frame != nullptr) { - line_offset += it->parent_frame->lines[it->line].offset.y; - it = it->parent_frame; - } + float line_offset = get_selection_line_offset(); + if (line_offset != -1.0) { vscroll->set_value(line_offset); } } @@ -5978,6 +5961,32 @@ int RichTextLabel::get_selection_to() const { return selection.to_frame->lines[selection.to_line].char_offset + selection.to_char - 1; } +float RichTextLabel::get_selection_line_offset() const { + if (selection.active && selection.from_frame && selection.from_line >= 0 && selection.from_line < (int)selection.from_frame->lines.size()) { + // Selected frame paragraph offset. + float line_offset = selection.from_frame->lines[selection.from_line].offset.y; + + // Add wrapped line offset. + for (int i = 0; i < selection.from_frame->lines[selection.from_line].text_buf->get_line_count(); i++) { + Vector2i range = selection.from_frame->lines[selection.from_line].text_buf->get_line_range(i); + if (range.x <= selection.from_char && range.y >= selection.from_char) { + break; + } + line_offset += selection.from_frame->lines[selection.from_line].text_buf->get_line_ascent(i) + selection.from_frame->lines[selection.from_line].text_buf->get_line_descent(i) + theme_cache.line_separation; + } + + // Add nested frame (e.g. table cell) offset. + ItemFrame *it = selection.from_frame; + while (it->parent_frame != nullptr) { + line_offset += it->parent_frame->lines[it->line].offset.y; + it = it->parent_frame; + } + return line_offset; + } + + return -1.0; +} + void RichTextLabel::set_text(const String &p_bbcode) { // Allow clearing the tag stack. if (!p_bbcode.is_empty() && text == p_bbcode) { @@ -6412,6 +6421,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_selection_from"), &RichTextLabel::get_selection_from); ClassDB::bind_method(D_METHOD("get_selection_to"), &RichTextLabel::get_selection_to); + ClassDB::bind_method(D_METHOD("get_selection_line_offset"), &RichTextLabel::get_selection_line_offset); ClassDB::bind_method(D_METHOD("select_all"), &RichTextLabel::select_all); ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index d9aac546ca0..12ae17ad4ad 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -787,6 +787,7 @@ class RichTextLabel : public Control { bool is_selection_enabled() const; int get_selection_from() const; int get_selection_to() const; + float get_selection_line_offset() const; String get_selected_text() const; void select_all(); void selection_copy(); From 66f9a7571ed26b771999ef98d2c8b542cbd2c6bb Mon Sep 17 00:00:00 2001 From: kobewi Date: Sun, 15 Dec 2024 00:31:50 +0100 Subject: [PATCH 08/86] Fix folders uncollapsed after restart with filter --- editor/filesystem_dock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 9bf21a54388..c53b5f692cd 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -3991,7 +3991,7 @@ void FileSystemDock::save_layout_to_config(Ref p_layout, const Strin PackedStringArray selected_files = get_selected_paths(); p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files); Vector uncollapsed_paths = get_uncollapsed_paths(); - p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", uncollapsed_paths); + p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", searched_tokens.is_empty() ? uncollapsed_paths : uncollapsed_paths_before_search); } void FileSystemDock::load_layout_from_config(Ref p_layout, const String &p_section) { From 7c182a15441e01b9fb0804890ec145b98225b9b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Mon, 16 Dec 2024 23:37:26 +0200 Subject: [PATCH 09/86] [TextServer] Fix space trimming around mandatory line breaks. --- servers/text_server.cpp | 12 +++++------ tests/servers/test_text_server.h | 37 ++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/servers/text_server.cpp b/servers/text_server.cpp index c758e25d36c..ab69ef71b49 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -890,10 +890,10 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped if (last_end <= l_gl[start_pos].start) { lines.push_back(l_gl[start_pos].start); lines.push_back(l_gl[end_pos].end); - last_end = l_gl[end_pos].end; - cur_safe_brk = end_pos; + last_end = l_gl[i].end; + cur_safe_brk = i; } - trim_next = false; + trim_next = true; } else { if (last_end <= line_start) { lines.push_back(line_start); @@ -1057,15 +1057,15 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { end_pos -= l_gl[end_pos].count; } - trim_next = false; + trim_next = true; if (last_end <= l_gl[start_pos].start) { lines.push_back(l_gl[start_pos].start); lines.push_back(l_gl[end_pos].end); if (p_width > indent) { l_width = p_width - indent; } - last_end = l_gl[end_pos].end; - cur_safe_brk = end_pos; + last_end = l_gl[i].end; + cur_safe_brk = i; } } else { if (last_end <= line_start) { diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h index 4e20d43efc2..da21399cb8a 100644 --- a/tests/servers/test_text_server.h +++ b/tests/servers/test_text_server.h @@ -461,7 +461,7 @@ TEST_SUITE("[TextServer]") { ts->free_rid(ctx); } - if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { + if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Line breaking opportunities. String test = U"เป็นภาษาราชการและภาษา"; RID ctx = ts->create_shaped_text(); CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed."); @@ -489,7 +489,7 @@ TEST_SUITE("[TextServer]") { ts->free_rid(ctx); } - if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { + if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Break line. struct TestCase { String text; PackedInt32Array breaks; @@ -504,15 +504,48 @@ TEST_SUITE("[TextServer]") { { U"الحمدا لحمدا لحمـــد", { 0, 13, 13, 20 } }, { U" الحمد test", { 0, 15, 15, 19 } }, { U"الحمـد الرياضي العربي", { 0, 7, 7, 15, 15, 21 } }, + { U"test \rtest", { 0, 6, 6, 10 } }, + { U"test\r test", { 0, 5, 5, 10 } }, + { U"test\r test \r test", { 0, 5, 5, 12, 12, 17 } }, }; for (size_t j = 0; j < sizeof(cases) / sizeof(TestCase); j++) { RID ctx = ts->create_shaped_text(); CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed."); bool ok = ts->shaped_text_add_string(ctx, cases[j].text, font, 16); CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed."); + PackedInt32Array breaks = ts->shaped_text_get_line_breaks(ctx, 90.0); + CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points."); + breaks = ts->shaped_text_get_line_breaks_adv(ctx, { 90.0 }, 0, false); CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points."); + + ts->free_rid(ctx); + } + } + + if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) { // Break line and trim spaces. + struct TestCase { + String text; + PackedInt32Array breaks; + }; + TestCase cases[] = { + { U"test \rtest", { 0, 4, 6, 10 } }, + { U"test\r test", { 0, 4, 6, 10 } }, + { U"test\r test \r test", { 0, 4, 6, 10, 13, 17 } }, + }; + for (size_t j = 0; j < sizeof(cases) / sizeof(TestCase); j++) { + RID ctx = ts->create_shaped_text(); + CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed."); + bool ok = ts->shaped_text_add_string(ctx, cases[j].text, font, 16); + CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed."); + + PackedInt32Array breaks = ts->shaped_text_get_line_breaks(ctx, 90.0, 0, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); + CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points."); + + breaks = ts->shaped_text_get_line_breaks_adv(ctx, { 90.0 }, 0, false, TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); + CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points."); + ts->free_rid(ctx); } } From 1e982a49c3bfccae7aeea0aafb6104fae4a31108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Mon, 16 Dec 2024 23:37:26 +0200 Subject: [PATCH 10/86] [Dictionary Property Editor] Use property editors instead of labels to display keys. --- doc/classes/EditorProperty.xml | 6 +++ editor/editor_inspector.cpp | 34 +++++++++++++- editor/editor_inspector.h | 8 ++++ editor/editor_properties_array_dict.cpp | 59 +++++++++++++++++++++++++ editor/editor_properties_array_dict.h | 10 ++++- 5 files changed, 114 insertions(+), 3 deletions(-) diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml index 2a9e4088a6b..cf8296841d4 100644 --- a/doc/classes/EditorProperty.xml +++ b/doc/classes/EditorProperty.xml @@ -109,6 +109,12 @@ Used by the inspector, set to [code]true[/code] when the property can be deleted by the user. + + Used by the inspector, set to [code]true[/code] when the property label is drawn. + + + Used by the inspector, set to [code]true[/code] when the property background is drawn. + Used by the inspector, set to [code]true[/code] when the property is drawn with the editor theme's warning color. This is used for editable children's properties. diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 3a899719edd..d939be84128 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -164,6 +164,9 @@ void EditorProperty::_notification(int p_what) { if (no_children) { text_size = size.width; rect = Rect2(size.width - 1, 0, 1, height); + } else if (!draw_label) { + text_size = 0; + rect = Rect2(1, 0, size.width - 1, height); } else { text_size = MAX(0, size.width - (child_room + 4 * EDSCALE)); if (is_layout_rtl()) { @@ -268,10 +271,10 @@ void EditorProperty::_notification(int p_what) { } Ref bg_stylebox = get_theme_stylebox(SNAME("child_bg")); - if (draw_top_bg && right_child_rect != Rect2()) { + if (draw_top_bg && right_child_rect != Rect2() && draw_background) { draw_style_box(bg_stylebox, right_child_rect); } - if (bottom_child_rect != Rect2()) { + if (bottom_child_rect != Rect2() && draw_background) { draw_style_box(bg_stylebox, bottom_child_rect); } @@ -605,6 +608,25 @@ bool EditorProperty::use_keying_next() const { return false; } +void EditorProperty::set_draw_label(bool p_draw_label) { + draw_label = p_draw_label; + queue_redraw(); + queue_sort(); +} + +bool EditorProperty::is_draw_label() const { + return draw_label; +} + +void EditorProperty::set_draw_background(bool p_draw_background) { + draw_background = p_draw_background; + queue_redraw(); +} + +bool EditorProperty::is_draw_background() const { + return draw_background; +} + void EditorProperty::set_checkable(bool p_checkable) { checkable = p_checkable; queue_redraw(); @@ -1070,6 +1092,12 @@ void EditorProperty::_bind_methods() { ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorProperty::set_read_only); ClassDB::bind_method(D_METHOD("is_read_only"), &EditorProperty::is_read_only); + ClassDB::bind_method(D_METHOD("set_draw_label", "draw_label"), &EditorProperty::set_draw_label); + ClassDB::bind_method(D_METHOD("is_draw_label"), &EditorProperty::is_draw_label); + + ClassDB::bind_method(D_METHOD("set_draw_background", "draw_background"), &EditorProperty::set_draw_background); + ClassDB::bind_method(D_METHOD("is_draw_background"), &EditorProperty::is_draw_background); + ClassDB::bind_method(D_METHOD("set_checkable", "checkable"), &EditorProperty::set_checkable); ClassDB::bind_method(D_METHOD("is_checkable"), &EditorProperty::is_checkable); @@ -1112,6 +1140,8 @@ void EditorProperty::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_label"), "set_draw_label", "is_draw_label"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_background"), "set_draw_background", "is_draw_background"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable"), "set_checkable", "is_checkable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked"), "set_checked", "is_checked"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_warning"), "set_draw_warning", "is_draw_warning"); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index bc2c8112937..6807b2f55e7 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -89,6 +89,8 @@ class EditorProperty : public Container { int property_usage; + bool draw_label = true; + bool draw_background = true; bool read_only = false; bool checkable = false; bool checked = false; @@ -170,6 +172,12 @@ class EditorProperty : public Container { void set_read_only(bool p_read_only); bool is_read_only() const; + void set_draw_label(bool p_draw_label); + bool is_draw_label() const; + + void set_draw_background(bool p_draw_background); + bool is_draw_background() const; + Object *get_edited_object(); StringName get_edited_property() const; inline Variant get_edited_property_value() const { diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 3cc3a0f7c2a..876b3b04d20 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -146,6 +146,16 @@ bool EditorPropertyDictionaryObject::get_by_property_name(const String &p_name, return true; } + if (name == "new_item_key_name") { + r_ret = TTR("New Key:"); + return true; + } + + if (name == "new_item_value_name") { + r_ret = TTR("New Value:"); + return true; + } + if (name.begins_with("indices")) { int index = name.get_slicec('/', 1).to_int(); Variant key = dict.get_key_at_index(index); @@ -153,6 +163,13 @@ bool EditorPropertyDictionaryObject::get_by_property_name(const String &p_name, return true; } + if (name.begins_with("keys")) { + int index = name.get_slicec('/', 1).to_int(); + Variant key = dict.get_key_at_index(index); + r_ret = key; + return true; + } + return false; } @@ -191,6 +208,17 @@ String EditorPropertyDictionaryObject::get_property_name_for_index(int p_index) } } +String EditorPropertyDictionaryObject::get_key_name_for_index(int p_index) { + switch (p_index) { + case NEW_KEY_INDEX: + return "new_item_key_name"; + case NEW_VALUE_INDEX: + return "new_item_value_name"; + default: + return "keys/" + itos(p_index); + } +} + String EditorPropertyDictionaryObject::get_label_for_index(int p_index) { switch (p_index) { case NEW_KEY_INDEX: @@ -931,7 +959,31 @@ void EditorPropertyDictionary::_add_key_value() { void EditorPropertyDictionary::_create_new_property_slot(int p_idx) { HBoxContainer *hbox = memnew(HBoxContainer); + + EditorProperty *prop_key = nullptr; + if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { + if (key_subtype == Variant::OBJECT) { + EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); + editor->setup("Object"); + prop_key = editor; + } else { + prop_key = EditorInspector::instantiate_property_editor(this, key_subtype, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE); + } + prop_key->set_read_only(true); + prop_key->set_selectable(false); + prop_key->set_focus_mode(Control::FOCUS_NONE); + prop_key->set_draw_background(false); + prop_key->set_use_folding(is_using_folding()); + prop_key->set_h_size_flags(SIZE_EXPAND_FILL); + prop_key->set_draw_label(false); + hbox->add_child(prop_key); + } + EditorProperty *prop = memnew(EditorPropertyNil); + prop->set_h_size_flags(SIZE_EXPAND_FILL); + if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { + prop->set_draw_label(false); + } hbox->add_child(prop); bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX; @@ -959,6 +1011,7 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) { Slot slot; slot.prop = prop; + slot.prop_key = prop_key; slot.object = object; slot.container = hbox; int index = p_idx + (p_idx >= 0 ? page_index * page_length : 0); @@ -1171,6 +1224,9 @@ void EditorPropertyDictionary::update_property() { new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyDictionary::_property_changed)); new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected)); new_prop->set_h_size_flags(SIZE_EXPAND_FILL); + if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { + new_prop->set_draw_label(false); + } new_prop->set_read_only(is_read_only()); slot.set_prop(new_prop); } else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { @@ -1188,6 +1244,9 @@ void EditorPropertyDictionary::update_property() { } slot.prop->update_property(); + if (slot.prop_key) { + slot.prop_key->update_property(); + } } updating = false; diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index 84c3f975be9..abc28d91d51 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -89,6 +89,7 @@ class EditorPropertyDictionaryObject : public RefCounted { String get_label_for_index(int p_index); String get_property_name_for_index(int p_index); + String get_key_name_for_index(int p_index); EditorPropertyDictionaryObject(); }; @@ -184,11 +185,14 @@ class EditorPropertyDictionary : public EditorProperty { Variant::Type type = Variant::VARIANT_MAX; bool as_id = false; EditorProperty *prop = nullptr; + EditorProperty *prop_key = nullptr; String prop_name; + String key_name; void set_index(int p_idx) { index = p_idx; prop_name = object->get_property_name_for_index(p_idx); + key_name = object->get_key_name_for_index(p_idx); update_prop_or_index(); } @@ -201,7 +205,11 @@ class EditorPropertyDictionary : public EditorProperty { void update_prop_or_index() { prop->set_object_and_property(object.ptr(), prop_name); - prop->set_label(object->get_label_for_index(index)); + if (prop_key) { + prop_key->set_object_and_property(object.ptr(), key_name); + } else { + prop->set_label(object->get_label_for_index(index)); + } } }; From b8f34bb8e90ff8b96f6adadc687f8f0821c086dc Mon Sep 17 00:00:00 2001 From: kobewi Date: Wed, 18 Dec 2024 15:57:54 +0100 Subject: [PATCH 11/86] Rework dock layout management --- editor/editor_dock_manager.cpp | 9 +++------ editor/filesystem_dock.cpp | 9 +++++---- editor/filesystem_dock.h | 8 ++++---- editor/history_dock.cpp | 25 +++++++++++++++---------- editor/history_dock.h | 6 +++++- editor/node_dock.cpp | 20 ++++++++++++++++++-- editor/node_dock.h | 7 ++++++- 7 files changed, 56 insertions(+), 28 deletions(-) diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp index bad683bf134..6a4a748bb76 100644 --- a/editor/editor_dock_manager.cpp +++ b/editor/editor_dock_manager.cpp @@ -40,7 +40,6 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" -#include "editor/filesystem_dock.h" #include "editor/gui/editor_bottom_panel.h" #include "editor/themes/editor_scale.h" #include "editor/window_wrapper.h" @@ -480,6 +479,8 @@ void EditorDockManager::save_docks_to_config(Ref p_layout, const Str Array bottom_docks_dump; Array closed_docks_dump; for (const KeyValue &d : all_docks) { + d.key->call(SNAME("_save_layout_to_config"), p_layout, p_section); + if (!d.value.at_bottom && d.value.open && (!d.value.previous_at_bottom || !d.value.dock_window)) { continue; } @@ -516,8 +517,6 @@ void EditorDockManager::save_docks_to_config(Ref p_layout, const Str for (int i = 0; i < hsplits.size(); i++) { p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), int(hsplits[i]->get_split_offset() / EDSCALE)); } - - FileSystemDock::get_singleton()->save_layout_to_config(p_layout, p_section); } void EditorDockManager::load_docks_from_config(Ref p_layout, const String &p_section) { @@ -548,6 +547,7 @@ void EditorDockManager::load_docks_from_config(Ref p_layout, const S continue; } Control *dock = dock_map[name]; + dock->call(SNAME("_load_layout_from_config"), p_layout, p_section); if (!all_docks[dock].enabled) { // Don't open disabled docks. @@ -612,9 +612,6 @@ void EditorDockManager::load_docks_from_config(Ref p_layout, const S int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1)); hsplits[i]->set_split_offset(ofs * EDSCALE); } - - FileSystemDock::get_singleton()->load_layout_from_config(p_layout, p_section); - _update_docks_menu(); } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 8d4a2a9c500..ac1702ee08f 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -184,8 +184,6 @@ FileSystemList::FileSystemList() { popup_editor->connect("popup_hide", callable_mp(this, &FileSystemList::_text_editor_popup_modal_close)); } -FileSystemDock *FileSystemDock::singleton = nullptr; - Ref FileSystemDock::_get_tree_item_icon(bool p_is_valid, const String &p_file_type, const String &p_icon_path) { if (!p_icon_path.is_empty()) { Ref icon = ResourceLoader::load(p_icon_path); @@ -3940,6 +3938,9 @@ void FileSystemDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_dock_horizontal", "enable"), &FileSystemDock::_set_dock_horizontal); ClassDB::bind_method(D_METHOD("_can_dock_horizontal"), &FileSystemDock::_can_dock_horizontal); + ClassDB::bind_method(D_METHOD("_save_layout_to_config"), &FileSystemDock::_save_layout_to_config); + ClassDB::bind_method(D_METHOD("_load_layout_from_config"), &FileSystemDock::_load_layout_from_config); + ADD_SIGNAL(MethodInfo("inherit", PropertyInfo(Variant::STRING, "file"))); ADD_SIGNAL(MethodInfo("instantiate", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files"))); @@ -3953,7 +3954,7 @@ void FileSystemDock::_bind_methods() { ADD_SIGNAL(MethodInfo("display_mode_changed")); } -void FileSystemDock::save_layout_to_config(Ref p_layout, const String &p_section) const { +void FileSystemDock::_save_layout_to_config(Ref p_layout, const String &p_section) const { p_layout->set_value(p_section, "dock_filesystem_h_split_offset", get_h_split_offset()); p_layout->set_value(p_section, "dock_filesystem_v_split_offset", get_v_split_offset()); p_layout->set_value(p_section, "dock_filesystem_display_mode", get_display_mode()); @@ -3965,7 +3966,7 @@ void FileSystemDock::save_layout_to_config(Ref p_layout, const Strin p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", uncollapsed_paths); } -void FileSystemDock::load_layout_from_config(Ref p_layout, const String &p_section) { +void FileSystemDock::_load_layout_from_config(Ref p_layout, const String &p_section) { if (p_layout->has_section_key(p_section, "dock_filesystem_h_split_offset")) { int fs_h_split_ofs = p_layout->get_value(p_section, "dock_filesystem_h_split_offset"); set_h_split_offset(fs_h_split_ofs); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index da47bc9abf8..431f7155b4e 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -357,8 +357,11 @@ class FileSystemDock : public VBoxContainer { bool _can_dock_horizontal() const; void _set_dock_horizontal(bool p_enable); + void _save_layout_to_config(Ref p_layout, const String &p_section) const; + void _load_layout_from_config(Ref p_layout, const String &p_section); + private: - static FileSystemDock *singleton; + inline static FileSystemDock *singleton = nullptr; public: static FileSystemDock *get_singleton() { return singleton; } @@ -414,9 +417,6 @@ class FileSystemDock : public VBoxContainer { void remove_resource_tooltip_plugin(const Ref &p_plugin); Control *create_tooltip_for_path(const String &p_path) const; - void save_layout_to_config(Ref p_layout, const String &p_section) const; - void load_layout_from_config(Ref p_layout, const String &p_section); - FileSystemDock(); ~FileSystemDock(); }; diff --git a/editor/history_dock.cpp b/editor/history_dock.cpp index 1a0971a15cb..ff267732f86 100644 --- a/editor/history_dock.cpp +++ b/editor/history_dock.cpp @@ -30,6 +30,7 @@ #include "history_dock.h" +#include "core/io/config_file.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -172,6 +173,17 @@ void HistoryDock::refresh_version() { action_list->set_current(idx); } +void HistoryDock::_save_layout_to_config(Ref p_layout, const String &p_section) const { + p_layout->set_value(p_section, "dock_history_include_scene", current_scene_checkbox->is_pressed()); + p_layout->set_value(p_section, "dock_history_include_global", global_history_checkbox->is_pressed()); +} + +void HistoryDock::_load_layout_from_config(Ref p_layout, const String &p_section) { + current_scene_checkbox->set_pressed_no_signal(p_layout->get_value(p_section, "dock_history_include_scene", true)); + global_history_checkbox->set_pressed_no_signal(p_layout->get_value(p_section, "dock_history_include_global", true)); + refresh_history(); +} + void HistoryDock::seek_history(int p_index) { bool include_scene = current_scene_checkbox->is_pressed(); bool include_global = global_history_checkbox->is_pressed(); @@ -220,9 +232,9 @@ void HistoryDock::_notification(int p_notification) { } } -void HistoryDock::save_options() { - EditorSettings::get_singleton()->set_project_metadata("history", "include_scene", current_scene_checkbox->is_pressed()); - EditorSettings::get_singleton()->set_project_metadata("history", "include_global", global_history_checkbox->is_pressed()); +void HistoryDock::_bind_methods() { + ClassDB::bind_method(D_METHOD("_save_layout_to_config"), &HistoryDock::_save_layout_to_config); + ClassDB::bind_method(D_METHOD("_load_layout_from_config"), &HistoryDock::_load_layout_from_config); } HistoryDock::HistoryDock() { @@ -235,28 +247,21 @@ HistoryDock::HistoryDock() { HBoxContainer *mode_hb = memnew(HBoxContainer); add_child(mode_hb); - bool include_scene = EditorSettings::get_singleton()->get_project_metadata("history", "include_scene", true); - bool include_global = EditorSettings::get_singleton()->get_project_metadata("history", "include_global", true); - current_scene_checkbox = memnew(CheckBox); mode_hb->add_child(current_scene_checkbox); current_scene_checkbox->set_flat(true); - current_scene_checkbox->set_pressed(include_scene); current_scene_checkbox->set_text(TTR("Scene")); current_scene_checkbox->set_h_size_flags(SIZE_EXPAND_FILL); current_scene_checkbox->set_clip_text(true); current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1)); - current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1)); global_history_checkbox = memnew(CheckBox); mode_hb->add_child(global_history_checkbox); global_history_checkbox->set_flat(true); - global_history_checkbox->set_pressed(include_global); global_history_checkbox->set_text(TTR("Global")); global_history_checkbox->set_h_size_flags(SIZE_EXPAND_FILL); global_history_checkbox->set_clip_text(true); global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1)); - global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1)); action_list = memnew(ItemList); action_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); diff --git a/editor/history_dock.h b/editor/history_dock.h index 0a023bdd059..00afbafbed9 100644 --- a/editor/history_dock.h +++ b/editor/history_dock.h @@ -34,6 +34,7 @@ #include "scene/gui/box_container.h" class CheckBox; +class ConfigFile; class ItemList; class EditorUndoRedoManager; @@ -53,10 +54,13 @@ class HistoryDock : public VBoxContainer { void refresh_history(); void on_version_changed(); void refresh_version(); - void save_options(); + + void _save_layout_to_config(Ref p_layout, const String &p_section) const; + void _load_layout_from_config(Ref p_layout, const String &p_section); protected: void _notification(int p_notification); + static void _bind_methods(); public: void seek_history(int p_index); diff --git a/editor/node_dock.cpp b/editor/node_dock.cpp index 6b711349da0..16819629185 100644 --- a/editor/node_dock.cpp +++ b/editor/node_dock.cpp @@ -30,6 +30,7 @@ #include "node_dock.h" +#include "core/io/config_file.h" #include "editor/connections_dialog.h" #include "editor/editor_node.h" #include "editor/themes/editor_scale.h" @@ -48,9 +49,21 @@ void NodeDock::show_connections() { connections->show(); } +void NodeDock::_save_layout_to_config(Ref p_layout, const String &p_section) const { + p_layout->set_value(p_section, "dock_node_current_tab", int(groups_button->is_pressed())); +} + +void NodeDock::_load_layout_from_config(Ref p_layout, const String &p_section) { + const int current_tab = p_layout->get_value(p_section, "dock_node_current_tab", 0); + if (current_tab == 0) { + show_connections(); + } else if (current_tab == 1) { + show_groups(); + } +} + void NodeDock::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { connections_button->set_button_icon(get_editor_theme_icon(SNAME("Signals"))); groups_button->set_button_icon(get_editor_theme_icon(SNAME("Groups"))); @@ -58,7 +71,10 @@ void NodeDock::_notification(int p_what) { } } -NodeDock *NodeDock::singleton = nullptr; +void NodeDock::_bind_methods() { + ClassDB::bind_method(D_METHOD("_save_layout_to_config"), &NodeDock::_save_layout_to_config); + ClassDB::bind_method(D_METHOD("_load_layout_from_config"), &NodeDock::_load_layout_from_config); +} void NodeDock::update_lists() { connections->update_tree(); diff --git a/editor/node_dock.h b/editor/node_dock.h index adf378bd066..24a8aa06bbd 100644 --- a/editor/node_dock.h +++ b/editor/node_dock.h @@ -33,6 +33,7 @@ #include "groups_editor.h" +class ConfigFile; class ConnectionsDock; class NodeDock : public VBoxContainer { @@ -48,14 +49,18 @@ class NodeDock : public VBoxContainer { Label *select_a_node = nullptr; + void _save_layout_to_config(Ref p_layout, const String &p_section) const; + void _load_layout_from_config(Ref p_layout, const String &p_section); + private: - static NodeDock *singleton; + inline static NodeDock *singleton = nullptr; public: static NodeDock *get_singleton() { return singleton; } protected: void _notification(int p_what); + static void _bind_methods(); public: void set_node(Node *p_node); From 143d8c87bb4c004c0078c5db93e57f89338ff133 Mon Sep 17 00:00:00 2001 From: demolke Date: Sat, 30 Nov 2024 00:05:05 +0100 Subject: [PATCH 12/86] Move reimport check to EditorImportPlugin reimport_append is used by gltf_document, fbx_document and editor_import_plugin. The first two will never call it when importing == false. It's only the editor_import_plugin that should guard against that. https://docs.godotengine.org/en/stable/classes/class_editorimportplugin.html#class-editorimportplugin-method-append-import-external-resource The motivation of removing the check from gltf_document call path is to be able to test nested imports (texture embedded in gltf). --- editor/editor_file_system.cpp | 1 - editor/import/editor_import_plugin.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 4911d5c702d..b3abbb9e70f 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -3205,7 +3205,6 @@ void EditorFileSystem::reimport_files(const Vector &p_files) { } Error EditorFileSystem::reimport_append(const String &p_file, const HashMap &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) { - ERR_FAIL_COND_V_MSG(!importing, ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process."); Vector reloads; reloads.append(p_file); diff --git a/editor/import/editor_import_plugin.cpp b/editor/import/editor_import_plugin.cpp index 27b59d3bef5..82710103e1f 100644 --- a/editor/import/editor_import_plugin.cpp +++ b/editor/import/editor_import_plugin.cpp @@ -214,6 +214,7 @@ Error EditorImportPlugin::_append_import_external_resource(const String &p_file, } Error EditorImportPlugin::append_import_external_resource(const String &p_file, const HashMap &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) { + ERR_FAIL_COND_V_MSG(!EditorFileSystem::get_singleton()->is_importing(), ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process."); return EditorFileSystem::get_singleton()->reimport_append(p_file, p_custom_options, p_custom_importer, p_generator_parameters); } From e649e7e3c59fc173fb080a30e643a25040cd7755 Mon Sep 17 00:00:00 2001 From: demolke Date: Fri, 29 Nov 2024 21:33:00 +0100 Subject: [PATCH 13/86] GLTF: Don't duplicate textures when importing blend files Blender imports will always start within `.godot/imported` folder because we first convert the .blend file to .gltf, store it in `.godot/imported` and run the import from there, so on-disk resources linked from .blend files end up with duplicate textures. --- .../editor/editor_scene_importer_gltf.cpp | 3 + modules/gltf/gltf_document.cpp | 75 +++++--- modules/gltf/gltf_document.h | 2 +- .../embedded_texture.gltf | 147 +++++++++++++++ .../gltf_placed_in_dot_godot_imported.gltf | 147 +++++++++++++++ .../texture.png | Bin 0 -> 92 bytes ...ting_to_texture_outside_of_res_folder.gltf | 147 +++++++++++++++ .../texture_source.png | Bin 0 -> 92 bytes modules/gltf/tests/test_gltf.h | 165 +++++++++++++++++ modules/gltf/tests/test_gltf_extras.h | 83 ++------- modules/gltf/tests/test_gltf_images.h | 169 ++++++++++++++++++ 11 files changed, 845 insertions(+), 93 deletions(-) create mode 100644 modules/gltf/tests/data/gltf_embedded_texture/embedded_texture.gltf create mode 100644 modules/gltf/tests/data/gltf_placed_in_dot_godot_imported/gltf_placed_in_dot_godot_imported.gltf create mode 100644 modules/gltf/tests/data/gltf_placed_in_dot_godot_imported/texture.png create mode 100644 modules/gltf/tests/data/gltf_pointing_to_texture_outside_of_res_folder/gltf_pointing_to_texture_outside_of_res_folder.gltf create mode 100644 modules/gltf/tests/data/gltf_pointing_to_texture_outside_of_res_folder/texture_source.png create mode 100644 modules/gltf/tests/test_gltf.h create mode 100644 modules/gltf/tests/test_gltf_images.h diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 41e294cfc6e..3e75017fe65 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -62,6 +62,9 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { state->set_import_as_skeleton_bones(true); } + if (p_options.has(SNAME("extract_path"))) { + state->set_extract_path(p_options["extract_path"]); + } state->set_bake_fps(p_options["animation/fps"]); Error err = gltf->append_from_file(p_path, state, p_flags); if (err != OK) { diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index bfd21891299..e5d8955fb6a 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -3932,7 +3932,7 @@ Ref GLTFDocument::_parse_image_bytes_into_image(Ref p_state, c return r_image; } -void GLTFDocument::_parse_image_save_image(Ref p_state, const Vector &p_bytes, const String &p_file_extension, int p_index, Ref p_image) { +void GLTFDocument::_parse_image_save_image(Ref p_state, const Vector &p_bytes, const String &p_resource_uri, const String &p_file_extension, int p_index, Ref p_image) { GLTFState::GLTFHandleBinary handling = GLTFState::GLTFHandleBinary(p_state->handle_binary_image); if (p_image->is_empty() || handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) { p_state->images.push_back(Ref()); @@ -3950,33 +3950,46 @@ void GLTFDocument::_parse_image_save_image(Ref p_state, const Vector< WARN_PRINT(vformat("glTF: Image index '%d' did not have a name. It will be automatically given a name based on its index.", p_index)); p_image->set_name(itos(p_index)); } - bool must_import = true; + bool must_write = true; // If the resource does not exist on the disk within res:// directory write it. + bool must_import = true; // Trigger import. Vector img_data = p_image->get_data(); Dictionary generator_parameters; - String file_path = p_state->get_extract_path().path_join(p_state->get_extract_prefix() + "_" + p_image->get_name()); - file_path += p_file_extension.is_empty() ? ".png" : p_file_extension; - if (FileAccess::exists(file_path + ".import")) { - Ref config; - config.instantiate(); - config->load(file_path + ".import"); - if (config->has_section_key("remap", "generator_parameters")) { - generator_parameters = (Dictionary)config->get_value("remap", "generator_parameters"); - } - if (!generator_parameters.has("md5")) { - must_import = false; // Didn't come from a gltf document; don't overwrite. + String file_path; + // If resource_uri is within res:// folder but outside of .godot/imported folder, use it. + if (!p_resource_uri.is_empty() && !p_resource_uri.begins_with("res://.godot/imported") && !p_resource_uri.begins_with("res://..")) { + file_path = p_resource_uri; + must_import = true; + must_write = !FileAccess::exists(file_path); + } else { + // Texture data has to be written to the res:// folder and imported. + file_path = p_state->get_extract_path().path_join(p_state->get_extract_prefix() + "_" + p_image->get_name()); + file_path += p_file_extension.is_empty() ? ".png" : p_file_extension; + if (FileAccess::exists(file_path + ".import")) { + Ref config; + config.instantiate(); + config->load(file_path + ".import"); + if (config->has_section_key("remap", "generator_parameters")) { + generator_parameters = (Dictionary)config->get_value("remap", "generator_parameters"); + } + if (!generator_parameters.has("md5")) { + must_write = false; // Didn't come from a gltf document; don't overwrite. + must_import = false; // And don't import. + } } } - if (must_import) { + + if (must_write) { String existing_md5 = generator_parameters["md5"]; unsigned char md5_hash[16]; CryptoCore::md5(img_data.ptr(), img_data.size(), md5_hash); String new_md5 = String::hex_encode_buffer(md5_hash, 16); generator_parameters["md5"] = new_md5; if (new_md5 == existing_md5) { + must_write = false; must_import = false; } } - if (must_import) { + if (must_write) { Error err = OK; if (p_file_extension.is_empty()) { // If a file extension was not specified, save the image data to a PNG file. @@ -3989,10 +4002,13 @@ void GLTFDocument::_parse_image_save_image(Ref p_state, const Vector< file->store_buffer(p_bytes); file->close(); } + } + if (must_import) { // ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed. HashMap custom_options; custom_options[SNAME("mipmaps/generate")] = true; // Will only use project settings defaults if custom_importer is empty. + EditorFileSystem::get_singleton()->update_file(file_path); EditorFileSystem::get_singleton()->reimport_append(file_path, custom_options, String(), generator_parameters); } @@ -4002,7 +4018,7 @@ void GLTFDocument::_parse_image_save_image(Ref p_state, const Vector< p_state->source_images.push_back(saved_image->get_image()); return; } else { - WARN_PRINT(vformat("glTF: Image index '%d' with the name '%s' couldn't be imported. It will be loaded directly instead, uncompressed.", p_index, p_image->get_name())); + WARN_PRINT(vformat("glTF: Image index '%d' with the name '%s' resolved to %s couldn't be imported. It will be loaded directly instead, uncompressed.", p_index, p_image->get_name(), file_path)); } } } @@ -4070,6 +4086,9 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p while (used_names.has(image_name)) { image_name += "_" + itos(i); } + + String resource_uri; + used_names.insert(image_name); // Load the image data. If we get a byte array, store here for later. Vector data; @@ -4087,14 +4106,14 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. - // If the image is in the .godot/imported directory, we can't use ResourceLoader. - if (!p_base_path.begins_with("res://.godot/imported")) { - // ResourceLoader will rely on the file extension to use the relevant loader. - // The spec says that if mimeType is defined, it should take precedence (e.g. - // there could be a `.png` image which is actually JPEG), but there's no easy - // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in - // the material), so we only do that only as fallback. - Ref texture = ResourceLoader::load(uri, "Texture2D"); + resource_uri = uri.simplify_path(); + // ResourceLoader will rely on the file extension to use the relevant loader. + // The spec says that if mimeType is defined, it should take precedence (e.g. + // there could be a `.png` image which is actually JPEG), but there's no easy + // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in + // the material), so we only do that only as fallback. + if (ResourceLoader::exists(resource_uri)) { + Ref texture = ResourceLoader::load(resource_uri, "Texture2D"); if (texture.is_valid()) { p_state->images.push_back(texture); p_state->source_images.push_back(texture->get_image()); @@ -4105,13 +4124,13 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p // If the mimeType does not match with the file extension, either it should be // specified in the file, or the GLTFDocumentExtension should handle it. if (mime_type.is_empty()) { - mime_type = "image/" + uri.get_extension(); + mime_type = "image/" + resource_uri.get_extension(); } // Fallback to loading as byte array. This enables us to support the // spec's requirement that we honor mimetype regardless of file URI. - data = FileAccess::get_file_as_bytes(uri); + data = FileAccess::get_file_as_bytes(resource_uri); if (data.size() == 0) { - WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded as a buffer of MIME type '%s' from URI: %s because there was no data to load. Skipping it.", i, mime_type, uri)); + WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded as a buffer of MIME type '%s' from URI: %s because there was no data to load. Skipping it.", i, mime_type, resource_uri)); p_state->images.push_back(Ref()); // Placeholder to keep count. p_state->source_images.push_back(Ref()); continue; @@ -4141,7 +4160,7 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p String file_extension; Ref img = _parse_image_bytes_into_image(p_state, data, mime_type, i, file_extension); img->set_name(image_name); - _parse_image_save_image(p_state, data, file_extension, i, img); + _parse_image_save_image(p_state, data, resource_uri, file_extension, i, img); } print_verbose("glTF: Total images: " + itos(p_state->images.size())); diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index a6d6caa3f0a..f17d1a1d732 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -190,7 +190,7 @@ class GLTFDocument : public Resource { Error _serialize_images(Ref p_state); Error _serialize_lights(Ref p_state); Ref _parse_image_bytes_into_image(Ref p_state, const Vector &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension); - void _parse_image_save_image(Ref p_state, const Vector &p_bytes, const String &p_file_extension, int p_index, Ref p_image); + void _parse_image_save_image(Ref p_state, const Vector &p_bytes, const String &p_resource_uri, const String &p_file_extension, int p_index, Ref p_image); Error _parse_images(Ref p_state, const String &p_base_path); Error _parse_textures(Ref p_state); Error _parse_texture_samplers(Ref p_state); diff --git a/modules/gltf/tests/data/gltf_embedded_texture/embedded_texture.gltf b/modules/gltf/tests/data/gltf_embedded_texture/embedded_texture.gltf new file mode 100644 index 00000000000..59b154fb3d6 --- /dev/null +++ b/modules/gltf/tests/data/gltf_embedded_texture/embedded_texture.gltf @@ -0,0 +1,147 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.2.70", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 1 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"mesh_instance_3d" + }, + { + "children":[ + 0 + ], + "name":"_Node3D_6" + } + ], + "materials":[ + { + "name":"material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.9999998807907104, + 0.9999998807907104, + 0.9999998807907104, + 1 + ], + "baseColorTexture":{ + "index":0 + }, + "metallicFactor":0 + } + } + ], + "meshes":[ + { + "name":"Mesh_0", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + } + ], + "images":[ + { + "mimeType":"image/png", + "name":"material_albedo000", + "uri":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABZJREFUCJljZGBg+P+fgYGBBUIxMAAAKCAEAplLvcoAAAAASUVORK5CYII=" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":4, + "max":[ + 1, + 0, + 1 + ], + "min":[ + -1, + 0, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":4, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":6, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":48, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":48, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":96, + "target":34962 + }, + { + "buffer":0, + "byteLength":12, + "byteOffset":128, + "target":34963 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":140, + "uri":"data:application/octet-stream;base64,AACAPwAAAAAAAIA/AACAvwAAAAAAAIA/AACAPwAAAAAAAIC/AACAvwAAAAAAAIC/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAACAAEAAAACAAMAAQA=" + } + ] +} diff --git a/modules/gltf/tests/data/gltf_placed_in_dot_godot_imported/gltf_placed_in_dot_godot_imported.gltf b/modules/gltf/tests/data/gltf_placed_in_dot_godot_imported/gltf_placed_in_dot_godot_imported.gltf new file mode 100644 index 00000000000..3c03102eea3 --- /dev/null +++ b/modules/gltf/tests/data/gltf_placed_in_dot_godot_imported/gltf_placed_in_dot_godot_imported.gltf @@ -0,0 +1,147 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.2.70", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 1 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"mesh_instance_3d" + }, + { + "children":[ + 0 + ], + "name":"_Node3D_6" + } + ], + "materials":[ + { + "name":"material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.9999998807907104, + 0.9999998807907104, + 0.9999998807907104, + 1 + ], + "baseColorTexture":{ + "index":0 + }, + "metallicFactor":0 + } + } + ], + "meshes":[ + { + "name":"Mesh_0", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + } + ], + "images":[ + { + "mimeType":"image/png", + "name":"material_albedo000", + "uri":"texture.png", + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":4, + "max":[ + 1, + 0, + 1 + ], + "min":[ + -1, + 0, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":4, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":6, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":48, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":48, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":96, + "target":34962 + }, + { + "buffer":0, + "byteLength":12, + "byteOffset":128, + "target":34963 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":140, + "uri":"data:application/octet-stream;base64,AACAPwAAAAAAAIA/AACAvwAAAAAAAIA/AACAPwAAAAAAAIC/AACAvwAAAAAAAIC/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAACAAEAAAACAAMAAQA=" + } + ] +} diff --git a/modules/gltf/tests/data/gltf_placed_in_dot_godot_imported/texture.png b/modules/gltf/tests/data/gltf_placed_in_dot_godot_imported/texture.png new file mode 100644 index 0000000000000000000000000000000000000000..cb872e7419b7904df8cc02fa9fb7f84a8885bb0a GIT binary patch literal 92 zcmeAS@N?(olHy`uVBq!ia0vp^Od!m`1|*BN@u~nR#^NA%Cx&(BWL^R}VxBIJAsjQ4 nQxXz>{GZ?0*vRT+Xu!arp}@j4(|hkJpb7?0S3j3^P6 import_scene; + import_scene.instantiate("PackedScene", true); + ResourceFormatImporter::get_singleton()->add_importer(import_scene); + Ref import_gltf; + import_gltf.instantiate(); + ResourceImporterScene::add_scene_importer(import_gltf); + + // Support processing png files in editor import. + Ref import_texture; + import_texture.instantiate(true); + ResourceFormatImporter::get_singleton()->add_importer(import_texture); + + // Once editor import convert pngs to ctex, we will need to load it as ctex resource. + Ref resource_loader_stream_texture; + resource_loader_stream_texture.instantiate(); + ResourceLoader::add_resource_format_loader(resource_loader_stream_texture); + + HashMap options(21); + options["nodes/root_type"] = ""; + options["nodes/root_name"] = ""; + options["nodes/apply_root_scale"] = true; + options["nodes/root_scale"] = 1.0; + options["meshes/ensure_tangents"] = true; + options["meshes/generate_lods"] = false; + options["meshes/create_shadow_meshes"] = true; + options["meshes/light_baking"] = 1; + options["meshes/lightmap_texel_size"] = 0.2; + options["meshes/force_disable_compression"] = false; + options["skins/use_named_skins"] = true; + options["animation/import"] = true; + options["animation/fps"] = 30; + options["animation/trimming"] = false; + options["animation/remove_immutable_tracks"] = true; + options["import_script/path"] = ""; + options["extract_path"] = "res://"; + options["_subresources"] = Dictionary(); + options["gltf/naming_version"] = 1; + + // Process gltf file, note that this generates `.scn` resource from the 2nd argument. + String scene_file = "res://" + p_file.get_file().get_basename(); + Error err = import_scene->import(0, p_file, scene_file, options, nullptr, nullptr, nullptr); + CHECK_MESSAGE(err == OK, "GLTF import failed."); + + Ref packed_scene = ResourceLoader::load(scene_file + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); + CHECK_MESSAGE(err == OK, "Loading scene failed."); + Node *p_scene = packed_scene->instantiate(); + + ResourceImporterScene::remove_scene_importer(import_gltf); + ResourceFormatImporter::get_singleton()->remove_importer(import_texture); + ResourceLoader::remove_resource_format_loader(resource_loader_stream_texture); + return p_scene; +} + +static Node *gltf_export_then_import(Node *p_root, const String &p_test_name) { + String tempfile = TestUtils::get_temp_path(p_test_name); + + Ref doc; + doc.instantiate(); + Ref state; + state.instantiate(); + Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS); + CHECK_MESSAGE(err == OK, "GLTF state generation failed."); + + err = doc->write_to_filesystem(state, tempfile + ".gltf"); + CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed."); + + return gltf_import(tempfile + ".gltf"); +} + +void init(const String &p_test, const String &p_copy_target = String()) { + Error err; + + // Setup project settings since it's needed for the import process. + String project_folder = TestUtils::get_temp_path(p_test.get_file().get_basename()); + Ref da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + da->make_dir_recursive(project_folder.path_join(".godot").path_join("imported")); + // Initialize res:// to `project_folder`. + TestProjectSettingsInternalsAccessor::resource_path() = project_folder; + err = ProjectSettings::get_singleton()->setup(project_folder, String(), true); + + if (p_copy_target.is_empty()) { + return; + } + + // Copy all the necessary test data files to the res:// directory. + da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + String test_data = String("modules/gltf/tests/data/").path_join(p_test); + da = DirAccess::open(test_data); + CHECK_MESSAGE(da.is_valid(), "Unable to open folder."); + da->list_dir_begin(); + for (String item = da->get_next(); !item.is_empty(); item = da->get_next()) { + if (!FileAccess::exists(test_data.path_join(item))) { + continue; + } + Ref output = FileAccess::open(p_copy_target.path_join(item), FileAccess::WRITE, &err); + CHECK_MESSAGE(err == OK, "Unable to open output file."); + output->store_buffer(FileAccess::get_file_as_bytes(test_data.path_join(item))); + output->close(); + } + da->list_dir_end(); +} + +} //namespace TestGltf + +#endif // TOOLS_ENABLED + +#endif // TEST_GLTF_H diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h index 73ef02e9f1b..84a4dff76b9 100644 --- a/modules/gltf/tests/test_gltf_extras.h +++ b/modules/gltf/tests/test_gltf_extras.h @@ -31,6 +31,7 @@ #ifndef TEST_GLTF_EXTRAS_H #define TEST_GLTF_EXTRAS_H +#include "test_gltf.h" #include "tests/test_macros.h" #ifdef TOOLS_ENABLED @@ -47,61 +48,10 @@ #include "scene/resources/material.h" #include "scene/resources/packed_scene.h" -namespace TestGltfExtras { - -static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) { - Ref doc; - doc.instantiate(); - Ref state; - state.instantiate(); - Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS); - CHECK_MESSAGE(err == OK, "GLTF state generation failed."); - err = doc->write_to_filesystem(state, p_tempfilebase + ".gltf"); - CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed."); - - // Setting up importers. - Ref import_scene = memnew(ResourceImporterScene("PackedScene", true)); - ResourceFormatImporter::get_singleton()->add_importer(import_scene); - Ref import_gltf; - import_gltf.instantiate(); - ResourceImporterScene::add_scene_importer(import_gltf); - - // GTLF importer behaves differently outside of editor, it's too late to modify Engine::get_editor_hint - // as the registration of runtime extensions already happened, so remove them. See modules/gltf/register_types.cpp - GLTFDocument::unregister_all_gltf_document_extensions(); - - HashMap options(20); - options["nodes/root_type"] = ""; - options["nodes/root_name"] = ""; - options["nodes/apply_root_scale"] = true; - options["nodes/root_scale"] = 1.0; - options["meshes/ensure_tangents"] = true; - options["meshes/generate_lods"] = false; - options["meshes/create_shadow_meshes"] = true; - options["meshes/light_baking"] = 1; - options["meshes/lightmap_texel_size"] = 0.2; - options["meshes/force_disable_compression"] = false; - options["skins/use_named_skins"] = true; - options["animation/import"] = true; - options["animation/fps"] = 30; - options["animation/trimming"] = false; - options["animation/remove_immutable_tracks"] = true; - options["import_script/path"] = ""; - options["_subresources"] = Dictionary(); - options["gltf/naming_version"] = 1; - - // Process gltf file, note that this generates `.scn` resource from the 2nd argument. - err = import_scene->import(0, p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr); - CHECK_MESSAGE(err == OK, "GLTF import failed."); - ResourceImporterScene::remove_scene_importer(import_gltf); - - Ref packed_scene = ResourceLoader::load(p_tempfilebase + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); - CHECK_MESSAGE(err == OK, "Loading scene failed."); - Node *p_scene = packed_scene->instantiate(); - return p_scene; -} +namespace TestGltf { TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") { + init("gltf_mesh_material_extras"); // Setup scene. Ref original_material = memnew(StandardMaterial3D); original_material->set_albedo(Color(1.0, .0, .0)); @@ -133,9 +83,11 @@ TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import" original->set_meta("extras", node_dict); original->set_meta("meta_not_nested_under_extras", "should not propagate"); + original->set_owner(SceneTree::get_singleton()->get_root()); + original_mesh_instance->set_owner(SceneTree::get_singleton()->get_root()); + // Convert to GLFT and back. - String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_extras"); - Node *loaded = _gltf_export_then_import(original, tempfile); + Node *loaded = gltf_export_then_import(original, "gltf_extras"); // Compare the results. CHECK(loaded->get_name() == "node3d"); @@ -161,6 +113,7 @@ TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import" } TEST_CASE("[SceneTree][Node] GLTF test skeleton and bone export and import") { + init("gltf_skeleton_extras"); // Setup scene. Skeleton3D *skeleton = memnew(Skeleton3D); skeleton->set_name("skeleton"); @@ -189,18 +142,20 @@ TEST_CASE("[SceneTree][Node] GLTF test skeleton and bone export and import") { mesh->set_mesh(meshdata); mesh->set_name("mesh_instance_3d"); - Node3D *scene = memnew(Node3D); - SceneTree::get_singleton()->get_root()->add_child(scene); - scene->add_child(skeleton); - scene->add_child(mesh); - scene->set_name("node3d"); + Node3D *original = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(original); + original->add_child(skeleton); + original->add_child(mesh); + original->set_name("node3d"); // Now that both skeleton and mesh are part of scene, link them. mesh->set_skeleton_path(mesh->get_path_to(skeleton)); + mesh->set_owner(SceneTree::get_singleton()->get_root()); + original->set_owner(SceneTree::get_singleton()->get_root()); + // Convert to GLFT and back. - String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_bone_extras"); - Node *loaded = _gltf_export_then_import(scene, tempfile); + Node *loaded = gltf_export_then_import(original, "gltf_bone_extras"); // Compare the results. CHECK(loaded->get_name() == "node3d"); @@ -212,10 +167,10 @@ TEST_CASE("[SceneTree][Node] GLTF test skeleton and bone export and import") { memdelete(skeleton); memdelete(mesh); - memdelete(scene); + memdelete(original); memdelete(loaded); } -} // namespace TestGltfExtras +} //namespace TestGltf #endif // TOOLS_ENABLED diff --git a/modules/gltf/tests/test_gltf_images.h b/modules/gltf/tests/test_gltf_images.h new file mode 100644 index 00000000000..969ed396656 --- /dev/null +++ b/modules/gltf/tests/test_gltf_images.h @@ -0,0 +1,169 @@ +/**************************************************************************/ +/* test_gltf_images.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_GLTF_IMAGES_H +#define TEST_GLTF_IMAGES_H + +#include "test_gltf.h" + +#ifdef TOOLS_ENABLED + +#include "editor/editor_file_system.h" +#include "editor/editor_paths.h" +#include "scene/resources/image_texture.h" + +namespace TestGltf { +Ref _check_texture(Node *p_node) { + MeshInstance3D *mesh_instance_3d = Object::cast_to(p_node->find_child("mesh_instance_3d", true, true)); + Ref material = mesh_instance_3d->get_active_material(0); + Ref texture = material->get_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO); + + CHECK_MESSAGE(texture->get_size().x == 2, "Texture width not correct."); + CHECK_MESSAGE(texture->get_size().y == 2, "Texture height not correct."); + + // Check if the loaded texture pixels are exactly as we expect. + for (int x = 0; x < 2; ++x) { + for (int y = 0; y < 2; ++y) { + Color c = texture->get_image()->get_pixel(x, y); + CHECK_MESSAGE(c == Color(x, y, y), "Texture content is incorrect."); + } + } + return texture; +} + +TEST_CASE("[SceneTree][Node] Export GLTF with external texture and import") { + init("gltf_images_external_export_import"); + // Setup scene. + Ref original_texture; + original_texture.instantiate(); + Ref image; + image.instantiate(); + image->initialize_data(2, 2, false, Image::FORMAT_RGBA8); + for (int x = 0; x < 2; ++x) { + for (int y = 0; y < 2; ++y) { + image->set_pixel(x, y, Color(x, y, y)); + } + } + + original_texture->set_image(image); + + Ref original_material; + original_material.instantiate(); + original_material->set_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, original_texture); + original_material->set_name("material"); + + Ref original_meshdata; + original_meshdata.instantiate(); + original_meshdata->set_name("planemesh"); + original_meshdata->surface_set_material(0, original_material); + + MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D); + original_mesh_instance->set_mesh(original_meshdata); + original_mesh_instance->set_name("mesh_instance_3d"); + + Node3D *original = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(original); + original->add_child(original_mesh_instance); + original->set_owner(SceneTree::get_singleton()->get_root()); + original_mesh_instance->set_owner(SceneTree::get_singleton()->get_root()); + + // Convert to GLFT and back. + Node *loaded = gltf_export_then_import(original, "gltf_images"); + _check_texture(loaded); + + memdelete(original_mesh_instance); + memdelete(original); + memdelete(loaded); +} + +TEST_CASE("[SceneTree][Node][Editor] Import GLTF from .godot/imported folder with external texture") { + init("gltf_placed_in_dot_godot_imported", "res://.godot/imported"); + + EditorFileSystem *efs = memnew(EditorFileSystem); + EditorResourcePreview *erp = memnew(EditorResourcePreview); + + Node *loaded = gltf_import("res://.godot/imported/gltf_placed_in_dot_godot_imported.gltf"); + Ref texture = _check_texture(loaded); + + // In-editor imports of gltf and texture from .godot/imported folder should end up in res:// if extract_path is defined. + CHECK_MESSAGE(texture->get_path() == "res://gltf_placed_in_dot_godot_imported_material_albedo000.png", "Texture not parsed as resource."); + + memdelete(loaded); + memdelete(erp); + memdelete(efs); +} + +TEST_CASE("[SceneTree][Node][Editor] Import GLTF with texture outside of res:// directory") { + init("gltf_pointing_to_texture_outside_of_res_folder", "res://"); + + EditorFileSystem *efs = memnew(EditorFileSystem); + EditorResourcePreview *erp = memnew(EditorResourcePreview); + + // Copy texture to the parent folder of res:// - i.e. to res://.. where we can't import from. + String oneup = TestUtils::get_temp_path("texture.png"); + Error err; + Ref output = FileAccess::open(oneup, FileAccess::WRITE, &err); + CHECK_MESSAGE(err == OK, "Unable to open texture file."); + output->store_buffer(FileAccess::get_file_as_bytes("res://texture_source.png")); + output->close(); + + Node *loaded = gltf_import("res://gltf_pointing_to_texture_outside_of_res_folder.gltf"); + Ref texture = _check_texture(loaded); + + // Imports of gltf with texture from outside of res:// folder should end up being copied to res:// + CHECK_MESSAGE(texture->get_path() == "res://gltf_pointing_to_texture_outside_of_res_folder_material_albedo000.png", "Texture not parsed as resource."); + + memdelete(loaded); + memdelete(erp); + memdelete(efs); +} + +TEST_CASE("[SceneTree][Node][Editor] Import GLTF with embedded texture, check how it got extracted") { + init("gltf_embedded_texture", "res://"); + + EditorFileSystem *efs = memnew(EditorFileSystem); + EditorResourcePreview *erp = memnew(EditorResourcePreview); + + Node *loaded = gltf_import("res://embedded_texture.gltf"); + Ref texture = _check_texture(loaded); + + // In-editor imports of texture embedded in file should end up with a resource. + CHECK_MESSAGE(texture->get_path() == "res://embedded_texture_material_albedo000.png", "Texture not parsed as resource."); + + memdelete(loaded); + memdelete(erp); + memdelete(efs); +} + +} //namespace TestGltf + +#endif // TOOLS_ENABLED + +#endif // TEST_GLTF_IMAGES_H From ba54a2805a2a92fb8efaac5095a8eb9ef9b25b97 Mon Sep 17 00:00:00 2001 From: kobewi Date: Wed, 18 Dec 2024 14:42:10 +0100 Subject: [PATCH 14/86] Add more menus support to EditorContextMenuPlugin --- doc/classes/EditorContextMenuPlugin.xml | 23 +++++++++++++-- editor/gui/editor_scene_tabs.cpp | 14 +++++++++ editor/gui/editor_scene_tabs.h | 3 ++ editor/plugins/canvas_item_editor_plugin.cpp | 29 +++++++++++++++++++ editor/plugins/editor_context_menu_plugin.cpp | 14 ++++++++- editor/plugins/editor_context_menu_plugin.h | 4 +++ editor/plugins/script_text_editor.cpp | 9 ++++++ 7 files changed, 93 insertions(+), 3 deletions(-) diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml index fb90a2a5cd6..2717a0bc9a2 100644 --- a/doc/classes/EditorContextMenuPlugin.xml +++ b/doc/classes/EditorContextMenuPlugin.xml @@ -85,11 +85,30 @@ Context menu of FileSystem dock. [method _popup_menu] and option callback will be called with list of paths of the currently selected files. + + Context menu of Script editor's script tabs. [method _popup_menu] will be called with the path to the currently edited script, while option callback will receive reference to that script. + The "Create..." submenu of FileSystem dock's context menu. [method _popup_menu] and option callback will be called with list of paths of the currently selected files. - - Context menu of Scene dock. [method _popup_menu] will be called with the path to the currently edited script, while option callback will receive reference to that script. + + Context menu of Script editor's code editor. [method _popup_menu] will be called with the path to the [CodeEdit] node. You can fetch it using this code: + [codeblock] + func _popup_menu(paths): + var code_edit = Engine.get_main_loop().root.get_node(paths[0]); + [/codeblock] + The option callback will receive reference to that node. You can use [CodeEdit] methods to perform symbol lookups etc. + + + Context menu of scene tabs. [method _popup_menu] will be called with the path of the clicked scene, or empty [PackedStringArray] if the menu was opened on empty space. The option callback will receive the path of the clicked scene, or empty [String] if none was clicked. + + + Context menu of 2D editor's basic right-click menu. [method _popup_menu] will be called with paths to all [CanvasItem] nodes under the cursor. You can fetch them using this code: + [codeblock] + func _popup_menu(paths): + var canvas_item = Engine.get_main_loop().root.get_node(paths[0]); # Replace 0 with the desired index. + [/codeblock] + The paths array is empty if there weren't any nodes under cursor. The option callback will receive a typed array of [CanvasItem] nodes. diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp index 5b42afdbe8b..36de87ef24a 100644 --- a/editor/gui/editor_scene_tabs.cpp +++ b/editor/gui/editor_scene_tabs.cpp @@ -36,6 +36,7 @@ #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/inspector_dock.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" @@ -195,7 +196,13 @@ void EditorSceneTabs::_update_context_menu() { scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), EditorNode::FILE_CLOSE_RIGHT); _disable_menu_option_if(EditorNode::FILE_CLOSE_RIGHT, EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1); scene_tabs_context_menu->add_item(TTR("Close All Tabs"), EditorNode::FILE_CLOSE_ALL); + + const PackedStringArray paths = { EditorNode::get_editor_data().get_scene_path(tab_id) }; + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, paths); + } else { + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, {}); } + last_hovered_tab = tab_id; } void EditorSceneTabs::_disable_menu_option_if(int p_option, bool p_condition) { @@ -204,6 +211,12 @@ void EditorSceneTabs::_disable_menu_option_if(int p_option, bool p_condition) { } } +void EditorSceneTabs::_custom_menu_option(int p_option) { + if (p_option >= EditorContextMenuPlugin::BASE_ID) { + EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, p_option, last_hovered_tab >= 0 ? EditorNode::get_editor_data().get_scene_path(last_hovered_tab) : String()); + } +} + void EditorSceneTabs::update_scene_tabs() { static bool menu_initialized = false; tab_preview_panel->hide(); @@ -410,6 +423,7 @@ EditorSceneTabs::EditorSceneTabs() { scene_tabs_context_menu = memnew(PopupMenu); tabbar_container->add_child(scene_tabs_context_menu); scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(false)); + scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorSceneTabs::_custom_menu_option)); scene_tab_add = memnew(Button); scene_tab_add->set_flat(true); diff --git a/editor/gui/editor_scene_tabs.h b/editor/gui/editor_scene_tabs.h index ac9e6b8c432..f9b94f34102 100644 --- a/editor/gui/editor_scene_tabs.h +++ b/editor/gui/editor_scene_tabs.h @@ -57,6 +57,8 @@ class EditorSceneTabs : public MarginContainer { Panel *tab_preview_panel = nullptr; TextureRect *tab_preview = nullptr; + int last_hovered_tab = -1; + void _scene_tab_changed(int p_tab); void _scene_tab_script_edited(int p_tab); void _scene_tab_closed(int p_tab); @@ -69,6 +71,7 @@ class EditorSceneTabs : public MarginContainer { void _reposition_active_tab(int p_to_index); void _update_context_menu(); void _disable_menu_option_if(int p_option, bool p_condition); + void _custom_menu_option(int p_option); void _tab_preview_done(const String &p_path, const Ref &p_preview, const Ref &p_small_preview, const Variant &p_udata); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 59775fe266d..17b55ba0fd6 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -43,6 +43,7 @@ #include "editor/gui/editor_toaster.h" #include "editor/gui/editor_zoom_widget.h" #include "editor/plugins/animation_player_editor_plugin.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/scene_tree_dock.h" #include "editor/themes/editor_scale.h" @@ -991,6 +992,19 @@ void CanvasItemEditor::_add_node_pressed(int p_result) { undo_redo->commit_action(); _reset_create_position(); } break; + default: { + if (p_result >= EditorContextMenuPlugin::BASE_ID) { + TypedArray nodes; + nodes.resize(selection_results.size()); + + int i = 0; + for (const _SelectResult &result : selection_results) { + nodes[i] = result.item; + i++; + } + EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, p_result, nodes); + } + } } } @@ -2461,6 +2475,21 @@ bool CanvasItemEditor::_gui_input_select(const Ref &p_event) { } } + // Context menu plugin receives paths of nodes under cursor. It's a complex operation, so perform it only when necessary. + if (EditorContextMenuPluginManager::get_singleton()->has_plugins_for_slot(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR)) { + selection_results.clear(); + _get_canvas_items_at_pos(transform.affine_inverse().xform(viewport->get_local_mouse_position()), selection_results, true); + + PackedStringArray paths; + paths.resize(selection_results.size()); + String *paths_write = paths.ptrw(); + + for (int i = 0; i < paths.size(); i++) { + paths_write[i] = selection_results[i].item->get_path(); + } + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(add_node_menu, EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, paths); + } + add_node_menu->reset_size(); add_node_menu->set_position(viewport->get_screen_transform().xform(b->get_position())); add_node_menu->popup(); diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp index b635816bd97..d35e4721ab3 100644 --- a/editor/plugins/editor_context_menu_plugin.cpp +++ b/editor/plugins/editor_context_menu_plugin.cpp @@ -87,8 +87,11 @@ void EditorContextMenuPlugin::_bind_methods() { BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE); BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM); - BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE); BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR); + BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE); + BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR_CODE); + BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TABS); + BIND_ENUM_CONSTANT(CONTEXT_SLOT_2D_EDITOR); } void EditorContextMenuPluginManager::add_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref &p_plugin) { @@ -106,6 +109,15 @@ void EditorContextMenuPluginManager::remove_plugin(const Ref &plugin : plugin_list) { + if (plugin->slot == p_slot) { + return true; + } + } + return false; +} + void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector &p_paths) { bool separator_added = false; const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)); diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h index 86c67dedda1..8f323239db8 100644 --- a/editor/plugins/editor_context_menu_plugin.h +++ b/editor/plugins/editor_context_menu_plugin.h @@ -52,6 +52,9 @@ class EditorContextMenuPlugin : public RefCounted { CONTEXT_SLOT_FILESYSTEM, CONTEXT_SLOT_SCRIPT_EDITOR, CONTEXT_SLOT_FILESYSTEM_CREATE, + CONTEXT_SLOT_SCRIPT_EDITOR_CODE, + CONTEXT_SLOT_SCENE_TABS, + CONTEXT_SLOT_2D_EDITOR, }; inline static constexpr int BASE_ID = 2000; @@ -100,6 +103,7 @@ class EditorContextMenuPluginManager : public Object { void add_plugin(ContextMenuSlot p_slot, const Ref &p_plugin); void remove_plugin(const Ref &p_plugin); + bool has_plugins_for_slot(ContextMenuSlot p_slot); void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector &p_paths); Callable match_custom_shortcut(ContextMenuSlot p_slot, const Ref &p_event); bool activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 94e3c8ba7b0..31a815f3332 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -41,6 +41,7 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/gui/editor_toaster.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/gui/menu_button.h" #include "scene/gui/rich_text_label.h" @@ -1721,6 +1722,11 @@ void ScriptTextEditor::_edit_option(int p_op) { _lookup_symbol(text, tx->get_caret_line(0), tx->get_caret_column(0)); } } break; + default: { + if (p_op >= EditorContextMenuPlugin::BASE_ID) { + EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR_CODE, p_op, tx); + } + } } } @@ -2310,6 +2316,9 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p } } + const PackedStringArray paths = { code_editor->get_text_editor()->get_path() }; + EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR_CODE, paths); + const CodeEdit *tx = code_editor->get_text_editor(); context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); From 06efe84bcae33c5bba48c6daf093e7882f0932ed Mon Sep 17 00:00:00 2001 From: Kiro Date: Sun, 1 Dec 2024 11:05:53 +0100 Subject: [PATCH 15/86] Remove duplicate `utf8()` calls --- core/io/pck_packer.cpp | 5 +++-- modules/gltf/gltf_document.cpp | 10 ++++++---- platform/linuxbsd/x11/display_server_x11.cpp | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index b9fe121ea6d..c7cfca190d7 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -198,11 +198,12 @@ Error PCKPacker::flush(bool p_verbose) { } for (int i = 0; i < files.size(); i++) { - int string_len = files[i].path.utf8().length(); + CharString utf8_string = files[i].path.utf8(); + int string_len = utf8_string.length(); int pad = _get_pad(4, string_len); fhead->store_32(uint32_t(string_len + pad)); - fhead->store_buffer((const uint8_t *)files[i].path.utf8().get_data(), string_len); + fhead->store_buffer((const uint8_t *)utf8_string.get_data(), string_len); for (int j = 0; j < pad; j++) { fhead->store_8(0); } diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index bfd21891299..ccca9c701bc 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -8235,11 +8235,10 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref p_state, Erro const int32_t header_size = 12; const int32_t chunk_header_size = 8; - int32_t padding = (chunk_header_size + json.utf8().length()) % 4; - json += String(" ").repeat(padding); - CharString cs = json.utf8(); - const uint32_t text_chunk_length = cs.length(); + int32_t padding = (chunk_header_size + cs.length()) % 4; + + const uint32_t text_chunk_length = cs.length() + padding; const uint32_t text_chunk_type = 0x4E4F534A; //JSON int32_t binary_data_length = 0; @@ -8257,6 +8256,9 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref p_state, Erro buffer->put_32(text_chunk_length); buffer->put_32(text_chunk_type); buffer->put_data((uint8_t *)&cs[0], cs.length()); + for (int i = 0; i < padding; i++) { + buffer->put_8(' '); + } if (binary_chunk_length) { buffer->put_32(binary_chunk_length); buffer->put_32(binary_chunk_type); diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index f8716c6f34c..ce081d75b27 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -1939,7 +1939,8 @@ void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false); Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false); if (_net_wm_name != None && utf8_string != None) { - XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length()); + CharString utf8_title = p_title.utf8(); + XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)utf8_title.get_data(), utf8_title.length()); } } From 1637736c209a8a84cf37a6bf93e4f35eaee401da Mon Sep 17 00:00:00 2001 From: landervr <31851431+CpnWaffle@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:50:06 +0100 Subject: [PATCH 16/86] ReflectionProbe priority --- .../forward_mobile/render_forward_mobile.cpp | 13 ++++++-- .../forward_mobile/render_forward_mobile.h | 8 +++++ .../scene_forward_clustered.glsl | 16 ++++++++-- .../forward_mobile/scene_forward_mobile.glsl | 19 ++++++++++-- .../shaders/scene_forward_lights_inc.glsl | 30 ++++++++----------- .../renderer_rd/storage_rd/light_storage.cpp | 7 +++-- .../renderer_rd/storage_rd/light_storage.h | 4 +-- 7 files changed, 68 insertions(+), 29 deletions(-) diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 20f8ef02c07..6db2a5b19c6 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -124,14 +124,23 @@ void RenderForwardMobile::fill_push_constant_instance_indices(SceneState::Instan p_instance_data->reflection_probes[0] = 0xFFFFFFFF; p_instance_data->reflection_probes[1] = 0xFFFFFFFF; + ForwardIDByMapSort sorted_reflection_probes[MAX_RDL_CULL]; + for (uint32_t i = 0; i < p_instance->reflection_probe_count; i++) { + sorted_reflection_probes[i].forward_id = p_instance->reflection_probes[i]; + sorted_reflection_probes[i].map = forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].map[p_instance->reflection_probes[i]]; + } + + SortArray sort_array; + sort_array.sort(sorted_reflection_probes, p_instance->reflection_probe_count); + idx = 0; for (uint32_t i = 0; i < p_instance->reflection_probe_count; i++) { uint32_t ofs = idx < 4 ? 0 : 1; uint32_t shift = (idx & 0x3) << 3; uint32_t mask = ~(0xFF << shift); - if (forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].last_pass[p_instance->reflection_probes[i]] == current_frame) { + if (forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].last_pass[sorted_reflection_probes[i].forward_id] == current_frame) { p_instance_data->reflection_probes[ofs] &= mask; - p_instance_data->reflection_probes[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].map[p_instance->reflection_probes[i]]) << shift; + p_instance_data->reflection_probes[ofs] |= uint32_t(forward_id_storage_mobile->forward_id_allocators[RendererRD::FORWARD_ID_TYPE_REFLECTION_PROBE].map[sorted_reflection_probes[i].forward_id]) << shift; idx++; } } diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index 0969dd0b508..1661d1f415c 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -529,6 +529,14 @@ class RenderForwardMobile : public RendererSceneRenderRD { return forward_id_storage_mobile; } + struct ForwardIDByMapSort { + uint8_t map; + RendererRD::ForwardID forward_id; + bool operator<(const ForwardIDByMapSort &p_sort) const { + return map > p_sort.map; + } + }; + public: static RenderForwardMobile *get_singleton() { return singleton; } diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index c14ea34c8f0..1a09261a326 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -1897,17 +1897,29 @@ void fragment_shader(in SceneData scene_data) { continue; //not masked } + if (reflection_accum.a >= 1.0 && ambient_accum.a >= 1.0) { + break; + } + reflection_process(reflection_index, vertex, ref_vec, normal, roughness, ambient_light, specular_light, ambient_accum, reflection_accum); } } + if (ambient_accum.a < 1.0) { + ambient_accum.rgb = mix(ambient_light, ambient_accum.rgb, ambient_accum.a); + } + + if (reflection_accum.a < 1.0) { + reflection_accum.rgb = mix(specular_light, reflection_accum.rgb, reflection_accum.a); + } + if (reflection_accum.a > 0.0) { - specular_light = reflection_accum.rgb / reflection_accum.a; + specular_light = reflection_accum.rgb; } #if !defined(USE_LIGHTMAP) if (ambient_accum.a > 0.0) { - ambient_light = ambient_accum.rgb / ambient_accum.a; + ambient_light = ambient_accum.rgb; } #endif } diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index a3c264b8236..cc98b64d480 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -1364,16 +1364,29 @@ void main() { uvec2 reflection_indices = instances.data[draw_call.instance_index].reflection_probes; for (uint i = 0; i < sc_reflection_probes(); i++) { uint reflection_index = (i > 3) ? ((reflection_indices.y >> ((i - 4) * 8)) & 0xFF) : ((reflection_indices.x >> (i * 8)) & 0xFF); - reflection_process(reflection_index, vertex, ref_vec, bent_normal, roughness, ambient_light, specular_light, ambient_accum, reflection_accum); + + if (reflection_accum.a >= 1.0 && ambient_accum.a >= 1.0) { + break; + } + + reflection_process(reflection_index, vertex, ref_vec, normal, roughness, ambient_light, specular_light, ambient_accum, reflection_accum); + } + + if (ambient_accum.a < 1.0) { + ambient_accum.rgb = mix(ambient_light, ambient_accum.rgb, ambient_accum.a); + } + + if (reflection_accum.a < 1.0) { + reflection_accum.rgb = mix(specular_light, reflection_accum.rgb, reflection_accum.a); } if (reflection_accum.a > 0.0) { - specular_light = reflection_accum.rgb / reflection_accum.a; + specular_light = reflection_accum.rgb; } #if !defined(USE_LIGHTMAP) if (ambient_accum.a > 0.0) { - ambient_light = ambient_accum.rgb / ambient_accum.a; + ambient_light = ambient_accum.rgb; } #endif } //Reflection probes diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index 5ea24bff715..fc26848784f 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -885,7 +885,7 @@ void reflection_process(uint ref_index, vec3 vertex, vec3 ref_vec, vec3 normal, blend = pow(blend_axes.x * blend_axes.y * blend_axes.z, 2.0); } - if (reflections.data[ref_index].intensity > 0.0) { // compute reflection + if (reflections.data[ref_index].intensity > 0.0 && reflection_accum.a < 1.0) { // compute reflection vec3 local_ref_vec = (reflections.data[ref_index].local_matrix * vec4(ref_vec, 0.0)).xyz; @@ -903,47 +903,43 @@ void reflection_process(uint ref_index, vec3 vertex, vec3 ref_vec, vec3 normal, } vec4 reflection; + float reflection_blend = max(0.0, blend - reflection_accum.a); reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), sqrt(roughness) * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier(); reflection.rgb *= reflections.data[ref_index].exposure_normalization; - if (reflections.data[ref_index].exterior) { - reflection.rgb = mix(specular_light, reflection.rgb, blend); - } + reflection.a = reflection_blend; - reflection.rgb *= reflections.data[ref_index].intensity; //intensity - reflection.a = blend; + reflection.rgb *= reflections.data[ref_index].intensity; reflection.rgb *= reflection.a; reflection_accum += reflection; } + if (ambient_accum.a >= 1.0) { + return; + } + switch (reflections.data[ref_index].ambient_mode) { case REFLECTION_AMBIENT_DISABLED: { //do nothing } break; case REFLECTION_AMBIENT_ENVIRONMENT: { - //do nothing vec3 local_amb_vec = (reflections.data[ref_index].local_matrix * vec4(normal, 0.0)).xyz; - vec4 ambient_out; + float ambient_blend = max(0.0, blend - ambient_accum.a); ambient_out.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_amb_vec, reflections.data[ref_index].index), MAX_ROUGHNESS_LOD).rgb; ambient_out.rgb *= reflections.data[ref_index].exposure_normalization; - ambient_out.a = blend; - if (reflections.data[ref_index].exterior) { - ambient_out.rgb = mix(ambient_light, ambient_out.rgb, blend); - } - + ambient_out.a = ambient_blend; ambient_out.rgb *= ambient_out.a; ambient_accum += ambient_out; } break; case REFLECTION_AMBIENT_COLOR: { vec4 ambient_out; - ambient_out.a = blend; + float ambient_blend = max(0.0, blend - ambient_accum.a); + ambient_out.rgb = reflections.data[ref_index].ambient; - if (reflections.data[ref_index].exterior) { - ambient_out.rgb = mix(ambient_light, ambient_out.rgb, blend); - } + ambient_out.a = ambient_blend; ambient_out.rgb *= ambient_out.a; ambient_accum += ambient_out; } break; diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp index abf673a1fa5..b291749e79f 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp @@ -1732,11 +1732,12 @@ void LightStorage::update_reflection_probe_buffer(RenderDataRD *p_render_data, c if (!rpi) { continue; } - - Transform3D transform = rpi->transform; + ReflectionProbe *probe = reflection_probe_owner.get_or_null(rpi->probe); + Vector3 extents = probe->size / 2; + float probe_size = extents.length(); reflection_sort[reflection_count].probe_instance = rpi; - reflection_sort[reflection_count].depth = -p_camera_inverse_transform.xform(transform.origin).z; + reflection_sort[reflection_count].size = -probe_size; reflection_count++; } diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h index b71c1d36fdd..73543ce6447 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -316,10 +316,10 @@ class LightStorage : public RendererLightStorage { }; struct ReflectionProbeInstanceSort { - float depth; + float size; ReflectionProbeInstance *probe_instance; bool operator<(const ReflectionProbeInstanceSort &p_sort) const { - return depth < p_sort.depth; + return size < p_sort.size; } }; From 637fe3ccdd826828414c85460c7652e6b42f5ace Mon Sep 17 00:00:00 2001 From: Lyuma Date: Tue, 24 Dec 2024 07:34:24 -0800 Subject: [PATCH 17/86] Allow post-import plugins to modify _subresources The old code fetched some data before the `EditorScenePostImportPlugin._pre_process` callback. While the callback could modify existing keys, this prevented users from adding new data on a fresh import. By fetching the keys after pre_process, this means users can consistently modify import options for nodes, meshes, materials and animations in a post-import plugin. --- doc/classes/EditorScenePostImportPlugin.xml | 1 + editor/import/3d/resource_importer_scene.cpp | 57 +++++++++++--------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/doc/classes/EditorScenePostImportPlugin.xml b/doc/classes/EditorScenePostImportPlugin.xml index 0004ec65c64..ddcbb757ad3 100644 --- a/doc/classes/EditorScenePostImportPlugin.xml +++ b/doc/classes/EditorScenePostImportPlugin.xml @@ -71,6 +71,7 @@ Pre Process the scene. This function is called right after the scene format loader loaded the scene and no changes have been made. + Pre process may be used to adjust internal import options in the [code]"nodes"[/code], [code]"meshes"[/code], [code]"animations"[/code] or [code]"materials"[/code] keys inside [code]get_option_value("_subresources")[/code]. diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 695c12150df..fe9e5801419 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -2934,38 +2934,22 @@ Error ResourceImporterScene::import(ResourceUID::ID p_source_id, const String &p Dictionary subresources = p_options["_subresources"]; - Dictionary node_data; - if (subresources.has("nodes")) { - node_data = subresources["nodes"]; - } - - Dictionary material_data; - if (subresources.has("materials")) { - material_data = subresources["materials"]; - } - - Dictionary animation_data; - if (subresources.has("animations")) { - animation_data = subresources["animations"]; - } - - Dictionary mesh_data; - if (subresources.has("meshes")) { - mesh_data = subresources["meshes"]; - } - Error err = OK; // Check whether any of the meshes or animations have nonexistent save paths // and if they do, fail the import immediately. - err = _check_resource_save_paths(mesh_data); - if (err != OK) { - return err; + if (subresources.has("meshes")) { + err = _check_resource_save_paths(subresources["meshes"]); + if (err != OK) { + return err; + } } - err = _check_resource_save_paths(animation_data); - if (err != OK) { - return err; + if (subresources.has("animations")) { + err = _check_resource_save_paths(subresources["animations"]); + if (err != OK) { + return err; + } } List missing_deps; // for now, not much will be done with this @@ -3005,6 +2989,27 @@ Error ResourceImporterScene::import(ResourceUID::ID p_source_id, const String &p post_importer_plugins.write[i]->pre_process(scene, p_options); } + // data in _subresources may be modified by pre_process(), so wait until now to check. + Dictionary node_data; + if (subresources.has("nodes")) { + node_data = subresources["nodes"]; + } + + Dictionary material_data; + if (subresources.has("materials")) { + material_data = subresources["materials"]; + } + + Dictionary animation_data; + if (subresources.has("animations")) { + animation_data = subresources["animations"]; + } + + Dictionary mesh_data; + if (subresources.has("meshes")) { + mesh_data = subresources["meshes"]; + } + float fps = 30; if (p_options.has(SNAME("animation/fps"))) { fps = (float)p_options[SNAME("animation/fps")]; From 38ff1500c790194846de0a7a472d61466bd9347c Mon Sep 17 00:00:00 2001 From: Michael Alexsander Date: Thu, 7 Nov 2024 12:24:14 -0300 Subject: [PATCH 18/86] Add toggle to hide filtered out parents in the "SceneTree" dock --- doc/classes/EditorSettings.xml | 3 + editor/debugger/editor_debugger_tree.cpp | 113 +++++++++++------ editor/debugger/editor_debugger_tree.h | 13 +- editor/editor_settings.cpp | 1 + editor/gui/scene_tree_editor.cpp | 152 +++++++++++++++++------ editor/gui/scene_tree_editor.h | 4 +- editor/scene_tree_dock.cpp | 15 ++- editor/scene_tree_dock.h | 2 +- 8 files changed, 223 insertions(+), 80 deletions(-) diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index e1c85d3595f..bd9f7a665ac 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -241,6 +241,9 @@ If [code]true[/code], new node created when reparenting node(s) will be positioned at the average position of the selected node(s). + + If [code]true[/code], the scene tree dock will only show nodes that match the filter, without showing parents that don't. This settings can also be changed in the Scene dock's top menu. + If [code]true[/code], the Create dialog (Create New Node/Create New Resource) will start with all its sections expanded. Otherwise, sections will be collapsed until the user starts searching (which will automatically expand sections as needed). diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp index a9e4adf674c..24c505aec4b 100644 --- a/editor/debugger/editor_debugger_tree.cpp +++ b/editor/debugger/editor_debugger_tree.cpp @@ -32,6 +32,7 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/gui/editor_file_dialog.h" #include "editor/scene_tree_dock.h" @@ -146,24 +147,50 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, Mou /// |-E /// void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) { + set_hide_root(false); + updating_scene_tree = true; const String last_path = get_selected_path(); const String filter = SceneTreeDock::get_singleton()->get_filter(); + TreeItem *select_item = nullptr; + bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"); + bool should_scroll = scrolling_to_item || filter != last_filter; scrolling_to_item = false; TreeItem *scroll_item = nullptr; // Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion. - List> parents; + List parents; for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) { TreeItem *parent = nullptr; + Pair move_from_to; if (parents.size()) { // Find last parent. - Pair &p = parents.front()->get(); - parent = p.first; - if (!(--p.second)) { // If no child left, remove it. + ParentItem &p = parents.front()->get(); + parent = p.tree_item; + if (!(--p.child_count)) { // If no child left, remove it. parents.pop_front(); + + if (hide_filtered_out_parents && !filter.is_subsequence_ofn(parent->get_text(0))) { + if (parent == get_root()) { + set_hide_root(true); + } else { + move_from_to.first = parent; + // Find the closest ancestor that matches the filter. + for (const ParentItem p2 : parents) { + move_from_to.second = p2.tree_item; + if (p2.matches_filter || move_from_to.second == get_root()) { + break; + } + } + + if (!move_from_to.second) { + move_from_to.second = get_root(); + } + } + } } } + // Add this node. TreeItem *item = create_item(parent); item->set_text(0, node.name); @@ -178,12 +205,17 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int } item->set_metadata(0, node.id); - // Set current item as collapsed if necessary (root is never collapsed). + String current_path; if (parent) { + current_path += (String)parent->get_meta("node_path"); + + // Set current item as collapsed if necessary (root is never collapsed). if (!unfold_cache.has(node.id)) { item->set_collapsed(true); } } + item->set_meta("node_path", current_path + "/" + item->get_text(0)); + // Select previously selected node. if (debugger_id == p_debugger) { // Can use remote id. if (node.id == inspected_object_id) { @@ -196,21 +228,18 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int updating_scene_tree = true; } - item->select(0); - + select_item = item; if (should_scroll) { scroll_item = item; } } - } else { // Must use path - if (last_path == _get_path(item)) { - updating_scene_tree = false; // Force emission of new selection. - item->select(0); - if (should_scroll) { - scroll_item = item; - } - updating_scene_tree = true; + } else if (last_path == (String)item->get_meta("node_path")) { // Must use path. + updating_scene_tree = false; // Force emission of new selection. + select_item = item; + if (should_scroll) { + scroll_item = item; } + updating_scene_tree = true; } // Add buttons. @@ -242,7 +271,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int // Add in front of the parents stack if children are expected. if (node.child_count) { - parents.push_front(Pair(item, node.child_count)); + parents.push_front(ParentItem(item, node.child_count, filter.is_subsequence_ofn(item->get_text(0)))); } else { // Apply filters. while (parent) { @@ -250,31 +279,60 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int if (filter.is_subsequence_ofn(item->get_text(0))) { break; // Filter matches, must survive. } + parent->remove_child(item); memdelete(item); - if (scroll_item == item) { + if (select_item == item || scroll_item == item) { + select_item = nullptr; scroll_item = nullptr; } + if (had_siblings) { break; // Parent must survive. } + item = parent; parent = item->get_parent(); // Check if parent expects more children. - for (const Pair &pair : parents) { - if (pair.first == item) { + for (ParentItem &pair : parents) { + if (pair.tree_item == item) { parent = nullptr; break; // Might have more children. } } } } + + // Move all children to the ancestor that matches the filter, if picked. + if (move_from_to.first) { + TreeItem *from = move_from_to.first; + TypedArray children = from->get_children(); + if (!children.is_empty()) { + for (Variant &c : children) { + TreeItem *ti = Object::cast_to(c); + from->remove_child(ti); + move_from_to.second->add_child(ti); + } + + from->get_parent()->remove_child(from); + memdelete(from); + if (select_item == from || scroll_item == from) { + select_item = nullptr; + scroll_item = nullptr; + } + } + } } debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree. + + if (select_item) { + select_item->select(0); + } if (scroll_item) { scroll_to_item(scroll_item, false); } + last_filter = filter; updating_scene_tree = false; } @@ -338,22 +396,7 @@ String EditorDebuggerTree::get_selected_path() { if (!get_selected()) { return ""; } - return _get_path(get_selected()); -} - -String EditorDebuggerTree::_get_path(TreeItem *p_item) { - ERR_FAIL_NULL_V(p_item, ""); - - if (p_item->get_parent() == nullptr) { - return "/root"; - } - String text = p_item->get_text(0); - TreeItem *cur = p_item->get_parent(); - while (cur) { - text = cur->get_text(0) + "/" + text; - cur = cur->get_parent(); - } - return "/" + text; + return get_selected()->get_meta("node_path"); } void EditorDebuggerTree::_item_menu_id_pressed(int p_option) { diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h index d048688cad2..46893d6dc3b 100644 --- a/editor/debugger/editor_debugger_tree.h +++ b/editor/debugger/editor_debugger_tree.h @@ -40,6 +40,18 @@ class EditorDebuggerTree : public Tree { GDCLASS(EditorDebuggerTree, Tree); private: + struct ParentItem { + TreeItem *tree_item; + int child_count; + bool matches_filter; + + ParentItem(TreeItem *p_tree_item = nullptr, int p_child_count = 0, bool p_matches_filter = false) { + tree_item = p_tree_item; + child_count = p_child_count; + matches_filter = p_matches_filter; + } + }; + enum ItemMenu { ITEM_MENU_SAVE_REMOTE_NODE, ITEM_MENU_COPY_NODE_PATH, @@ -56,7 +68,6 @@ class EditorDebuggerTree : public Tree { EditorFileDialog *file_dialog = nullptr; String last_filter; - String _get_path(TreeItem *p_item); void _scene_tree_folded(Object *p_obj); void _scene_tree_selected(); void _scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index bd886291bb9..aa77128fff7 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -633,6 +633,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { _initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false); _initial_set("docks/scene_tree/auto_expand_to_selected", true); _initial_set("docks/scene_tree/center_node_on_reparent", false); + _initial_set("docks/scene_tree/hide_filtered_out_parents", true); // FileSystem EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16") diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 6e65d4d8ee4..cf443d6f7b2 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -952,47 +952,60 @@ bool SceneTreeEditor::_update_filter(TreeItem *p_parent, bool p_scroll_to_select return false; } - bool keep_for_children = false; - for (TreeItem *child = p_parent->get_first_child(); child; child = child->get_next()) { - // Always keep if at least one of the children are kept. - keep_for_children = _update_filter(child, p_scroll_to_selected) || keep_for_children; - } - // Now find other reasons to keep this Node, too. PackedStringArray terms = filter.to_lower().split_spaces(); bool keep = _item_matches_all_terms(p_parent, terms); bool selectable = keep; - if (keep && !valid_types.is_empty()) { - selectable = false; + bool is_root = p_parent == tree->get_root(); + + if (keep) { Node *n = get_node(p_parent->get_metadata(0)); + if (!p_parent->is_visible() || (is_root && tree->is_root_hidden())) { + // Place back moved out children from when this item has hidden. + HashMap::Iterator I = node_cache.get(n, false); + if (I && I->value.has_moved_children) { + _update_node_subtree(I->value.node, nullptr, true); + } + } - for (const StringName &E : valid_types) { - if (n->is_class(E) || - EditorNode::get_singleton()->is_object_of_custom_type(n, E)) { - selectable = true; - break; - } else { - Ref