summaryrefslogtreecommitdiff
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/action_map_editor.cpp2
-rw-r--r--editor/animation_bezier_editor.cpp48
-rw-r--r--editor/animation_bezier_editor.h39
-rw-r--r--editor/code_editor.cpp28
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.cpp203
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.h16
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.cpp541
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.h17
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_server.cpp31
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_server.h2
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_types.h10
-rw-r--r--editor/debugger/editor_debugger_node.cpp11
-rw-r--r--editor/debugger/editor_debugger_node.h2
-rw-r--r--editor/debugger/editor_debugger_server.cpp17
-rw-r--r--editor/debugger/editor_debugger_server.h2
-rw-r--r--editor/debugger/script_editor_debugger.cpp14
-rw-r--r--editor/debugger/script_editor_debugger.h3
-rw-r--r--editor/doc_tools.cpp22
-rw-r--r--editor/editor_autoload_settings.cpp36
-rw-r--r--editor/editor_autoload_settings.h1
-rw-r--r--editor/editor_command_palette.cpp27
-rw-r--r--editor/editor_command_palette.h11
-rw-r--r--editor/editor_data.cpp15
-rw-r--r--editor/editor_data.h7
-rw-r--r--editor/editor_export.cpp11
-rw-r--r--editor/editor_file_dialog.cpp2
-rw-r--r--editor/editor_file_system.cpp87
-rw-r--r--editor/editor_file_system.h2
-rw-r--r--editor/editor_fonts.cpp400
-rw-r--r--editor/editor_help.cpp36
-rw-r--r--editor/editor_inspector.cpp1408
-rw-r--r--editor/editor_inspector.h148
-rw-r--r--editor/editor_log.cpp2
-rw-r--r--editor/editor_node.cpp117
-rw-r--r--editor/editor_node.h13
-rw-r--r--editor/editor_plugin.cpp1
-rw-r--r--editor/editor_properties.cpp351
-rw-r--r--editor/editor_properties.h32
-rw-r--r--editor/editor_run_native.cpp18
-rw-r--r--editor/editor_run_native.h3
-rw-r--r--editor/editor_spin_slider.cpp8
-rw-r--r--editor/editor_themes.cpp29
-rw-r--r--editor/filesystem_dock.cpp15
-rw-r--r--editor/icons/AnimatableBody2D.svg1
-rw-r--r--editor/icons/AnimatableBody3D.svg1
-rw-r--r--editor/icons/GuiCheckedDisabled.svg1
-rw-r--r--editor/icons/GuiRadioCheckedDisabled.svg1
-rw-r--r--editor/icons/GuiRadioUncheckedDisabled.svg1
-rw-r--r--editor/icons/GuiSpinboxUpdownDisabled.svg1
-rw-r--r--editor/icons/GuiUncheckedDisabled.svg1
-rw-r--r--editor/icons/Listener2D.svg1
-rw-r--r--editor/icons/PageFirst.svg47
-rw-r--r--editor/icons/PageLast.svg47
-rw-r--r--editor/icons/PageNext.svg42
-rw-r--r--editor/icons/PagePrevious.svg42
-rw-r--r--editor/icons/SeparationRayShape2D.svg1
-rw-r--r--editor/icons/SeparationRayShape3D.svg1
-rw-r--r--editor/import/collada.cpp4
-rw-r--r--editor/import/dynamicfont_import_settings.cpp1889
-rw-r--r--editor/import/dynamicfont_import_settings.h167
-rw-r--r--editor/import/resource_importer_bmfont.cpp797
-rw-r--r--editor/import/resource_importer_bmfont.h56
-rw-r--r--editor/import/resource_importer_dynamicfont.cpp304
-rw-r--r--editor/import/resource_importer_dynamicfont.h71
-rw-r--r--editor/import/resource_importer_imagefont.cpp162
-rw-r--r--editor/import/resource_importer_imagefont.h58
-rw-r--r--editor/import/resource_importer_scene.cpp195
-rw-r--r--editor/import/resource_importer_scene.h202
-rw-r--r--editor/import/resource_importer_shader_file.cpp2
-rw-r--r--editor/import/resource_importer_texture.cpp2
-rw-r--r--editor/import/scene_import_settings.cpp74
-rw-r--r--editor/import/scene_import_settings.h4
-rw-r--r--editor/import/scene_importer_mesh.cpp80
-rw-r--r--editor/import/scene_importer_mesh.h9
-rw-r--r--editor/multi_node_edit.cpp13
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp2
-rw-r--r--editor/plugins/animation_player_editor_plugin.h5
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp5
-rw-r--r--editor/plugins/collision_shape_2d_editor_plugin.cpp41
-rw-r--r--editor/plugins/collision_shape_2d_editor_plugin.h1
-rw-r--r--editor/plugins/editor_preview_plugins.cpp63
-rw-r--r--editor/plugins/font_editor_plugin.cpp247
-rw-r--r--editor/plugins/font_editor_plugin.h33
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp3
-rw-r--r--editor/plugins/mesh_library_editor_plugin.cpp59
-rw-r--r--editor/plugins/mesh_library_editor_plugin.h14
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp65
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/packed_scene_translation_parser_plugin.cpp49
-rw-r--r--editor/plugins/packed_scene_translation_parser_plugin.h4
-rw-r--r--editor/plugins/script_editor_plugin.cpp32
-rw-r--r--editor/plugins/script_editor_plugin.h4
-rw-r--r--editor/plugins/script_text_editor.cpp15
-rw-r--r--editor/plugins/script_text_editor.h2
-rw-r--r--editor/plugins/skeleton_2d_editor_plugin.cpp5
-rw-r--r--editor/plugins/text_editor.h2
-rw-r--r--editor/plugins/tiles/tile_atlas_view.cpp6
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp32
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp116
-rw-r--r--editor/plugins/tiles/tile_map_editor.h2
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp2
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp225
-rw-r--r--editor/plugins/tiles/tile_set_editor.h1
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp3
-rw-r--r--editor/pot_generator.cpp13
-rw-r--r--editor/project_manager.cpp4
-rw-r--r--editor/project_settings_editor.cpp1
-rw-r--r--editor/rename_dialog.cpp12
-rw-r--r--editor/rename_dialog.h7
-rw-r--r--editor/scene_tree_dock.cpp19
-rw-r--r--editor/scene_tree_dock.h5
-rw-r--r--editor/scene_tree_editor.cpp8
-rw-r--r--editor/translations/el.po2
113 files changed, 7805 insertions, 1371 deletions
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp
index 7aa63f899b..6789b5be00 100644
--- a/editor/action_map_editor.cpp
+++ b/editor/action_map_editor.cpp
@@ -248,10 +248,8 @@ void InputEventConfigurationDialog::_listen_window_input(const Ref<InputEvent> &
k->set_pressed(false); // to avoid serialisation of 'pressed' property - doesn't matter for actions anyway.
// Maintain physical keycode option state
if (physical_key_checkbox->is_pressed()) {
- k->set_physical_keycode(k->get_keycode());
k->set_keycode(KEY_NONE);
} else {
- k->set_keycode((Key)k->get_physical_keycode());
k->set_physical_keycode(KEY_NONE);
}
}
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index bf7d808d50..fca69f34f3 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -217,6 +217,8 @@ void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const V
void AnimationBezierTrackEdit::_notification(int p_what) {
if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
+ close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons")));
+
bezier_icon = get_theme_icon(SNAME("KeyBezierPoint"), SNAME("EditorIcons"));
bezier_handle_icon = get_theme_icon(SNAME("KeyBezierHandle"), SNAME("EditorIcons"));
selected_icon = get_theme_icon(SNAME("KeyBezierSelected"), SNAME("EditorIcons"));
@@ -231,8 +233,8 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
int hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList"));
int vsep = get_theme_constant(SNAME("vseparation"), SNAME("ItemList"));
- handle_mode_option->set_position(Vector2(right_limit + hsep, get_size().height - handle_mode_option->get_combined_minimum_size().height - vsep));
- handle_mode_option->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, handle_mode_option->get_combined_minimum_size().height));
+ right_column->set_position(Vector2(right_limit + hsep, vsep));
+ right_column->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, get_size().y - vsep * 2));
}
if (p_what == NOTIFICATION_DRAW) {
if (animation.is_null()) {
@@ -261,12 +263,6 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor, Math::round(EDSCALE));
- Ref<Texture2D> close_icon = get_theme_icon(SNAME("Close"), SNAME("EditorIcons"));
-
- close_icon_rect.position = Vector2(get_size().width - close_icon->get_width() - hsep, hsep);
- close_icon_rect.size = close_icon->get_size();
- draw_texture(close_icon, close_icon_rect.position);
-
String base_path = animation->track_get_path(track);
int end = base_path.find(":");
if (end != -1) {
@@ -1126,10 +1122,6 @@ void AnimationBezierTrackEdit::delete_selection() {
}
}
-void AnimationBezierTrackEdit::set_block_animation_update_ptr(bool *p_block_ptr) {
- block_animation_update_ptr = p_block_ptr;
-}
-
void AnimationBezierTrackEdit::_bind_methods() {
ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection);
ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection_for_anim);
@@ -1150,21 +1142,6 @@ void AnimationBezierTrackEdit::_bind_methods() {
}
AnimationBezierTrackEdit::AnimationBezierTrackEdit() {
- undo_redo = nullptr;
- timeline = nullptr;
- root = nullptr;
- menu = nullptr;
- block_animation_update_ptr = nullptr;
-
- moving_selection_attempt = false;
- moving_selection = false;
- select_single_attempt = -1;
- box_selecting = false;
- box_selecting_attempt = false;
-
- moving_handle = 0;
-
- play_position_pos = 0;
play_position = memnew(Control);
play_position->set_mouse_filter(MOUSE_FILTER_PASS);
add_child(play_position);
@@ -1172,18 +1149,21 @@ AnimationBezierTrackEdit::AnimationBezierTrackEdit() {
play_position->connect("draw", callable_mp(this, &AnimationBezierTrackEdit::_play_position_draw));
set_focus_mode(FOCUS_CLICK);
- v_scroll = 0;
- v_zoom = 1;
-
- panning_timeline = false;
set_clip_contents(true);
handle_mode = HANDLE_MODE_FREE;
handle_mode_option = memnew(OptionButton);
- add_child(handle_mode_option);
+
+ close_button = memnew(Button);
+ close_button->connect("pressed", Callable(this, SNAME("emit_signal")), varray(SNAME("close_request")));
+ close_button->set_text(TTR("Close"));
+
+ right_column = memnew(VBoxContainer);
+ right_column->add_child(close_button);
+ right_column->add_spacer();
+ right_column->add_child(handle_mode_option);
+ add_child(right_column);
menu = memnew(PopupMenu);
add_child(menu);
menu->connect("id_pressed", callable_mp(this, &AnimationBezierTrackEdit::_menu_selected));
-
- //set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection
}
diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h
index a4a662ebcb..578c6f9337 100644
--- a/editor/animation_bezier_editor.h
+++ b/editor/animation_bezier_editor.h
@@ -51,11 +51,14 @@ class AnimationBezierTrackEdit : public Control {
HandleMode handle_mode;
OptionButton *handle_mode_option;
- AnimationTimelineEdit *timeline;
- UndoRedo *undo_redo;
- Node *root;
+ VBoxContainer *right_column;
+ Button *close_button;
+
+ AnimationTimelineEdit *timeline = nullptr;
+ UndoRedo *undo_redo = nullptr;
+ Node *root = nullptr;
Control *play_position; //separate control used to draw so updates for only position changed are much faster
- float play_position_pos;
+ float play_position_pos = 0;
Ref<Animation> animation;
int track;
@@ -70,37 +73,35 @@ class AnimationBezierTrackEdit : public Control {
Map<int, Rect2> subtracks;
- float v_scroll;
- float v_zoom;
+ float v_scroll = 0;
+ float v_zoom = 1;
- PopupMenu *menu;
+ PopupMenu *menu = nullptr;
void _zoom_changed();
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _menu_selected(int p_index);
- bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up)
-
void _play_position_draw();
Vector2 insert_at_pos;
- bool moving_selection_attempt;
- int select_single_attempt;
- bool moving_selection;
+ bool moving_selection_attempt = false;
+ int select_single_attempt = -1;
+ bool moving_selection = false;
int moving_selection_from_key;
Vector2 moving_selection_offset;
- bool box_selecting_attempt;
- bool box_selecting;
- bool box_selecting_add;
+ bool box_selecting_attempt = false;
+ bool box_selecting = false;
+ bool box_selecting_add = false;
Vector2 box_selection_from;
Vector2 box_selection_to;
- int moving_handle; //0 no move -1 or +1 out
- int moving_handle_key;
+ int moving_handle = 0; //0 no move -1 or +1 out
+ int moving_handle_key = 0;
Vector2 moving_handle_left;
Vector2 moving_handle_right;
@@ -129,7 +130,7 @@ class AnimationBezierTrackEdit : public Control {
Set<int> selection;
- bool panning_timeline;
+ bool panning_timeline = false;
float panning_timeline_from;
float panning_timeline_at;
@@ -155,8 +156,6 @@ public:
void set_editor(AnimationTrackEditor *p_editor);
void set_root(Node *p_root);
- void set_block_animation_update_ptr(bool *p_block_ptr);
-
void set_play_position(float p_pos);
void update_play_position();
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 89c2e49814..5599076c40 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -715,7 +715,27 @@ void CodeTextEditor::input(const Ref<InputEvent> &event) {
ERR_FAIL_COND(event.is_null());
const Ref<InputEventKey> key_event = event;
- if (!key_event.is_valid() || !key_event->is_pressed() || !text_editor->has_focus()) {
+
+ if (!key_event.is_valid()) {
+ return;
+ }
+ if (!key_event->is_pressed()) {
+ return;
+ }
+
+ if (!text_editor->has_focus()) {
+ if ((find_replace_bar != nullptr && find_replace_bar->is_visible()) && (find_replace_bar->has_focus() || find_replace_bar->is_ancestor_of(get_focus_owner()))) {
+ if (ED_IS_SHORTCUT("script_text_editor/find_next", key_event)) {
+ find_replace_bar->search_next();
+ accept_event();
+ return;
+ }
+ if (ED_IS_SHORTCUT("script_text_editor/find_previous", key_event)) {
+ find_replace_bar->search_prev();
+ accept_event();
+ return;
+ }
+ }
return;
}
@@ -1282,7 +1302,9 @@ void CodeTextEditor::_delete_line(int p_line) {
text_editor->set_caret_column(0);
}
text_editor->backspace();
- text_editor->unfold_line(p_line);
+ if (p_line < text_editor->get_line_count()) {
+ text_editor->unfold_line(p_line);
+ }
text_editor->set_caret_line(p_line);
}
@@ -1735,7 +1757,7 @@ void CodeTextEditor::goto_prev_bookmark() {
text_editor->set_caret_line(bmarks[bmarks.size() - 1]);
text_editor->center_viewport_to_caret();
} else {
- for (int i = bmarks.size(); i >= 0; i--) {
+ for (int i = bmarks.size() - 1; i >= 0; i--) {
int bmark_line = bmarks[i];
if (bmark_line < line) {
text_editor->unfold_line(bmark_line);
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
index 945291b163..fbd3aaa409 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
@@ -33,12 +33,15 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_node.h"
+#include "editor/editor_run_native.h"
void DebugAdapterParser::_bind_methods() {
// Requests
ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize);
- ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::prepare_success_response);
+ ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect);
ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch);
+ ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach);
+ ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart);
ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate);
ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response);
ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause);
@@ -46,10 +49,13 @@ void DebugAdapterParser::_bind_methods() {
ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads);
ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace);
ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints);
+ ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations);
ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes);
ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables);
ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next);
ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn);
+ ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate);
+ ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg);
}
Dictionary DebugAdapterParser::prepare_base_event() const {
@@ -80,13 +86,31 @@ Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params
DAP::Message message;
String error, error_desc;
switch (err_type) {
+ case DAP::ErrorType::WRONG_PATH:
+ error = "wrong_path";
+ error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"";
+ break;
+ case DAP::ErrorType::NOT_RUNNING:
+ error = "not_running";
+ error_desc = "Can't attach to a running session since there isn't one.";
+ break;
+ case DAP::ErrorType::TIMEOUT:
+ error = "timeout";
+ error_desc = "Timeout reached while processing a request.";
+ break;
+ case DAP::ErrorType::UNKNOWN_PLATFORM:
+ error = "unknown_platform";
+ error_desc = "The specified platform is unknown.";
+ break;
+ case DAP::ErrorType::MISSING_DEVICE:
+ error = "missing_device";
+ error_desc = "There's no connected device with specified id.";
+ break;
case DAP::ErrorType::UNKNOWN:
+ default:
error = "unknown";
error_desc = "An unknown error has ocurred when processing the request.";
break;
- case DAP::ErrorType::WRONG_PATH:
- error = "wrong_path";
- error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"";
}
message.id = err_type;
@@ -114,10 +138,35 @@ Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const
DebugAdapterProtocol::get_singleton()->notify_initialized();
+ if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) {
+ // Send all current breakpoints
+ List<String> breakpoints;
+ ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
+ for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
+ String breakpoint = E->get();
+
+ String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://"
+ int line = breakpoint.substr(path.size()).to_int();
+
+ DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true);
+ }
+ } else {
+ // Remove all current breakpoints
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints();
+ }
+
return response;
}
-Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) {
+Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const {
+ if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) {
+ EditorNode::get_singleton()->run_stop();
+ }
+
+ return prepare_success_response(p_params);
+}
+
+Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const {
Dictionary args = p_params["arguments"];
if (args.has("project") && !is_valid_path(args["project"])) {
Dictionary variables;
@@ -126,17 +175,85 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) {
return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables);
}
+ if (args.has("godot/custom_data")) {
+ DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"];
+ }
+
ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) {
dbg->debug_skip_breakpoints();
}
- EditorNode::get_singleton()->run_play();
+ String platform_string = args.get("platform", "host");
+ if (platform_string == "host") {
+ EditorNode::get_singleton()->run_play();
+ } else {
+ int device = args.get("device", -1);
+ int idx = -1;
+ if (platform_string == "android") {
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
+ if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") {
+ idx = i;
+ break;
+ }
+ }
+ } else if (platform_string == "web") {
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
+ if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "HTML5") {
+ idx = i;
+ break;
+ }
+ }
+ }
+
+ if (idx == -1) {
+ return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM);
+ }
+
+ EditorNode *editor = EditorNode::get_singleton();
+ Error err = platform_string == "android" ? editor->run_play_native(device, idx) : editor->run_play_native(-1, idx);
+ if (err) {
+ if (err == ERR_INVALID_PARAMETER && platform_string == "android") {
+ return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE);
+ } else {
+ return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
+ }
+ }
+ }
+
+ DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false;
DebugAdapterProtocol::get_singleton()->notify_process();
return prepare_success_response(p_params);
}
+Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const {
+ ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
+ if (!dbg->is_session_active()) {
+ return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING);
+ }
+
+ DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true;
+ DebugAdapterProtocol::get_singleton()->notify_process();
+ return prepare_success_response(p_params);
+}
+
+Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const {
+ // Extract embedded "arguments" so it can be given to req_launch/req_attach
+ Dictionary params = p_params, args;
+ args = params["arguments"];
+ args = args["arguments"];
+ params["arguments"] = args;
+
+ Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params);
+ if (!response["success"]) {
+ response["command"] = p_params["command"];
+ return response;
+ }
+
+ return prepare_success_response(p_params);
+}
+
Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const {
EditorNode::get_singleton()->run_stop();
@@ -205,7 +322,7 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const
return response;
}
-Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) {
+Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
@@ -230,14 +347,30 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) {
lines.push_back(breakpoint.line + !lines_at_one);
}
- EditorDebuggerNode::get_singleton()->set_breakpoints(ProjectSettings::get_singleton()->localize_path(source.path), lines);
Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines);
body["breakpoints"] = updated_breakpoints;
return response;
}
-Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) {
+Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const {
+ Dictionary response = prepare_success_response(p_params), body;
+ response["body"] = body;
+ Dictionary args = p_params["arguments"];
+
+ Array locations;
+ DAP::BreakpointLocation location;
+ location.line = args["line"];
+ if (args.has("endLine")) {
+ location.endLine = args["endLine"];
+ }
+ locations.push_back(location.to_json());
+
+ body["breakpoints"] = locations;
+ return response;
+}
+
+Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
@@ -291,7 +424,14 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
int variable_id = args["variablesReference"];
Map<int, Array>::Element *E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id);
+
if (E) {
+ if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
+ for (int i = 0; i < E->value().size(); i++) {
+ Dictionary variable = E->value()[i];
+ variable.erase("type");
+ }
+ }
body["variables"] = E ? E->value() : Array();
return response;
} else {
@@ -313,6 +453,29 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
return prepare_success_response(p_params);
}
+Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
+ Dictionary response = prepare_success_response(p_params), body;
+ response["body"] = body;
+
+ Dictionary args = p_params["arguments"];
+
+ String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
+ body["result"] = value;
+
+ return response;
+}
+
+Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
+ Dictionary args = p_params["arguments"];
+
+ String msg = args["message"];
+ Array data = args["data"];
+
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data);
+
+ return prepare_success_response(p_params);
+}
+
Dictionary DebugAdapterParser::ev_initialized() const {
Dictionary event = prepare_base_event();
event["event"] = "initialized";
@@ -423,3 +586,25 @@ Dictionary DebugAdapterParser::ev_output(const String &p_message) const {
return event;
}
+
+Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const {
+ Dictionary event = prepare_base_event(), body;
+ event["event"] = "breakpoint";
+ event["body"] = body;
+
+ body["reason"] = p_enabled ? "new" : "removed";
+ body["breakpoint"] = p_breakpoint.to_json();
+
+ return event;
+}
+
+Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const {
+ Dictionary event = prepare_base_event(), body;
+ event["event"] = "godot/custom_data";
+ event["body"] = body;
+
+ body["message"] = p_msg;
+ body["data"] = p_data;
+
+ return event;
+}
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h
index b86b37d067..4c93464e39 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.h
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.h
@@ -44,7 +44,7 @@ class DebugAdapterParser : public Object {
private:
friend DebugAdapterProtocol;
- _FORCE_INLINE_ bool is_valid_path(const String &p_path) {
+ _FORCE_INLINE_ bool is_valid_path(const String &p_path) const {
return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path());
}
@@ -60,17 +60,23 @@ protected:
public:
// Requests
Dictionary req_initialize(const Dictionary &p_params) const;
- Dictionary req_launch(const Dictionary &p_params);
+ Dictionary req_launch(const Dictionary &p_params) const;
+ Dictionary req_disconnect(const Dictionary &p_params) const;
+ Dictionary req_attach(const Dictionary &p_params) const;
+ Dictionary req_restart(const Dictionary &p_params) const;
Dictionary req_terminate(const Dictionary &p_params) const;
Dictionary req_pause(const Dictionary &p_params) const;
Dictionary req_continue(const Dictionary &p_params) const;
Dictionary req_threads(const Dictionary &p_params) const;
Dictionary req_stackTrace(const Dictionary &p_params) const;
- Dictionary req_setBreakpoints(const Dictionary &p_params);
- Dictionary req_scopes(const Dictionary &p_params);
+ Dictionary req_setBreakpoints(const Dictionary &p_params) const;
+ Dictionary req_breakpointLocations(const Dictionary &p_params) const;
+ Dictionary req_scopes(const Dictionary &p_params) const;
Dictionary req_variables(const Dictionary &p_params) const;
Dictionary req_next(const Dictionary &p_params) const;
Dictionary req_stepIn(const Dictionary &p_params) const;
+ Dictionary req_evaluate(const Dictionary &p_params) const;
+ Dictionary req_godot_put_msg(const Dictionary &p_params) const;
// Events
Dictionary ev_initialized() const;
@@ -83,6 +89,8 @@ public:
Dictionary ev_stopped_step() const;
Dictionary ev_continued() const;
Dictionary ev_output(const String &p_message) const;
+ Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const;
+ Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const;
};
#endif
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
index 0482271432..2e0e6cb7c8 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
@@ -94,11 +94,17 @@ Error DAPeer::handle_data() {
String msg;
msg.parse_utf8((const char *)req_buf, req_pos);
+ // Apply a timestamp if it there's none yet
+ if (!timestamp) {
+ timestamp = OS::get_singleton()->get_ticks_msec();
+ }
+
// Response
if (DebugAdapterProtocol::get_singleton()->process_message(msg)) {
// Reset to read again
req_pos = 0;
has_header = false;
+ timestamp = 0;
}
}
return OK;
@@ -180,12 +186,460 @@ void DebugAdapterProtocol::reset_stack_info() {
variable_list.clear();
}
+int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
+ switch (p_var.get_type()) {
+ case Variant::VECTOR2:
+ case Variant::VECTOR2I: {
+ int id = variable_id++;
+ Vector2 vec = p_var;
+ DAP::Variable x, y;
+ x.name = "x";
+ y.name = "y";
+ x.type = y.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT);
+ x.value = rtos(vec.x);
+ y.value = rtos(vec.y);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::RECT2:
+ case Variant::RECT2I: {
+ int id = variable_id++;
+ Rect2 rect = p_var;
+ DAP::Variable x, y, w, h;
+ x.name = "x";
+ y.name = "y";
+ w.name = "w";
+ h.name = "h";
+ x.type = y.type = w.type = h.type = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT);
+ x.value = rtos(rect.position.x);
+ y.value = rtos(rect.position.y);
+ w.value = rtos(rect.size.x);
+ h.value = rtos(rect.size.y);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(w.to_json());
+ arr.push_back(h.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::VECTOR3:
+ case Variant::VECTOR3I: {
+ int id = variable_id++;
+ Vector3 vec = p_var;
+ DAP::Variable x, y, z;
+ x.name = "x";
+ y.name = "y";
+ z.name = "z";
+ x.type = y.type = z.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT);
+ x.value = rtos(vec.x);
+ y.value = rtos(vec.y);
+ z.value = rtos(vec.z);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(z.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::TRANSFORM2D: {
+ int id = variable_id++;
+ Transform2D transform = p_var;
+ DAP::Variable x, y, origin;
+ x.name = "x";
+ y.name = "y";
+ origin.name = "origin";
+ x.type = y.type = origin.type = Variant::get_type_name(Variant::VECTOR2);
+ x.value = transform.elements[0];
+ y.value = transform.elements[1];
+ origin.value = transform.elements[2];
+ x.variablesReference = parse_variant(transform.elements[0]);
+ y.variablesReference = parse_variant(transform.elements[1]);
+ origin.variablesReference = parse_variant(transform.elements[2]);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(origin.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PLANE: {
+ int id = variable_id++;
+ Plane plane = p_var;
+ DAP::Variable d, normal;
+ d.name = "d";
+ normal.name = "normal";
+ d.type = Variant::get_type_name(Variant::FLOAT);
+ normal.type = Variant::get_type_name(Variant::VECTOR3);
+ d.value = rtos(plane.d);
+ normal.value = plane.normal;
+ normal.variablesReference = parse_variant(plane.normal);
+
+ Array arr;
+ arr.push_back(d.to_json());
+ arr.push_back(normal.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::QUATERNION: {
+ int id = variable_id++;
+ Quaternion quat = p_var;
+ DAP::Variable x, y, z, w;
+ x.name = "x";
+ y.name = "y";
+ z.name = "z";
+ w.name = "w";
+ x.type = y.type = z.type = w.type = Variant::get_type_name(Variant::FLOAT);
+ x.value = rtos(quat.x);
+ y.value = rtos(quat.y);
+ z.value = rtos(quat.z);
+ w.value = rtos(quat.w);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(z.to_json());
+ arr.push_back(w.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::AABB: {
+ int id = variable_id++;
+ AABB aabb = p_var;
+ DAP::Variable position, size;
+ position.name = "position";
+ size.name = "size";
+ position.type = size.type = Variant::get_type_name(Variant::VECTOR3);
+ position.value = aabb.position;
+ size.value = aabb.size;
+ position.variablesReference = parse_variant(aabb.position);
+ size.variablesReference = parse_variant(aabb.size);
+
+ Array arr;
+ arr.push_back(position.to_json());
+ arr.push_back(size.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::BASIS: {
+ int id = variable_id++;
+ Basis basis = p_var;
+ DAP::Variable x, y, z;
+ x.name = "x";
+ y.name = "y";
+ z.name = "z";
+ x.type = y.type = z.type = Variant::get_type_name(Variant::VECTOR2);
+ x.value = basis.elements[0];
+ y.value = basis.elements[1];
+ z.value = basis.elements[2];
+ x.variablesReference = parse_variant(basis.elements[0]);
+ y.variablesReference = parse_variant(basis.elements[1]);
+ z.variablesReference = parse_variant(basis.elements[2]);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(z.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::TRANSFORM3D: {
+ int id = variable_id++;
+ Transform3D transform = p_var;
+ DAP::Variable basis, origin;
+ basis.name = "basis";
+ origin.name = "origin";
+ basis.type = Variant::get_type_name(Variant::BASIS);
+ origin.type = Variant::get_type_name(Variant::VECTOR3);
+ basis.value = transform.basis;
+ origin.value = transform.origin;
+ basis.variablesReference = parse_variant(transform.basis);
+ origin.variablesReference = parse_variant(transform.origin);
+
+ Array arr;
+ arr.push_back(basis.to_json());
+ arr.push_back(origin.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::COLOR: {
+ int id = variable_id++;
+ Color color = p_var;
+ DAP::Variable r, g, b, a;
+ r.name = "r";
+ g.name = "g";
+ b.name = "b";
+ a.name = "a";
+ r.type = g.type = b.type = a.type = Variant::get_type_name(Variant::FLOAT);
+ r.value = rtos(color.r);
+ g.value = rtos(color.g);
+ b.value = rtos(color.b);
+ a.value = rtos(color.a);
+
+ Array arr;
+ arr.push_back(r.to_json());
+ arr.push_back(g.to_json());
+ arr.push_back(b.to_json());
+ arr.push_back(a.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::ARRAY: {
+ int id = variable_id++;
+ Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(array[i].get_type());
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::DICTIONARY: {
+ int id = variable_id++;
+ Dictionary dictionary = p_var;
+ Array arr;
+
+ for (int i = 0; i < dictionary.size(); i++) {
+ DAP::Variable var;
+ var.name = dictionary.get_key_at_index(i);
+ Variant value = dictionary.get_value_at_index(i);
+ var.type = Variant::get_type_name(value.get_type());
+ var.value = value;
+ var.variablesReference = parse_variant(value);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_BYTE_ARRAY: {
+ int id = variable_id++;
+ PackedByteArray array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "byte";
+ var.value = itos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_INT32_ARRAY: {
+ int id = variable_id++;
+ PackedInt32Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "int";
+ var.value = itos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_INT64_ARRAY: {
+ int id = variable_id++;
+ PackedInt64Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "long";
+ var.value = itos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ int id = variable_id++;
+ PackedFloat32Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "float";
+ var.value = rtos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_FLOAT64_ARRAY: {
+ int id = variable_id++;
+ PackedFloat64Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "double";
+ var.value = rtos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_STRING_ARRAY: {
+ int id = variable_id++;
+ PackedStringArray array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::STRING);
+ var.value = array[i];
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_VECTOR2_ARRAY: {
+ int id = variable_id++;
+ PackedVector2Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::VECTOR2);
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_VECTOR3_ARRAY: {
+ int id = variable_id++;
+ PackedVector2Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::VECTOR3);
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_COLOR_ARRAY: {
+ int id = variable_id++;
+ PackedColorArray array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::COLOR);
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ default:
+ // Simple atomic stuff, or too complex to be manipulated
+ return 0;
+ }
+}
+
bool DebugAdapterProtocol::process_message(const String &p_text) {
JSON json;
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Mal-formed message!");
Dictionary params = json.get_data();
bool completed = true;
+ if (OS::get_singleton()->get_ticks_msec() - _current_peer->timestamp > _request_timeout) {
+ Dictionary response = parser->prepare_error_response(params, DAP::ErrorType::TIMEOUT);
+ _current_peer->res_queue.push_front(response);
+ return true;
+ }
+
// Append "req_" to any command received; prevents name clash with existing functions, and possibly exploiting
String command = "req_" + (String)params["command"];
if (parser->has_method(command)) {
@@ -211,7 +665,7 @@ void DebugAdapterProtocol::notify_initialized() {
}
void DebugAdapterProtocol::notify_process() {
- String launch_mode = _current_request.is_empty() ? "launch" : _current_request;
+ String launch_mode = _current_peer->attached ? "attach" : "launch";
Dictionary event = parser->ev_process(launch_mode);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
@@ -222,7 +676,7 @@ void DebugAdapterProtocol::notify_process() {
void DebugAdapterProtocol::notify_terminated() {
Dictionary event = parser->ev_terminated();
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
- if (_current_request == "launch" && _current_peer == E->get()) {
+ if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) {
continue;
}
E->get()->res_queue.push_back(event);
@@ -232,7 +686,7 @@ void DebugAdapterProtocol::notify_terminated() {
void DebugAdapterProtocol::notify_exited(const int &p_exitcode) {
Dictionary event = parser->ev_exited(p_exitcode);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
- if (_current_request == "launch" && _current_peer == E->get()) {
+ if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) {
continue;
}
E->get()->res_queue.push_back(event);
@@ -286,25 +740,46 @@ void DebugAdapterProtocol::notify_output(const String &p_message) {
}
}
+void DebugAdapterProtocol::notify_custom_data(const String &p_msg, const Array &p_data) {
+ Dictionary event = parser->ev_custom_data(p_msg, p_data);
+ for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
+ Ref<DAPeer> peer = E->get();
+ if (peer->supportsCustomData) {
+ peer->res_queue.push_back(event);
+ }
+ }
+}
+
+void DebugAdapterProtocol::notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) {
+ Dictionary event = parser->ev_breakpoint(p_breakpoint, p_enabled);
+ for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
+ if (_current_request == "setBreakpoints" && E->get() == _current_peer) {
+ continue;
+ }
+ E->get()->res_queue.push_back(event);
+ }
+}
+
Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array &p_lines) {
Array updated_breakpoints;
+ // Add breakpoints
for (int i = 0; i < p_lines.size(); i++) {
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true);
DAP::Breakpoint breakpoint;
- breakpoint.verified = true;
- breakpoint.source.path = p_path;
- breakpoint.source.compute_checksums();
breakpoint.line = p_lines[i];
+ breakpoint.source.path = p_path;
- List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
- if (E) {
- breakpoint.id = E->get().id;
- } else {
- breakpoint.id = breakpoint_id++;
- breakpoint_list.push_back(breakpoint);
- }
+ ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array());
+ updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json());
+ }
- updated_breakpoints.push_back(breakpoint.to_json());
+ // Remove breakpoints
+ for (List<DAP::Breakpoint>::Element *E = breakpoint_list.front(); E; E = E->next()) {
+ DAP::Breakpoint b = E->get();
+ if (b.source.path == p_path && !p_lines.has(b.line)) {
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false);
+ }
}
return updated_breakpoints;
@@ -347,6 +822,29 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool
_processing_stackdump = p_has_stackdump;
}
+void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) {
+ DAP::Breakpoint breakpoint;
+ breakpoint.verified = true;
+ breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path);
+ breakpoint.source.compute_checksums();
+ breakpoint.line = p_line;
+
+ if (p_enabled) {
+ // Add the breakpoint
+ breakpoint.id = breakpoint_id++;
+ breakpoint_list.push_back(breakpoint);
+ } else {
+ // Remove the breakpoint
+ List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
+ if (E) {
+ breakpoint.id = E->get().id;
+ breakpoint_list.erase(E);
+ }
+ }
+
+ notify_breakpoint(breakpoint, p_enabled);
+}
+
void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
if (_processing_breakpoint && !p_stack_dump.is_empty()) {
// Find existing breakpoint
@@ -424,11 +922,21 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) {
variable.name = stack_var.name;
variable.value = stack_var.value;
variable.type = Variant::get_type_name(stack_var.value.get_type());
+ variable.variablesReference = parse_variant(stack_var.value);
variable_list.find(variable_id)->value().push_back(variable.to_json());
_remaining_vars--;
}
+void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_data) {
+ // Ignore data that is already handled by DAP
+ if (p_msg == "debug_enter" || p_msg == "debug_exit" || p_msg == "stack_dump" || p_msg == "stack_frame_vars" || p_msg == "stack_frame_var" || p_msg == "output" || p_msg == "request_quit") {
+ return;
+ }
+
+ notify_custom_data(p_msg, p_data);
+}
+
void DebugAdapterProtocol::poll() {
if (server->is_connection_available()) {
on_client_connected();
@@ -459,6 +967,8 @@ void DebugAdapterProtocol::poll() {
}
Error DebugAdapterProtocol::start(int p_port, const IPAddress &p_bind_ip) {
+ _request_timeout = (uint64_t)_EDITOR_GET("network/debug_adapter/request_timeout");
+ _sync_breakpoints = (bool)_EDITOR_GET("network/debug_adapter/sync_breakpoints");
_initialized = true;
return server->listen(p_port, p_bind_ip);
}
@@ -484,12 +994,15 @@ DebugAdapterProtocol::DebugAdapterProtocol() {
node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused));
EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton();
+ debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled));
+
debugger_node->get_default_debugger()->connect("stopped", callable_mp(this, &DebugAdapterProtocol::on_debug_stopped));
debugger_node->get_default_debugger()->connect("output", callable_mp(this, &DebugAdapterProtocol::on_debug_output));
debugger_node->get_default_debugger()->connect("breaked", callable_mp(this, &DebugAdapterProtocol::on_debug_breaked));
debugger_node->get_default_debugger()->connect("stack_dump", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_dump));
debugger_node->get_default_debugger()->connect("stack_frame_vars", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_vars));
debugger_node->get_default_debugger()->connect("stack_frame_var", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_var));
+ debugger_node->get_default_debugger()->connect("debug_data", callable_mp(this, &DebugAdapterProtocol::on_debug_data));
}
DebugAdapterProtocol::~DebugAdapterProtocol() {
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h
index 6b542033ed..d4291992bf 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.h
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h
@@ -52,12 +52,17 @@ struct DAPeer : RefCounted {
int content_length = 0;
List<Dictionary> res_queue;
int seq = 0;
+ uint64_t timestamp = 0;
// Client specific info
bool linesStartAt1 = false;
bool columnsStartAt1 = false;
bool supportsVariableType = false;
bool supportsInvalidatedEvent = false;
+ bool supportsCustomData = false;
+
+ // Internal client info
+ bool attached = false;
Error handle_data();
Error send_data();
@@ -82,20 +87,26 @@ private:
void on_debug_stopped();
void on_debug_output(const String &p_message);
void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump);
+ void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled);
void on_debug_stack_dump(const Array &p_stack_dump);
void on_debug_stack_frame_vars(const int &p_size);
void on_debug_stack_frame_var(const Array &p_data);
+ void on_debug_data(const String &p_msg, const Array &p_data);
void reset_current_info();
void reset_ids();
void reset_stack_info();
+ int parse_variant(const Variant &p_var);
+
bool _initialized = false;
bool _processing_breakpoint = false;
bool _stepping = false;
bool _processing_stackdump = false;
int _remaining_vars = 0;
int _current_frame = 0;
+ uint64_t _request_timeout = 1000;
+ bool _sync_breakpoints = false;
String _current_request;
Ref<DAPeer> _current_peer;
@@ -108,6 +119,8 @@ private:
Map<int, Array> variable_list;
public:
+ friend class DebugAdapterServer;
+
_FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; }
_FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; }
@@ -126,8 +139,10 @@ public:
void notify_stopped_step();
void notify_continued();
void notify_output(const String &p_message);
+ void notify_custom_data(const String &p_msg, const Array &p_data);
+ void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled);
- Array update_breakpoints(const String &p_path, const Array &p_breakpoints);
+ Array update_breakpoints(const String &p_path, const Array &p_lines);
void poll();
Error start(int p_port, const IPAddress &p_bind_ip);
diff --git a/editor/debugger/debug_adapter/debug_adapter_server.cpp b/editor/debugger/debug_adapter/debug_adapter_server.cpp
index f9092a1791..4775e2c8b0 100644
--- a/editor/debugger/debug_adapter/debug_adapter_server.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_server.cpp
@@ -36,7 +36,8 @@
DebugAdapterServer::DebugAdapterServer() {
_EDITOR_DEF("network/debug_adapter/remote_port", remote_port);
- _EDITOR_DEF("network/debug_adapter/use_thread", use_thread);
+ _EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout);
+ _EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints);
}
void DebugAdapterServer::_notification(int p_what) {
@@ -50,16 +51,17 @@ void DebugAdapterServer::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
// The main loop can be run again during request processing, which modifies internal state of the protocol.
// Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished.
- if (started && !use_thread && !polling) {
+ if (started && !polling) {
polling = true;
protocol.poll();
polling = false;
}
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ protocol._request_timeout = EditorSettings::get_singleton()->get("network/debug_adapter/request_timeout");
+ protocol._sync_breakpoints = EditorSettings::get_singleton()->get("network/debug_adapter/sync_breakpoints");
int remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port");
- bool use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread");
- if (remote_port != this->remote_port || use_thread != this->use_thread) {
+ if (remote_port != this->remote_port) {
this->stop();
this->start();
}
@@ -67,35 +69,16 @@ void DebugAdapterServer::_notification(int p_what) {
}
}
-void DebugAdapterServer::thread_func(void *p_userdata) {
- DebugAdapterServer *self = static_cast<DebugAdapterServer *>(p_userdata);
- while (self->thread_running) {
- // Poll 20 times per second
- self->protocol.poll();
- OS::get_singleton()->delay_usec(50000);
- }
-}
-
void DebugAdapterServer::start() {
remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port");
- use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread");
if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) {
EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR);
- if (use_thread) {
- thread_running = true;
- thread.start(DebugAdapterServer::thread_func, this);
- }
- set_process_internal(!use_thread);
+ set_process_internal(true);
started = true;
}
}
void DebugAdapterServer::stop() {
- if (use_thread) {
- ERR_FAIL_COND(!thread.is_started());
- thread_running = false;
- thread.wait_to_finish();
- }
protocol.stop();
started = false;
EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR);
diff --git a/editor/debugger/debug_adapter/debug_adapter_server.h b/editor/debugger/debug_adapter/debug_adapter_server.h
index f8a38965cc..c449403cc2 100644
--- a/editor/debugger/debug_adapter/debug_adapter_server.h
+++ b/editor/debugger/debug_adapter/debug_adapter_server.h
@@ -39,11 +39,9 @@ class DebugAdapterServer : public EditorPlugin {
DebugAdapterProtocol protocol;
- Thread thread;
int remote_port = 6006;
bool thread_running = false;
bool started = false;
- bool use_thread = false;
bool polling = false;
static void thread_func(void *p_userdata);
diff --git a/editor/debugger/debug_adapter/debug_adapter_types.h b/editor/debugger/debug_adapter/debug_adapter_types.h
index aa9ab1adcd..5156c91d14 100644
--- a/editor/debugger/debug_adapter/debug_adapter_types.h
+++ b/editor/debugger/debug_adapter/debug_adapter_types.h
@@ -38,7 +38,11 @@ namespace DAP {
enum ErrorType {
UNKNOWN,
- WRONG_PATH
+ WRONG_PATH,
+ NOT_RUNNING,
+ TIMEOUT,
+ UNKNOWN_PLATFORM,
+ MISSING_DEVICE
};
struct Checksum {
@@ -118,10 +122,14 @@ struct Breakpoint {
struct BreakpointLocation {
int line;
+ int endLine = -1;
_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;
dict["line"] = line;
+ if (endLine >= 0) {
+ dict["endLine"] = endLine;
+ }
return dict;
}
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index a9cb1a0131..be84e8dec5 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -164,6 +164,7 @@ void EditorDebuggerNode::_bind_methods() {
ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script")));
ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug")));
+ ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled")));
}
EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() {
@@ -182,16 +183,16 @@ ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const {
return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(0));
}
-Error EditorDebuggerNode::start(const String &p_protocol) {
+Error EditorDebuggerNode::start(const String &p_uri) {
stop();
+ ERR_FAIL_COND_V(p_uri.find("://") < 0, ERR_INVALID_PARAMETER);
if (EDITOR_GET("run/output/always_open_output_on_play")) {
EditorNode::get_singleton()->make_bottom_panel_item_visible(EditorNode::get_log());
} else {
EditorNode::get_singleton()->make_bottom_panel_item_visible(this);
}
-
- server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_protocol));
- const Error err = server->start();
+ server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_uri.substr(0, p_uri.find("://") + 3)));
+ const Error err = server->start(p_uri);
if (err != OK) {
return err;
}
@@ -487,6 +488,8 @@ void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
dbg->set_breakpoint(p_path, p_line, p_enabled);
});
+
+ emit_signal("breakpoint_toggled", p_path, p_line, p_enabled);
}
void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) {
diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h
index 39a95326be..4d9e846834 100644
--- a/editor/debugger/editor_debugger_node.h
+++ b/editor/debugger/editor_debugger_node.h
@@ -188,7 +188,7 @@ public:
void set_camera_override(CameraOverride p_override);
CameraOverride get_camera_override();
- Error start(const String &p_protocol = "tcp://");
+ Error start(const String &p_uri = "tcp://");
void stop();
diff --git a/editor/debugger/editor_debugger_server.cpp b/editor/debugger/editor_debugger_server.cpp
index e8524e0702..8c3833af50 100644
--- a/editor/debugger/editor_debugger_server.cpp
+++ b/editor/debugger/editor_debugger_server.cpp
@@ -45,7 +45,7 @@ private:
public:
static EditorDebuggerServer *create(const String &p_protocol);
virtual void poll() {}
- virtual Error start();
+ virtual Error start(const String &p_uri);
virtual void stop();
virtual bool is_active() const;
virtual bool is_connection_available() const;
@@ -63,11 +63,18 @@ EditorDebuggerServerTCP::EditorDebuggerServerTCP() {
server.instantiate();
}
-Error EditorDebuggerServerTCP::start() {
- int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
- const Error err = server->listen(remote_port);
+Error EditorDebuggerServerTCP::start(const String &p_uri) {
+ int bind_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
+ String bind_host = (String)EditorSettings::get_singleton()->get("network/debug/remote_host");
+ if (!p_uri.is_empty() && p_uri != "tcp://") {
+ String scheme, path;
+ Error err = p_uri.parse_url(scheme, bind_host, bind_port, path);
+ ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER);
+ }
+ const Error err = server->listen(bind_port, bind_host);
if (err != OK) {
- EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR);
+ EditorNode::get_log()->add_message(String("Error listening on port ") + itos(bind_port), EditorLog::MSG_TYPE_ERROR);
return err;
}
return err;
diff --git a/editor/debugger/editor_debugger_server.h b/editor/debugger/editor_debugger_server.h
index 6216d0df3d..844d1a9e5a 100644
--- a/editor/debugger/editor_debugger_server.h
+++ b/editor/debugger/editor_debugger_server.h
@@ -48,7 +48,7 @@ public:
static void register_protocol_handler(const String &p_protocol, CreateServerFunc p_func);
static EditorDebuggerServer *create(const String &p_protocol);
virtual void poll() = 0;
- virtual Error start() = 0;
+ virtual Error start(const String &p_uri = "") = 0;
virtual void stop() = 0;
virtual bool is_active() const = 0;
virtual bool is_connection_available() const = 0;
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 2a5013893f..2f5bde64a9 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -296,6 +296,7 @@ Size2 ScriptEditorDebugger::get_minimum_size() const {
}
void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) {
+ emit_signal(SNAME("debug_data"), p_msg, p_data);
if (p_msg == "debug_enter") {
_put_msg("get_stack_dump", Array());
@@ -872,6 +873,16 @@ void ScriptEditorDebugger::_clear_execution() {
inspector->clear_stack_variables();
}
+void ScriptEditorDebugger::_set_breakpoint(const String &p_file, const int &p_line, const bool &p_enabled) {
+ Ref<Script> script = ResourceLoader::load(p_file);
+ emit_signal("set_breakpoint", script, p_line - 1, p_enabled);
+ script.unref();
+}
+
+void ScriptEditorDebugger::_clear_breakpoints() {
+ emit_signal("clear_breakpoints");
+}
+
void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
error_count = 0;
warning_count = 0;
@@ -1503,6 +1514,9 @@ void ScriptEditorDebugger::_bind_methods() {
ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump")));
ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars")));
ADD_SIGNAL(MethodInfo("stack_frame_var", PropertyInfo(Variant::ARRAY, "data")));
+ ADD_SIGNAL(MethodInfo("debug_data", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::ARRAY, "data")));
+ ADD_SIGNAL(MethodInfo("set_breakpoint", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled")));
+ ADD_SIGNAL(MethodInfo("clear_breakpoints"));
}
void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) {
diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h
index 499dda86da..1c1c0fd3e5 100644
--- a/editor/debugger/script_editor_debugger.h
+++ b/editor/debugger/script_editor_debugger.h
@@ -205,6 +205,9 @@ private:
void _clear_execution();
void _stop_and_notify();
+ void _set_breakpoint(const String &p_path, const int &p_line, const bool &p_enabled);
+ void _clear_breakpoints();
+
protected:
void _notification(int p_what);
static void _bind_methods();
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index fb5f7448c4..d04875f188 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -277,7 +277,7 @@ void DocTools::generate(bool p_basic_types) {
EO = EO->next();
}
- if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL) {
+ if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) {
continue;
}
@@ -434,6 +434,18 @@ void DocTools::generate(bool p_basic_types) {
}
}
+ Vector<Error> errs = ClassDB::get_method_error_return_values(name, E.name);
+ if (errs.size()) {
+ if (errs.find(OK) == -1) {
+ errs.insert(0, OK);
+ }
+ for (int i = 0; i < errs.size(); i++) {
+ if (method.errors_returned.find(errs[i]) == -1) {
+ method.errors_returned.push_back(errs[i]);
+ }
+ }
+ }
+
c.methods.push_back(method);
}
@@ -874,6 +886,9 @@ static Error _parse_methods(Ref<XMLParser> &parser, Vector<DocData::MethodDoc> &
if (parser->has_attribute("enum")) {
method.return_enum = parser->get_attribute_value("enum");
}
+ } else if (name == "returns_error") {
+ ERR_FAIL_COND_V(!parser->has_attribute("number"), ERR_FILE_CORRUPT);
+ method.errors_returned.push_back(parser->get_attribute_value("number").to_int());
} else if (name == "argument") {
DocData::ArgumentDoc argument;
ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT);
@@ -1222,6 +1237,11 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
}
_write_string(f, 3, "<return type=\"" + m.return_type + "\"" + enum_text + " />");
}
+ if (m.errors_returned.size() > 0) {
+ for (int j = 0; j < m.errors_returned.size(); j++) {
+ _write_string(f, 3, "<returns_error number=\"" + itos(m.errors_returned[j]) + "\"/>");
+ }
+ }
for (int j = 0; j < m.arguments.size(); j++) {
const DocData::ArgumentDoc &a = m.arguments[j];
diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp
index fad76682b5..fcf79a80a7 100644
--- a/editor/editor_autoload_settings.cpp
+++ b/editor/editor_autoload_settings.cpp
@@ -64,7 +64,12 @@ void EditorAutoloadSettings::_notification(int p_what) {
bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, String *r_error) {
if (!p_name.is_valid_identifier()) {
if (r_error) {
- *r_error = TTR("Invalid name.") + "\n" + TTR("Valid characters:") + " a-z, A-Z, 0-9 or _";
+ *r_error = TTR("Invalid name.") + " ";
+ if (p_name.size() > 0 && p_name.left(1).is_numeric()) {
+ *r_error += TTR("Cannot begin with a digit.");
+ } else {
+ *r_error += TTR("Valid characters:") + " a-z, A-Z, 0-9 or _";
+ }
}
return false;
@@ -72,7 +77,15 @@ bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, Strin
if (ClassDB::class_exists(p_name)) {
if (r_error) {
- *r_error = TTR("Invalid name.") + "\n" + TTR("Must not collide with an existing engine class name.");
+ *r_error = TTR("Invalid name.") + " " + TTR("Must not collide with an existing engine class name.");
+ }
+
+ return false;
+ }
+
+ if (ScriptServer::is_global_class(p_name)) {
+ if (r_error) {
+ *r_error = TTR("Invalid name.") + "\n" + TTR("Must not collide with an existing global script class name.");
}
return false;
@@ -81,7 +94,7 @@ bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, Strin
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (Variant::get_type_name(Variant::Type(i)) == p_name) {
if (r_error) {
- *r_error = TTR("Invalid name.") + "\n" + TTR("Must not collide with an existing built-in type name.");
+ *r_error = TTR("Invalid name.") + " " + TTR("Must not collide with an existing built-in type name.");
}
return false;
@@ -91,7 +104,7 @@ bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, Strin
for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
if (CoreConstants::get_global_constant_name(i) == p_name) {
if (r_error) {
- *r_error = TTR("Invalid name.") + "\n" + TTR("Must not collide with an existing global constant name.");
+ *r_error = TTR("Invalid name.") + " " + TTR("Must not collide with an existing global constant name.");
}
return false;
@@ -104,7 +117,7 @@ bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, Strin
for (const String &E : keywords) {
if (E == p_name) {
if (r_error) {
- *r_error = TTR("Invalid name.") + "\n" + TTR("Keyword cannot be used as an autoload name.");
+ *r_error = TTR("Invalid name.") + " " + TTR("Keyword cannot be used as an autoload name.");
}
return false;
@@ -338,8 +351,11 @@ void EditorAutoloadSettings::_autoload_path_text_changed(const String p_path) {
}
void EditorAutoloadSettings::_autoload_text_changed(const String p_name) {
- add_autoload->set_disabled(
- autoload_add_path->get_text() == "" || !_autoload_name_is_valid(p_name, nullptr));
+ String error_string;
+ bool is_name_valid = _autoload_name_is_valid(p_name, &error_string);
+ add_autoload->set_disabled(autoload_add_path->get_text() == "" || !is_name_valid);
+ error_message->set_text(error_string);
+ error_message->set_visible(autoload_add_name->get_text() != "" && !is_name_valid);
}
Node *EditorAutoloadSettings::_create_autoload(const String &p_path) {
@@ -820,6 +836,12 @@ EditorAutoloadSettings::EditorAutoloadSettings() {
HBoxContainer *hbc = memnew(HBoxContainer);
add_child(hbc);
+ error_message = memnew(Label);
+ error_message->hide();
+ error_message->set_align(Label::Align::ALIGN_RIGHT);
+ error_message->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ add_child(error_message);
+
Label *l = memnew(Label);
l->set_text(TTR("Path:"));
hbc->add_child(l);
diff --git a/editor/editor_autoload_settings.h b/editor/editor_autoload_settings.h
index b709728856..b8e054cd14 100644
--- a/editor/editor_autoload_settings.h
+++ b/editor/editor_autoload_settings.h
@@ -70,6 +70,7 @@ class EditorAutoloadSettings : public VBoxContainer {
LineEdit *autoload_add_name;
Button *add_autoload;
LineEdit *autoload_add_path;
+ Label *error_message;
Button *browse_button;
EditorFileDialog *file_dialog;
diff --git a/editor/editor_command_palette.cpp b/editor/editor_command_palette.cpp
index 25250e231e..4ad45f9649 100644
--- a/editor/editor_command_palette.cpp
+++ b/editor/editor_command_palette.cpp
@@ -70,6 +70,7 @@ void EditorCommandPalette::_update_command_search(const String &search_text) {
r.key_name = command_keys[i];
r.display_name = commands[r.key_name].name;
r.shortcut_text = commands[r.key_name].shortcut;
+ r.last_used = commands[r.key_name].last_used;
if (search_text.is_subsequence_ofi(r.display_name)) {
if (!search_text.is_empty()) {
@@ -94,6 +95,9 @@ void EditorCommandPalette::_update_command_search(const String &search_text) {
if (!search_text.is_empty()) {
SortArray<CommandEntry, CommandEntryComparator> sorter;
sorter.sort(entries.ptrw(), entries.size());
+ } else {
+ SortArray<CommandEntry, CommandHistoryComparator> sorter;
+ sorter.sort(entries.ptrw(), entries.size());
}
const int entry_limit = MIN(entries.size(), 300);
@@ -213,7 +217,9 @@ void EditorCommandPalette::_add_command(String p_command_name, String p_key_name
void EditorCommandPalette::execute_command(String &p_command_key) {
ERR_FAIL_COND_MSG(!commands.has(p_command_key), p_command_key + " not found.");
+ commands[p_command_key].last_used = OS::get_singleton()->get_unix_time();
commands[p_command_key].callable.call_deferred(nullptr, 0);
+ _save_history();
}
void EditorCommandPalette::register_shortcuts_as_command() {
@@ -230,6 +236,14 @@ void EditorCommandPalette::register_shortcuts_as_command() {
key = unregistered_shortcuts.next(key);
}
unregistered_shortcuts.clear();
+
+ // Load command use history.
+ Dictionary command_history = EditorSettings::get_singleton()->get_project_metadata("command_palette", "command_history", Dictionary());
+ Array history_entries = command_history.keys();
+ for (int i = 0; i < history_entries.size(); i++) {
+ const String &history_key = history_entries[i];
+ commands[history_key].last_used = command_history[history_key];
+ }
}
Ref<Shortcut> EditorCommandPalette::add_shortcut_command(const String &p_command, const String &p_key, Ref<Shortcut> p_shortcut) {
@@ -252,6 +266,19 @@ void EditorCommandPalette::_theme_changed() {
command_search_box->set_right_icon(search_options->get_theme_icon("Search", "EditorIcons"));
}
+void EditorCommandPalette::_save_history() const {
+ Dictionary command_history;
+ List<String> command_keys;
+ commands.get_key_list(&command_keys);
+
+ for (const String &key : command_keys) {
+ if (commands[key].last_used > 0) {
+ command_history[key] = commands[key].last_used;
+ }
+ }
+ EditorSettings::get_singleton()->set_project_metadata("command_palette", "command_history", command_history);
+}
+
EditorCommandPalette *EditorCommandPalette::get_singleton() {
if (singleton == nullptr) {
singleton = memnew(EditorCommandPalette);
diff --git a/editor/editor_command_palette.h b/editor/editor_command_palette.h
index 093f4b797d..39821a1169 100644
--- a/editor/editor_command_palette.h
+++ b/editor/editor_command_palette.h
@@ -47,13 +47,15 @@ class EditorCommandPalette : public ConfirmationDialog {
Callable callable;
String name;
String shortcut;
+ int last_used = 0; // Store time as int, because doubles have problems with text serialization.
};
struct CommandEntry {
String key_name;
String display_name;
String shortcut_text;
- float score;
+ int last_used = 0;
+ float score = 0;
};
struct CommandEntryComparator {
@@ -62,6 +64,12 @@ class EditorCommandPalette : public ConfirmationDialog {
}
};
+ struct CommandHistoryComparator {
+ _FORCE_INLINE_ bool operator()(const CommandEntry &A, const CommandEntry &B) const {
+ return A.last_used > B.last_used;
+ }
+ };
+
HashMap<String, Command> commands;
HashMap<String, Pair<String, Ref<Shortcut>>> unregistered_shortcuts;
@@ -74,6 +82,7 @@ class EditorCommandPalette : public ConfirmationDialog {
void _update_command_keys();
void _add_command(String p_command_name, String p_key_name, Callable p_binded_action, String p_shortcut_text = "None");
void _theme_changed();
+ void _save_history() const;
EditorCommandPalette();
protected:
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index c62e5b75b2..e40bbefef8 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -438,6 +438,21 @@ const Vector<Callable> EditorData::get_undo_redo_inspector_hook_callback() {
return undo_redo_callbacks;
}
+void EditorData::add_move_array_element_function(const StringName &p_class, Callable p_callable) {
+ move_element_functions.insert(p_class, p_callable);
+}
+
+void EditorData::remove_move_array_element_function(const StringName &p_class) {
+ move_element_functions.erase(p_class);
+}
+
+Callable EditorData::get_move_array_element_function(const StringName &p_class) const {
+ if (move_element_functions.has(p_class)) {
+ return move_element_functions[p_class];
+ }
+ return Callable();
+}
+
void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) {
p_plugin->undo_redo = nullptr;
editor_plugins.erase(p_plugin);
diff --git a/editor/editor_data.h b/editor/editor_data.h
index df6ba9d0c9..9184ddcf39 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -133,6 +133,7 @@ private:
List<PropertyData> clipboard;
UndoRedo undo_redo;
Vector<Callable> undo_redo_callbacks;
+ Map<StringName, Callable> move_element_functions;
void _cleanup_history();
@@ -167,10 +168,14 @@ public:
EditorPlugin *get_editor_plugin(int p_idx);
UndoRedo &get_undo_redo();
- void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have 4 args: (Object* undo_redo, Object *modified_object, String property, Variant new_value)
+ void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value)
void remove_undo_redo_inspector_hook_callback(Callable p_callable);
const Vector<Callable> get_undo_redo_inspector_hook_callback();
+ void add_move_array_element_function(const StringName &p_class, Callable p_callable); // Function should have this signature: void (Object* undo_redo, Object *modified_object, String array_prefix, int element_index, int new_position)
+ void remove_move_array_element_function(const StringName &p_class);
+ Callable get_move_array_element_function(const StringName &p_class) const;
+
void save_editor_global_states();
void restore_editor_global_states();
diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp
index 91c3c51c4d..10ed76673e 100644
--- a/editor/editor_export.cpp
+++ b/editor/editor_export.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/crypto/crypto_core.h"
+#include "core/extension/native_extension.h"
#include "core/io/config_file.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
@@ -1050,6 +1051,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
}
}
+ if (FileAccess::exists(NativeExtension::EXTENSION_LIST_CONFIG_FILE)) {
+ Vector<uint8_t> array = FileAccess::get_file_as_array(NativeExtension::EXTENSION_LIST_CONFIG_FILE);
+ err = p_func(p_udata, NativeExtension::EXTENSION_LIST_CONFIG_FILE, array, idx, total, enc_in_filters, enc_ex_filters, key);
+ if (err != OK) {
+ return err;
+ }
+ }
+
// Store text server data if it is supported.
if (TS->has_feature(TextServer::FEATURE_USE_SUPPORT_DATA)) {
bool use_data = ProjectSettings::get_singleton()->get("internationalization/locale/include_text_server_data");
@@ -1939,7 +1948,7 @@ void EditorExportPlatformPC::set_debug_32(const String &p_file) {
void EditorExportPlatformPC::get_platform_features(List<String> *r_features) {
r_features->push_back("pc"); //all pcs support "pc"
r_features->push_back("s3tc"); //all pcs support "s3tc" compression
- r_features->push_back(get_os_name()); //OS name is a feature
+ r_features->push_back(get_os_name().to_lower()); //OS name is a feature
}
void EditorExportPlatformPC::resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp
index 1e9d579708..bf95e6cf62 100644
--- a/editor/editor_file_dialog.cpp
+++ b/editor/editor_file_dialog.cpp
@@ -958,7 +958,7 @@ String EditorFileDialog::get_current_path() const {
}
void EditorFileDialog::set_current_dir(const String &p_dir) {
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
dir_access->change_dir(OS::get_singleton()->get_resource_dir());
}
dir_access->change_dir(p_dir);
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 78861eff9d..8523833d52 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -31,6 +31,7 @@
#include "editor_file_system.h"
#include "core/config/project_settings.h"
+#include "core/extension/native_extension_manager.h"
#include "core/io/file_access.h"
#include "core/io/resource_importer.h"
#include "core/io/resource_loader.h"
@@ -443,6 +444,10 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name);
+ if (importer.is_null()) {
+ return true; // the importer has possibly changed, try to reimport.
+ }
+
if (importer->get_format_version() > version) {
return true; // version changed, reimport
}
@@ -605,6 +610,18 @@ bool EditorFileSystem::_update_scan_actions() {
}
}
+ if (_scan_extensions()) {
+ //needs editor restart
+ //extensions also may provide filetypes to be imported, so they must run before importing
+ if (EditorNode::immediate_confirmation_dialog(TTR("Some extensions need the editor to restart to take effect."), first_scan ? TTR("Restart") : TTR("Save&Restart"), TTR("Continue"))) {
+ if (!first_scan) {
+ EditorNode::get_singleton()->save_all_scenes();
+ }
+ EditorNode::get_singleton()->restart_editor();
+ //do not import
+ return true;
+ }
+ }
if (reimports.size()) {
reimport_files(reimports);
} else {
@@ -2222,6 +2239,76 @@ ResourceUID::ID EditorFileSystem::_resource_saver_get_resource_id_for_path(const
}
}
+static void _scan_extensions_dir(EditorFileSystemDirectory *d, Set<String> &extensions) {
+ int fc = d->get_file_count();
+ for (int i = 0; i < fc; i++) {
+ if (d->get_file_type(i) == SNAME("NativeExtension")) {
+ extensions.insert(d->get_file_path(i));
+ }
+ }
+ int dc = d->get_subdir_count();
+ for (int i = 0; i < dc; i++) {
+ _scan_extensions_dir(d->get_subdir(i), extensions);
+ }
+}
+bool EditorFileSystem::_scan_extensions() {
+ EditorFileSystemDirectory *d = get_filesystem();
+ Set<String> extensions;
+ _scan_extensions_dir(d, extensions);
+
+ //verify against loaded extensions
+
+ Vector<String> extensions_added;
+ Vector<String> extensions_removed;
+
+ for (const String &E : extensions) {
+ if (!NativeExtensionManager::get_singleton()->is_extension_loaded(E)) {
+ extensions_added.push_back(E);
+ }
+ }
+
+ Vector<String> loaded_extensions = NativeExtensionManager::get_singleton()->get_loaded_extensions();
+ for (int i = 0; i < loaded_extensions.size(); i++) {
+ if (!extensions.has(loaded_extensions[i])) {
+ extensions_removed.push_back(loaded_extensions[i]);
+ }
+ }
+
+ if (extensions.size()) {
+ if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed
+ FileAccessRef f = FileAccess::open(NativeExtension::EXTENSION_LIST_CONFIG_FILE, FileAccess::WRITE);
+ for (const String &E : extensions) {
+ f->store_line(E);
+ }
+ }
+ } else {
+ if (loaded_extensions.size() || FileAccess::exists(NativeExtension::EXTENSION_LIST_CONFIG_FILE)) { //extensions were removed
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ da->remove(NativeExtension::EXTENSION_LIST_CONFIG_FILE);
+ }
+ }
+
+ bool needs_restart = false;
+ for (int i = 0; i < extensions_added.size(); i++) {
+ NativeExtensionManager::LoadStatus st = NativeExtensionManager::get_singleton()->load_extension(extensions_added[i]);
+ if (st == NativeExtensionManager::LOAD_STATUS_FAILED) {
+ EditorNode::get_singleton()->add_io_error("Error loading extension: " + extensions_added[i]);
+ } else if (st == NativeExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+ for (int i = 0; i < extensions_removed.size(); i++) {
+ NativeExtensionManager::LoadStatus st = NativeExtensionManager::get_singleton()->unload_extension(extensions_removed[i]);
+ if (st == NativeExtensionManager::LOAD_STATUS_FAILED) {
+ EditorNode::get_singleton()->add_io_error("Error removing extension: " + extensions_added[i]);
+ } else if (st == NativeExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+
+ return needs_restart;
+}
+
void EditorFileSystem::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_filesystem"), &EditorFileSystem::get_filesystem);
ClassDB::bind_method(D_METHOD("is_scanning"), &EditorFileSystem::is_scanning);
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 9dce29d09c..b47cf5523a 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -255,6 +255,8 @@ class EditorFileSystem : public Node {
static ResourceUID::ID _resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate);
+ bool _scan_extensions();
+
protected:
void _notification(int p_what);
static void _bind_methods();
diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp
index 1e3db1a7b0..e5d6315ef7 100644
--- a/editor/editor_fonts.cpp
+++ b/editor/editor_fonts.cpp
@@ -67,46 +67,113 @@
m_name->add_data(FontJapanese); \
m_name->add_data(FontFallback);
-// the custom spacings might only work with Noto Sans
-#define MAKE_DEFAULT_FONT(m_name) \
- Ref<Font> m_name; \
- m_name.instantiate(); \
- if (CustomFont.is_valid()) { \
- m_name->add_data(CustomFont); \
- m_name->add_data(DefaultFont); \
- } else { \
- m_name->add_data(DefaultFont); \
- } \
- m_name->set_spacing(Font::SPACING_TOP, -EDSCALE); \
- m_name->set_spacing(Font::SPACING_BOTTOM, -EDSCALE); \
+#define MAKE_DEFAULT_FONT(m_name, m_variations, m_base_size) \
+ Ref<Font> m_name; \
+ m_name.instantiate(); \
+ if (CustomFont.is_valid()) { \
+ m_name->add_data(CustomFont); \
+ m_name->add_data(DefaultFont); \
+ } else { \
+ m_name->add_data(DefaultFont); \
+ } \
+ { \
+ Dictionary variations; \
+ if (m_variations != String()) { \
+ Vector<String> variation_tags = m_variations.split(","); \
+ for (int i = 0; i < variation_tags.size(); i++) { \
+ Vector<String> tokens = variation_tags[i].split("="); \
+ if (tokens.size() == 2) { \
+ variations[tokens[0]] = tokens[1].to_float(); \
+ } \
+ } \
+ } \
+ m_name->set_variation_coordinates(variations); \
+ } \
+ m_name->set_base_size(m_base_size); \
+ m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
+ m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
MAKE_FALLBACKS(m_name);
-#define MAKE_BOLD_FONT(m_name) \
- Ref<Font> m_name; \
- m_name.instantiate(); \
- if (CustomFontBold.is_valid()) { \
- m_name->add_data(CustomFontBold); \
- m_name->add_data(DefaultFontBold); \
- } else { \
- m_name->add_data(DefaultFontBold); \
- } \
- m_name->set_spacing(Font::SPACING_TOP, -EDSCALE); \
- m_name->set_spacing(Font::SPACING_BOTTOM, -EDSCALE); \
+#define MAKE_BOLD_FONT(m_name, m_variations, m_base_size) \
+ Ref<Font> m_name; \
+ m_name.instantiate(); \
+ if (CustomFontBold.is_valid()) { \
+ m_name->add_data(CustomFontBold); \
+ m_name->add_data(DefaultFontBold); \
+ } else { \
+ m_name->add_data(DefaultFontBold); \
+ } \
+ { \
+ Dictionary variations; \
+ if (m_variations != String()) { \
+ Vector<String> variation_tags = m_variations.split(","); \
+ for (int i = 0; i < variation_tags.size(); i++) { \
+ Vector<String> tokens = variation_tags[i].split("="); \
+ if (tokens.size() == 2) { \
+ variations[tokens[0]] = tokens[1].to_float(); \
+ } \
+ } \
+ } \
+ m_name->set_variation_coordinates(variations); \
+ } \
+ m_name->set_base_size(m_base_size); \
+ m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
+ m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
MAKE_FALLBACKS_BOLD(m_name);
-#define MAKE_SOURCE_FONT(m_name) \
- Ref<Font> m_name; \
- m_name.instantiate(); \
- if (CustomFontSource.is_valid()) { \
- m_name->add_data(CustomFontSource); \
- m_name->add_data(dfmono); \
- } else { \
- m_name->add_data(dfmono); \
- } \
- m_name->set_spacing(Font::SPACING_TOP, -EDSCALE); \
- m_name->set_spacing(Font::SPACING_BOTTOM, -EDSCALE); \
+#define MAKE_SOURCE_FONT(m_name, m_variations, m_base_size) \
+ Ref<Font> m_name; \
+ m_name.instantiate(); \
+ if (CustomFontSource.is_valid()) { \
+ m_name->add_data(CustomFontSource); \
+ m_name->add_data(dfmono); \
+ } else { \
+ m_name->add_data(dfmono); \
+ } \
+ { \
+ Dictionary variations; \
+ if (m_variations != String()) { \
+ Vector<String> variation_tags = m_variations.split(","); \
+ for (int i = 0; i < variation_tags.size(); i++) { \
+ Vector<String> tokens = variation_tags[i].split("="); \
+ if (tokens.size() == 2) { \
+ variations[tokens[0]] = tokens[1].to_float(); \
+ } \
+ } \
+ } \
+ m_name->set_variation_coordinates(variations); \
+ } \
+ m_name->set_base_size(m_base_size); \
+ m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
+ m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
MAKE_FALLBACKS(m_name);
+Ref<FontData> load_cached_external_font(const String &p_path, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint) {
+ Ref<FontData> font;
+ font.instantiate();
+
+ Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
+
+ font->set_data(data);
+ font->set_antialiased(p_aa);
+ font->set_hinting(p_hinting);
+ font->set_force_autohinter(p_autohint);
+
+ return font;
+}
+
+Ref<FontData> load_cached_internal_font(const uint8_t *p_data, size_t p_size, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint) {
+ Ref<FontData> font;
+ font.instantiate();
+
+ font->set_data_ptr(p_data, p_size);
+ font->set_antialiased(p_aa);
+ font->set_hinting(p_hinting);
+ font->set_force_autohinter(p_autohint);
+
+ return font;
+}
+
void editor_register_fonts(Ref<Theme> p_theme) {
DirAccess *dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -144,11 +211,7 @@ void editor_register_fonts(Ref<Theme> p_theme) {
String custom_font_path = EditorSettings::get_singleton()->get("interface/editor/main_font");
Ref<FontData> CustomFont;
if (custom_font_path.length() > 0 && dir->file_exists(custom_font_path)) {
- CustomFont.instantiate();
- CustomFont->load_resource(custom_font_path, default_font_size);
- CustomFont->set_antialiased(font_antialiased);
- CustomFont->set_hinting(font_hinting);
- CustomFont->set_force_autohinter(true); //just looks better..i think?
+ CustomFont = load_cached_external_font(custom_font_path, font_hinting, font_antialiased, true);
} else {
EditorSettings::get_singleton()->set_manually("interface/editor/main_font", "");
}
@@ -158,11 +221,7 @@ void editor_register_fonts(Ref<Theme> p_theme) {
String custom_font_path_bold = EditorSettings::get_singleton()->get("interface/editor/main_font_bold");
Ref<FontData> CustomFontBold;
if (custom_font_path_bold.length() > 0 && dir->file_exists(custom_font_path_bold)) {
- CustomFontBold.instantiate();
- CustomFontBold->load_resource(custom_font_path_bold, default_font_size);
- CustomFontBold->set_antialiased(font_antialiased);
- CustomFontBold->set_hinting(font_hinting);
- CustomFontBold->set_force_autohinter(true); //just looks better..i think?
+ CustomFontBold = load_cached_external_font(custom_font_path_bold, font_hinting, font_antialiased, true);
} else {
EditorSettings::get_singleton()->set_manually("interface/editor/main_font_bold", "");
}
@@ -172,231 +231,51 @@ void editor_register_fonts(Ref<Theme> p_theme) {
String custom_font_path_source = EditorSettings::get_singleton()->get("interface/editor/code_font");
Ref<FontData> CustomFontSource;
if (custom_font_path_source.length() > 0 && dir->file_exists(custom_font_path_source)) {
- CustomFontSource.instantiate();
- CustomFontSource->load_resource(custom_font_path_source, default_font_size);
- CustomFontSource->set_antialiased(font_antialiased);
- CustomFontSource->set_hinting(font_hinting);
-
- Vector<String> subtag = String(EditorSettings::get_singleton()->get("interface/editor/code_font_custom_variations")).split(",");
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- if (subtag_a.size() == 2) {
- CustomFontSource->set_variation(subtag_a[0], subtag_a[1].to_float());
- }
- }
+ CustomFontSource = load_cached_external_font(custom_font_path_source, font_hinting, font_antialiased, true);
} else {
EditorSettings::get_singleton()->set_manually("interface/editor/code_font", "");
}
memdelete(dir);
- /* Noto Sans UI */
-
- Ref<FontData> DefaultFont;
- DefaultFont.instantiate();
- DefaultFont->load_memory(_font_NotoSans_Regular, _font_NotoSans_Regular_size, "ttf", default_font_size);
- DefaultFont->set_antialiased(font_antialiased);
- DefaultFont->set_hinting(font_hinting);
- DefaultFont->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> DefaultFontBold;
- DefaultFontBold.instantiate();
- DefaultFontBold->load_memory(_font_NotoSans_Bold, _font_NotoSans_Bold_size, "ttf", default_font_size);
- DefaultFontBold->set_antialiased(font_antialiased);
- DefaultFontBold->set_hinting(font_hinting);
- DefaultFontBold->set_force_autohinter(true); // just looks better..i think?
-
- Ref<FontData> FontArabic;
- FontArabic.instantiate();
- FontArabic->load_memory(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, "ttf", default_font_size);
- FontArabic->set_antialiased(font_antialiased);
- FontArabic->set_hinting(font_hinting);
- FontArabic->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontArabicBold;
- FontArabicBold.instantiate();
- FontArabicBold->load_memory(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, "ttf", default_font_size);
- FontArabicBold->set_antialiased(font_antialiased);
- FontArabicBold->set_hinting(font_hinting);
- FontArabicBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontBengali;
- FontBengali.instantiate();
- FontBengali->load_memory(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, "ttf", default_font_size);
- FontBengali->set_antialiased(font_antialiased);
- FontBengali->set_hinting(font_hinting);
- FontBengali->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontBengaliBold;
- FontBengaliBold.instantiate();
- FontBengaliBold->load_memory(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, "ttf", default_font_size);
- FontBengaliBold->set_antialiased(font_antialiased);
- FontBengaliBold->set_hinting(font_hinting);
- FontBengaliBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontDevanagari;
- FontDevanagari.instantiate();
- FontDevanagari->load_memory(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, "ttf", default_font_size);
- FontDevanagari->set_antialiased(font_antialiased);
- FontDevanagari->set_hinting(font_hinting);
- FontDevanagari->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontDevanagariBold;
- FontDevanagariBold.instantiate();
- FontDevanagariBold->load_memory(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, "ttf", default_font_size);
- FontDevanagariBold->set_antialiased(font_antialiased);
- FontDevanagariBold->set_hinting(font_hinting);
- FontDevanagariBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontGeorgian;
- FontGeorgian.instantiate();
- FontGeorgian->load_memory(_font_NotoSansGeorgian_Regular, _font_NotoSansGeorgian_Regular_size, "ttf", default_font_size);
- FontGeorgian->set_antialiased(font_antialiased);
- FontGeorgian->set_hinting(font_hinting);
- FontGeorgian->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontGeorgianBold;
- FontGeorgianBold.instantiate();
- FontGeorgianBold->load_memory(_font_NotoSansGeorgian_Bold, _font_NotoSansGeorgian_Bold_size, "ttf", default_font_size);
- FontGeorgianBold->set_antialiased(font_antialiased);
- FontGeorgianBold->set_hinting(font_hinting);
- FontGeorgianBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontHebrew;
- FontHebrew.instantiate();
- FontHebrew->load_memory(_font_NotoSansHebrew_Regular, _font_NotoSansHebrew_Regular_size, "ttf", default_font_size);
- FontHebrew->set_antialiased(font_antialiased);
- FontHebrew->set_hinting(font_hinting);
- FontHebrew->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontHebrewBold;
- FontHebrewBold.instantiate();
- FontHebrewBold->load_memory(_font_NotoSansHebrew_Bold, _font_NotoSansHebrew_Bold_size, "ttf", default_font_size);
- FontHebrewBold->set_antialiased(font_antialiased);
- FontHebrewBold->set_hinting(font_hinting);
- FontHebrewBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontMalayalam;
- FontMalayalam.instantiate();
- FontMalayalam->load_memory(_font_NotoSansMalayalamUI_Regular, _font_NotoSansMalayalamUI_Regular_size, "ttf", default_font_size);
- FontMalayalam->set_antialiased(font_antialiased);
- FontMalayalam->set_hinting(font_hinting);
- FontMalayalam->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontMalayalamBold;
- FontMalayalamBold.instantiate();
- FontMalayalamBold->load_memory(_font_NotoSansMalayalamUI_Bold, _font_NotoSansMalayalamUI_Bold_size, "ttf", default_font_size);
- FontMalayalamBold->set_antialiased(font_antialiased);
- FontMalayalamBold->set_hinting(font_hinting);
- FontMalayalamBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontOriya;
- FontOriya.instantiate();
- FontOriya->load_memory(_font_NotoSansOriyaUI_Regular, _font_NotoSansOriyaUI_Regular_size, "ttf", default_font_size);
- FontOriya->set_antialiased(font_antialiased);
- FontOriya->set_hinting(font_hinting);
- FontOriya->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontOriyaBold;
- FontOriyaBold.instantiate();
- FontOriyaBold->load_memory(_font_NotoSansOriyaUI_Bold, _font_NotoSansOriyaUI_Bold_size, "ttf", default_font_size);
- FontOriyaBold->set_antialiased(font_antialiased);
- FontOriyaBold->set_hinting(font_hinting);
- FontOriyaBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontSinhala;
- FontSinhala.instantiate();
- FontSinhala->load_memory(_font_NotoSansSinhalaUI_Regular, _font_NotoSansSinhalaUI_Regular_size, "ttf", default_font_size);
- FontSinhala->set_antialiased(font_antialiased);
- FontSinhala->set_hinting(font_hinting);
- FontSinhala->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontSinhalaBold;
- FontSinhalaBold.instantiate();
- FontSinhalaBold->load_memory(_font_NotoSansSinhalaUI_Bold, _font_NotoSansSinhalaUI_Bold_size, "ttf", default_font_size);
- FontSinhalaBold->set_antialiased(font_antialiased);
- FontSinhalaBold->set_hinting(font_hinting);
- FontSinhalaBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontTamil;
- FontTamil.instantiate();
- FontTamil->load_memory(_font_NotoSansTamilUI_Regular, _font_NotoSansTamilUI_Regular_size, "ttf", default_font_size);
- FontTamil->set_antialiased(font_antialiased);
- FontTamil->set_hinting(font_hinting);
- FontTamil->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontTamilBold;
- FontTamilBold.instantiate();
- FontTamilBold->load_memory(_font_NotoSansTamilUI_Bold, _font_NotoSansTamilUI_Bold_size, "ttf", default_font_size);
- FontTamilBold->set_antialiased(font_antialiased);
- FontTamilBold->set_hinting(font_hinting);
- FontTamilBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontTelugu;
- FontTelugu.instantiate();
- FontTelugu->load_memory(_font_NotoSansTeluguUI_Regular, _font_NotoSansTeluguUI_Regular_size, "ttf", default_font_size);
- FontTelugu->set_antialiased(font_antialiased);
- FontTelugu->set_hinting(font_hinting);
- FontTelugu->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontTeluguBold;
- FontTeluguBold.instantiate();
- FontTeluguBold->load_memory(_font_NotoSansTeluguUI_Bold, _font_NotoSansTeluguUI_Bold_size, "ttf", default_font_size);
- FontTeluguBold->set_antialiased(font_antialiased);
- FontTeluguBold->set_hinting(font_hinting);
- FontTeluguBold->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontThai;
- FontThai.instantiate();
- FontThai->load_memory(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, "ttf", default_font_size);
- FontThai->set_antialiased(font_antialiased);
- FontThai->set_hinting(font_hinting);
- FontThai->set_force_autohinter(true); //just looks better..i think?
-
- Ref<FontData> FontThaiBold;
- FontThaiBold.instantiate();
- FontThaiBold->load_memory(_font_NotoSansThaiUI_Bold, _font_NotoSansThaiUI_Bold_size, "ttf", default_font_size);
- FontThaiBold->set_antialiased(font_antialiased);
- FontThaiBold->set_hinting(font_hinting);
- FontThaiBold->set_force_autohinter(true); //just looks better..i think?
-
- /* Droid Sans Fallback */
-
- Ref<FontData> FontFallback;
- FontFallback.instantiate();
- FontFallback->load_memory(_font_DroidSansFallback, _font_DroidSansFallback_size, "ttf", default_font_size);
- FontFallback->set_antialiased(font_antialiased);
- FontFallback->set_hinting(font_hinting);
- FontFallback->set_force_autohinter(true); //just looks better..i think?
-
- /* Droid Sans Japanese */
-
- Ref<FontData> FontJapanese;
- FontJapanese.instantiate();
- FontJapanese->load_memory(_font_DroidSansJapanese, _font_DroidSansJapanese_size, "ttf", default_font_size);
- FontJapanese->set_antialiased(font_antialiased);
- FontJapanese->set_hinting(font_hinting);
- FontJapanese->set_force_autohinter(true); //just looks better..i think?
+ /* Noto Sans */
+
+ Ref<FontData> DefaultFont = load_cached_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> DefaultFontBold = load_cached_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontArabic = load_cached_internal_font(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontArabicBold = load_cached_internal_font(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontBengali = load_cached_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontBengaliBold = load_cached_internal_font(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontDevanagari = load_cached_internal_font(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontDevanagariBold = load_cached_internal_font(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontGeorgian = load_cached_internal_font(_font_NotoSansGeorgian_Regular, _font_NotoSansGeorgian_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontGeorgianBold = load_cached_internal_font(_font_NotoSansGeorgian_Bold, _font_NotoSansGeorgian_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontHebrew = load_cached_internal_font(_font_NotoSansHebrew_Regular, _font_NotoSansHebrew_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontHebrewBold = load_cached_internal_font(_font_NotoSansHebrew_Bold, _font_NotoSansHebrew_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontMalayalam = load_cached_internal_font(_font_NotoSansMalayalamUI_Regular, _font_NotoSansMalayalamUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontMalayalamBold = load_cached_internal_font(_font_NotoSansMalayalamUI_Bold, _font_NotoSansMalayalamUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontOriya = load_cached_internal_font(_font_NotoSansOriyaUI_Regular, _font_NotoSansOriyaUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontOriyaBold = load_cached_internal_font(_font_NotoSansOriyaUI_Bold, _font_NotoSansOriyaUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontSinhala = load_cached_internal_font(_font_NotoSansSinhalaUI_Regular, _font_NotoSansSinhalaUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontSinhalaBold = load_cached_internal_font(_font_NotoSansSinhalaUI_Bold, _font_NotoSansSinhalaUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontTamil = load_cached_internal_font(_font_NotoSansTamilUI_Regular, _font_NotoSansTamilUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontTamilBold = load_cached_internal_font(_font_NotoSansTamilUI_Bold, _font_NotoSansTamilUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontTelugu = load_cached_internal_font(_font_NotoSansTeluguUI_Regular, _font_NotoSansTeluguUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontTeluguBold = load_cached_internal_font(_font_NotoSansTeluguUI_Bold, _font_NotoSansTeluguUI_Bold_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontThai = load_cached_internal_font(_font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontThaiBold = load_cached_internal_font(_font_NotoSansThaiUI_Bold, _font_NotoSansThaiUI_Bold_size, font_hinting, font_antialiased, true);
+
+ /* Droid Sans */
+
+ Ref<FontData> FontFallback = load_cached_internal_font(_font_DroidSansFallback, _font_DroidSansFallback_size, font_hinting, font_antialiased, true);
+ Ref<FontData> FontJapanese = load_cached_internal_font(_font_DroidSansJapanese, _font_DroidSansJapanese_size, font_hinting, font_antialiased, true);
/* Hack */
- Ref<FontData> dfmono;
- dfmono.instantiate();
- dfmono->load_memory(_font_Hack_Regular, _font_Hack_Regular_size, "ttf", default_font_size);
- dfmono->set_antialiased(font_antialiased);
- dfmono->set_hinting(font_hinting);
-
- Vector<String> subtag = String(EditorSettings::get_singleton()->get("interface/editor/code_font_custom_variations")).split(",");
- Dictionary ftrs;
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- if (subtag_a.size() == 2) {
- dfmono->set_variation(subtag_a[0], subtag_a[1].to_float());
- }
- }
+ Ref<FontData> dfmono = load_cached_internal_font(_font_Hack_Regular, _font_Hack_Regular_size, font_hinting, font_antialiased, true);
// Default font
- MAKE_DEFAULT_FONT(df);
+ MAKE_DEFAULT_FONT(df, String(), default_font_size);
p_theme->set_default_theme_font(df); // Default theme font
p_theme->set_default_theme_font_size(default_font_size);
@@ -404,7 +283,7 @@ void editor_register_fonts(Ref<Theme> p_theme) {
p_theme->set_font("main", "EditorFonts", df);
// Bold font
- MAKE_BOLD_FONT(df_bold);
+ MAKE_BOLD_FONT(df_bold, String(), default_font_size);
p_theme->set_font_size("bold_size", "EditorFonts", default_font_size);
p_theme->set_font("bold", "EditorFonts", df_bold);
@@ -430,7 +309,8 @@ void editor_register_fonts(Ref<Theme> p_theme) {
p_theme->set_font_size("font_size", "HeaderLarge", default_font_size + 3 * EDSCALE);
// Documentation fonts
- MAKE_SOURCE_FONT(df_code);
+ String code_font_custom_variations = EditorSettings::get_singleton()->get("interface/editor/code_font_custom_variations");
+ MAKE_SOURCE_FONT(df_code, code_font_custom_variations, default_font_size);
p_theme->set_font_size("doc_size", "EditorFonts", int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE);
p_theme->set_font("doc", "EditorFonts", df);
p_theme->set_font_size("doc_bold_size", "EditorFonts", int(EDITOR_GET("text_editor/help/help_font_size")) * EDSCALE);
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 16151e36de..8c935b846a 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -30,6 +30,7 @@
#include "editor_help.h"
+#include "core/core_constants.h"
#include "core/input/input.h"
#include "core/os/keyboard.h"
#include "doc_data_compressed.gen.h"
@@ -116,7 +117,7 @@ void EditorHelp::_class_desc_select(const String &p_select) {
} else {
if (table->has(link)) {
// Found in the current page
- class_desc->scroll_to_line((*table)[link]);
+ class_desc->scroll_to_paragraph((*table)[link]);
} else {
if (topic == "class_enum") {
// Try to find the enum in @GlobalScope
@@ -695,7 +696,7 @@ void EditorHelp::_update_doc() {
class_desc->pop(); //cell
}
- if (m[i].description != "") {
+ if (m[i].description != "" || m[i].errors_returned.size() > 0) {
method_descr = true;
}
@@ -1227,6 +1228,31 @@ void EditorHelp::_update_doc() {
class_desc->push_color(text_color);
class_desc->push_font(doc_font);
class_desc->push_indent(1);
+ if (methods_filtered[i].errors_returned.size()) {
+ class_desc->append_bbcode(TTR("Error codes returned:"));
+ class_desc->add_newline();
+ class_desc->push_list(0, RichTextLabel::LIST_DOTS, false);
+ for (int j = 0; j < methods_filtered[i].errors_returned.size(); j++) {
+ if (j > 0) {
+ class_desc->add_newline();
+ }
+ int val = methods_filtered[i].errors_returned[j];
+ String text = itos(val);
+ for (int k = 0; k < CoreConstants::get_global_constant_count(); k++) {
+ if (CoreConstants::get_global_constant_value(k) == val && CoreConstants::get_global_constant_enum(k) == SNAME("Error")) {
+ text = CoreConstants::get_global_constant_name(k);
+ break;
+ }
+ }
+
+ class_desc->push_bold();
+ class_desc->append_bbcode(text);
+ class_desc->pop();
+ }
+ class_desc->pop();
+ class_desc->add_newline();
+ class_desc->add_newline();
+ }
if (!methods_filtered[i].description.strip_edges().is_empty()) {
_add_text(DTR(methods_filtered[i].description));
} else {
@@ -1302,6 +1328,8 @@ void EditorHelp::_help_callback(const String &p_topic) {
} else if (what == "class_global") {
if (constant_line.has(name)) {
line = constant_line[name];
+ } else if (method_line.has(name)) {
+ line = method_line[name];
} else {
Map<String, Map<String, int>>::Element *iter = enum_values_line.front();
while (true) {
@@ -1317,7 +1345,7 @@ void EditorHelp::_help_callback(const String &p_topic) {
}
}
- class_desc->call_deferred(SNAME("scroll_to_line"), line);
+ class_desc->call_deferred(SNAME("scroll_to_paragraph"), line);
}
static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt) {
@@ -1625,7 +1653,7 @@ Vector<Pair<String, int>> EditorHelp::get_sections() {
void EditorHelp::scroll_to_section(int p_section_index) {
int line = section_line[p_section_index].second;
- class_desc->scroll_to_line(line);
+ class_desc->scroll_to_paragraph(line);
}
void EditorHelp::popup_search() {
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 97cb9b6f85..7631e425e8 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -31,11 +31,13 @@
#include "editor_inspector.h"
#include "array_property_edit.h"
+#include "core/os/keyboard.h"
#include "dictionary_property_edit.h"
#include "editor/doc_tools.h"
#include "editor_feature_profile.h"
#include "editor_node.h"
#include "editor_scale.h"
+#include "editor_settings.h"
#include "multi_node_edit.h"
#include "scene/resources/packed_scene.h"
@@ -244,9 +246,9 @@ void EditorProperty::_notification(int p_what) {
Color color;
if (draw_red) {
- color = get_theme_color(SNAME("error_color"));
+ color = get_theme_color(is_read_only() ? SNAME("readonly_error_color") : SNAME("error_color"));
} else {
- color = get_theme_color(SNAME("property_color"));
+ color = get_theme_color(is_read_only() ? SNAME("readonly_color") : SNAME("property_color"));
}
if (label.find(".") != -1) {
color.a = 0.5; //this should be un-hacked honestly, as it's used for editor overrides
@@ -281,7 +283,7 @@ void EditorProperty::_notification(int p_what) {
check_rect = Rect2();
}
- if (can_revert) {
+ if (can_revert && !is_read_only()) {
Ref<Texture2D> reload_icon = get_theme_icon(SNAME("ReloadSmall"), SNAME("EditorIcons"));
text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")) * 2;
revert_rect = Rect2(text_limit + get_theme_constant(SNAME("hseparator"), SNAME("Tree")), (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height());
@@ -382,8 +384,12 @@ void EditorProperty::update_property() {
GDVIRTUAL_CALL(_update_property);
}
+void EditorProperty::_set_read_only(bool p_read_only) {
+}
+
void EditorProperty::set_read_only(bool p_read_only) {
read_only = p_read_only;
+ _set_read_only(p_read_only);
}
bool EditorProperty::is_read_only() const {
@@ -782,9 +788,46 @@ void EditorProperty::gui_input(const Ref<InputEvent> &p_event) {
update();
emit_signal(SNAME("property_checked"), property, checked);
}
+ } else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ _ensure_popup();
+ menu->set_position(get_screen_position() + get_local_mouse_position());
+ menu->set_size(Vector2(1, 1));
+ menu->popup();
+ select();
+ return;
+ }
+}
+
+void EditorProperty::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ if (!selected || !p_event->is_pressed()) {
+ return;
+ }
+
+ if (ED_IS_SHORTCUT("property_editor/copy_property", p_event)) {
+ menu_option(MENU_COPY_PROPERTY);
+ accept_event();
+ } else if (ED_IS_SHORTCUT("property_editor/paste_property", p_event) && !is_read_only()) {
+ menu_option(MENU_PASTE_PROPERTY);
+ accept_event();
+ } else if (ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) {
+ menu_option(MENU_COPY_PROPERTY_PATH);
+ accept_event();
}
}
+const Color *EditorProperty::_get_property_colors() {
+ const Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const float saturation = base.get_s() * 0.75;
+ const float value = base.get_v();
+
+ static Color c[4];
+ c[0].set_hsv(0.0 / 3.0 + 0.05, saturation, value);
+ c[1].set_hsv(1.0 / 3.0 + 0.05, saturation, value);
+ c[2].set_hsv(2.0 / 3.0 + 0.05, saturation, value);
+ c[3].set_hsv(1.5 / 3.0 + 0.05, saturation, value);
+ return c;
+}
+
void EditorProperty::set_label_reference(Control *p_control) {
label_reference = p_control;
}
@@ -895,6 +938,20 @@ String EditorProperty::get_tooltip_text() const {
return tooltip_text;
}
+void EditorProperty::menu_option(int p_option) {
+ switch (p_option) {
+ case MENU_COPY_PROPERTY: {
+ EditorNode::get_singleton()->get_inspector()->set_property_clipboard(object->get(property));
+ } break;
+ case MENU_PASTE_PROPERTY: {
+ emit_changed(property, EditorNode::get_singleton()->get_inspector()->get_property_clipboard());
+ } break;
+ case MENU_COPY_PROPERTY_PATH: {
+ DisplayServer::get_singleton()->clipboard_set(property);
+ } break;
+ }
+}
+
void EditorProperty::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_label", "text"), &EditorProperty::set_label);
ClassDB::bind_method(D_METHOD("get_label"), &EditorProperty::get_label);
@@ -971,6 +1028,21 @@ EditorProperty::EditorProperty() {
label_reference = nullptr;
bottom_editor = nullptr;
delete_hover = false;
+ menu = nullptr;
+ set_process_unhandled_key_input(true);
+}
+
+void EditorProperty::_ensure_popup() {
+ if (menu) {
+ return;
+ }
+ menu = memnew(PopupMenu);
+ menu->add_shortcut(ED_GET_SHORTCUT("property_editor/copy_property"), MENU_COPY_PROPERTY);
+ menu->add_shortcut(ED_GET_SHORTCUT("property_editor/paste_property"), MENU_PASTE_PROPERTY);
+ menu->add_shortcut(ED_GET_SHORTCUT("property_editor/copy_property_path"), MENU_COPY_PROPERTY_PATH);
+ menu->connect("id_pressed", callable_mp(this, &EditorProperty::menu_option));
+ menu->set_item_disabled(MENU_PASTE_PROPERTY, is_read_only());
+ add_child(menu);
}
////////////////////////////////////////////////
@@ -1125,147 +1197,150 @@ EditorInspectorCategory::EditorInspectorCategory() {
void EditorInspectorSection::_test_unfold() {
if (!vbox_added) {
add_child(vbox);
+ move_child(vbox, 0);
vbox_added = true;
}
}
void EditorInspectorSection::_notification(int p_what) {
- if (p_what == NOTIFICATION_SORT_CHILDREN) {
- Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
- int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
-
- Ref<Texture2D> arrow;
-
- if (foldable) {
- if (object->editor_is_section_unfolded(section)) {
- arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
- } else {
- if (is_layout_rtl()) {
- arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ minimum_size_changed();
+ } break;
+ case NOTIFICATION_SORT_CHILDREN: {
+ if (!vbox_added) {
+ return;
+ }
+ // Get the section header font.
+ Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
+
+ // Get the right direction arrow texture, if the section is foldable.
+ Ref<Texture2D> arrow;
+ if (foldable) {
+ if (object->editor_is_section_unfolded(section)) {
+ arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
} else {
- arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ if (is_layout_rtl()) {
+ arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ } else {
+ arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ }
}
}
- }
- Size2 size = get_size();
- Point2 offset;
- Rect2 rect;
- offset.y = font->get_height(font_size);
- if (arrow.is_valid()) {
- offset.y = MAX(offset.y, arrow->get_height());
- }
-
- offset.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
- if (is_layout_rtl()) {
- rect = Rect2(offset, size - offset - Vector2(get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")), 0));
- } else {
- offset.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor"));
- rect = Rect2(offset, size - offset);
- }
-
- //set children
- for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c) {
- continue;
+ // Compute the height of the section header.
+ int header_height = font->get_height(font_size);
+ if (arrow.is_valid()) {
+ header_height = MAX(header_height, arrow->get_height());
}
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible_in_tree()) {
- continue;
- }
-
- fit_child_in_rect(c, rect);
- }
+ header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
- update(); //need to redraw text
- }
+ int inspector_margin = get_theme_constant(SNAME("inspector_margin"), SNAME("Editor"));
+ Size2 size = get_size() - Vector2(inspector_margin, 0);
+ Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height);
+ for (int i = 0; i < get_child_count(); i++) {
+ Control *c = Object::cast_to<Control>(get_child(i));
+ if (!c) {
+ continue;
+ }
+ if (c->is_set_as_top_level()) {
+ continue;
+ }
- if (p_what == NOTIFICATION_DRAW) {
- Ref<Texture2D> arrow;
- bool rtl = is_layout_rtl();
+ fit_child_in_rect(c, Rect2(offset, size));
+ }
+ } break;
+ case NOTIFICATION_DRAW: {
+ // Get the section header font.
+ Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
- if (foldable) {
- if (object->editor_is_section_unfolded(section)) {
- arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
- } else {
- if (is_layout_rtl()) {
- arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ // Get the right direction arrow texture, if the section is foldable.
+ Ref<Texture2D> arrow;
+ if (foldable) {
+ if (object->editor_is_section_unfolded(section)) {
+ arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
} else {
- arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ if (is_layout_rtl()) {
+ arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
+ } else {
+ arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
+ }
}
}
- }
- Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
- int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
-
- int h = font->get_height(font_size);
- if (arrow.is_valid()) {
- h = MAX(h, arrow->get_height());
- }
- h += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
+ bool rtl = is_layout_rtl();
- Color c = bg_color;
- c.a *= 0.4;
- draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), c);
-
- const int arrow_margin = 2;
- const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0;
- Color color = get_theme_color(SNAME("font_color"));
- float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE);
- draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (h - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color);
-
- if (arrow.is_valid()) {
- if (rtl) {
- draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor());
- } else {
- draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (h - arrow->get_height()) / 2).floor());
+ // Compute the height of the section header.
+ int header_height = font->get_height(font_size);
+ if (arrow.is_valid()) {
+ header_height = MAX(header_height, arrow->get_height());
}
- }
+ header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree"));
- if (dropping && !vbox->is_visible_in_tree()) {
- Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
- draw_rect(Rect2(Point2(), get_size()), accent_color, false);
- }
- }
+ Rect2 header_rect = Rect2(Vector2(), Vector2(get_size().width, header_height));
+ Color c = bg_color;
+ c.a *= 0.4;
+ if (foldable && header_rect.has_point(get_local_mouse_position())) {
+ c = c.lightened(Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT) ? -0.05 : 0.2);
+ }
+ draw_rect(header_rect, c);
- if (p_what == NOTIFICATION_DRAG_BEGIN) {
- Dictionary dd = get_viewport()->gui_get_drag_data();
+ const int arrow_margin = 2;
+ const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0;
+ Color color = get_theme_color(SNAME("font_color"));
+ float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE);
+ draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2).floor(), label, rtl ? HALIGN_RIGHT : HALIGN_LEFT, text_width, font_size, color);
- // Only allow dropping if the section contains properties which can take the dragged data.
- bool children_can_drop = false;
- for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) {
- Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx));
+ if (arrow.is_valid()) {
+ if (rtl) {
+ draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor());
+ } else {
+ draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor());
+ }
+ }
- // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached.
- if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) {
- children_can_drop = true;
- break;
+ if (dropping && !vbox->is_visible_in_tree()) {
+ Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ draw_rect(Rect2(Point2(), get_size()), accent_color, false);
}
- }
+ } break;
+ case NOTIFICATION_DRAG_BEGIN: {
+ Dictionary dd = get_viewport()->gui_get_drag_data();
- dropping = children_can_drop;
- update();
- }
+ // Only allow dropping if the section contains properties which can take the dragged data.
+ bool children_can_drop = false;
+ for (int child_idx = 0; child_idx < vbox->get_child_count(); child_idx++) {
+ Control *editor_property = Object::cast_to<Control>(vbox->get_child(child_idx));
- if (p_what == NOTIFICATION_DRAG_END) {
- dropping = false;
- update();
- }
+ // Test can_drop_data and can_drop_data_fw, since can_drop_data only works if set up with forwarding or if script attached.
+ if (editor_property && (editor_property->can_drop_data(Point2(), dd) || editor_property->call("_can_drop_data_fw", Point2(), dd, this))) {
+ children_can_drop = true;
+ break;
+ }
+ }
- if (p_what == NOTIFICATION_MOUSE_ENTER) {
- if (dropping) {
- dropping_unfold_timer->start();
- }
- }
+ dropping = children_can_drop;
+ update();
+ } break;
+ case NOTIFICATION_DRAG_END: {
+ dropping = false;
+ update();
+ } break;
+ case NOTIFICATION_MOUSE_ENTER: {
+ if (dropping) {
+ dropping_unfold_timer->start();
+ }
+ update();
+ } break;
- if (p_what == NOTIFICATION_MOUSE_EXIT) {
- if (dropping) {
- dropping_unfold_timer->stop();
- }
+ case NOTIFICATION_MOUSE_EXIT: {
+ if (dropping) {
+ dropping_unfold_timer->stop();
+ }
+ update();
+ } break;
}
}
@@ -1304,6 +1379,7 @@ void EditorInspectorSection::setup(const String &p_section, const String &p_labe
if (!foldable && !vbox_added) {
add_child(vbox);
+ move_child(vbox, 0);
vbox_added = true;
}
@@ -1338,6 +1414,8 @@ void EditorInspectorSection::gui_input(const Ref<InputEvent> &p_event) {
} else {
fold();
}
+ } else if (mb.is_valid() && !mb->is_pressed()) {
+ update();
}
}
@@ -1363,7 +1441,7 @@ void EditorInspectorSection::fold() {
}
if (!vbox_added) {
- return; //kinda pointless
+ return;
}
object->editor_set_section_unfold(section, false);
@@ -1400,6 +1478,732 @@ EditorInspectorSection::~EditorInspectorSection() {
////////////////////////////////////////////////
////////////////////////////////////////////////
+int EditorInspectorArray::_get_array_count() {
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ List<PropertyInfo> object_property_list;
+ object->get_property_list(&object_property_list);
+ return _extract_properties_as_array(object_property_list).size();
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ bool valid;
+ int count = object->get(count_property, &valid);
+ ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property));
+ return count;
+ }
+ return 0;
+}
+
+void EditorInspectorArray::_add_button_pressed() {
+ _move_element(-1, -1);
+}
+
+void EditorInspectorArray::_first_page_button_pressed() {
+ emit_signal("page_change_request", 0);
+}
+
+void EditorInspectorArray::_prev_page_button_pressed() {
+ emit_signal("page_change_request", MAX(0, page - 1));
+}
+
+void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) {
+ if (p_text.is_valid_int()) {
+ int new_page = p_text.to_int() - 1;
+ new_page = MIN(MAX(0, new_page), max_page);
+ page_line_edit->set_text(Variant(new_page));
+ emit_signal("page_change_request", new_page);
+ } else {
+ page_line_edit->set_text(Variant(page));
+ }
+}
+
+void EditorInspectorArray::_next_page_button_pressed() {
+ emit_signal("page_change_request", MIN(max_page, page + 1));
+}
+
+void EditorInspectorArray::_last_page_button_pressed() {
+ emit_signal("page_change_request", max_page);
+}
+
+void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) {
+ switch (p_id) {
+ case OPTION_MOVE_UP:
+ if (popup_array_index_pressed > 0) {
+ _move_element(popup_array_index_pressed, popup_array_index_pressed - 1);
+ }
+ break;
+ case OPTION_MOVE_DOWN:
+ if (popup_array_index_pressed < count - 1) {
+ _move_element(popup_array_index_pressed, popup_array_index_pressed + 2);
+ }
+ break;
+ case OPTION_NEW_BEFORE:
+ _move_element(-1, popup_array_index_pressed);
+ break;
+ case OPTION_NEW_AFTER:
+ _move_element(-1, popup_array_index_pressed + 1);
+ break;
+ case OPTION_REMOVE:
+ _move_element(popup_array_index_pressed, -1);
+ break;
+ case OPTION_CLEAR_ARRAY:
+ _clear_array();
+ break;
+ case OPTION_RESIZE_ARRAY:
+ new_size = count;
+ new_size_line_edit->set_text(Variant(new_size));
+ resize_dialog->get_ok_button()->set_disabled(true);
+ resize_dialog->popup_centered();
+ new_size_line_edit->grab_focus();
+ new_size_line_edit->select_all();
+ break;
+ default:
+ break;
+ }
+}
+
+void EditorInspectorArray::_control_dropping_draw() {
+ int drop_position = _drop_position();
+
+ if (dropping && drop_position >= 0) {
+ Vector2 from;
+ Vector2 to;
+ if (drop_position < elements_vbox->get_child_count()) {
+ Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform();
+ from = xform.xform(Vector2());
+ to = xform.xform(Vector2(elements_vbox->get_size().x, 0));
+ } else {
+ Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1));
+ Transform2D xform = child->get_transform();
+ from = xform.xform(Vector2(0, child->get_size().y));
+ to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y));
+ }
+ Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ control_dropping->draw_line(from, to, color, 2);
+ }
+}
+
+void EditorInspectorArray::_vbox_visibility_changed() {
+ control_dropping->set_visible(vbox->is_visible_in_tree());
+}
+
+void EditorInspectorArray::_panel_draw(int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)array_elements.size());
+
+ Ref<StyleBox> style = get_theme_stylebox("Focus", "EditorStyles");
+ if (!style.is_valid()) {
+ return;
+ }
+ if (array_elements[p_index].panel->has_focus()) {
+ array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size()));
+ }
+}
+
+void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)array_elements.size());
+
+ Ref<InputEventKey> key_ref = p_event;
+ if (key_ref.is_valid()) {
+ const InputEventKey &key = **key_ref;
+
+ if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == KEY_DELETE) {
+ _move_element(begin_array_index + p_index, -1);
+ array_elements[p_index].panel->accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ popup_array_index_pressed = begin_array_index + p_index;
+ rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0);
+ rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1);
+ rmb_popup->set_position(mb->get_global_position());
+ rmb_popup->set_size(Vector2());
+ rmb_popup->popup();
+ }
+ }
+}
+
+void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) {
+ String action_name;
+ if (p_element_index < 0) {
+ action_name = vformat("Add element to property array with prefix %s.", array_element_prefix);
+ } else if (p_to_pos < 0) {
+ action_name = vformat("Remove element %d from property array with prefix %s.", p_element_index, array_element_prefix);
+ } else {
+ action_name = vformat("Move element %d to position %d in property array with prefix %s.", p_element_index, p_to_pos, array_element_prefix);
+ }
+ undo_redo->create_action(action_name);
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, p_element_index, p_to_pos };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count);
+ List<PropertyInfo> object_property_list;
+ object->get_property_list(&object_property_list);
+
+ Array properties_as_array = _extract_properties_as_array(object_property_list);
+ properties_as_array.resize(count);
+
+ // For undoing things
+ undo_redo->add_undo_property(object, count_property, properties_as_array.size());
+ for (int i = 0; i < (int)properties_as_array.size(); i++) {
+ Dictionary d = Dictionary(properties_as_array[i]);
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_undo_property(object, vformat(key, i), d[key]);
+ }
+ }
+
+ if (p_element_index < 0) {
+ // Add an element.
+ properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary());
+ } else if (p_to_pos < 0) {
+ // Delete the element.
+ properties_as_array.remove(p_element_index);
+ } else {
+ // Move the element.
+ properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate());
+ properties_as_array.remove(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index);
+ }
+
+ // Change the array size then set the properties.
+ undo_redo->add_do_property(object, count_property, properties_as_array.size());
+ for (int i = 0; i < (int)properties_as_array.size(); i++) {
+ Dictionary d = properties_as_array[i];
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_do_property(object, vformat(key, i), d[key]);
+ }
+ }
+ }
+ undo_redo->commit_action();
+
+ // Handle page change and update counts.
+ if (p_element_index < 0) {
+ int added_index = p_to_pos < 0 ? count : p_to_pos;
+ emit_signal("page_change_request", added_index / page_lenght);
+ count += 1;
+ } else if (p_to_pos < 0) {
+ count -= 1;
+ if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) {
+ emit_signal("page_change_request", max_page - 1);
+ }
+ }
+ begin_array_index = page * page_lenght;
+ end_array_index = MIN(count, (page + 1) * page_lenght);
+ max_page = MAX(0, count - 1) / page_lenght;
+}
+
+void EditorInspectorArray::_clear_array() {
+ undo_redo->create_action(vformat("Clear property array with prefix %s.", array_element_prefix));
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ for (int i = count - 1; i >= 0; i--) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ List<PropertyInfo> object_property_list;
+ object->get_property_list(&object_property_list);
+
+ Array properties_as_array = _extract_properties_as_array(object_property_list);
+ properties_as_array.resize(count);
+
+ // For undoing things
+ undo_redo->add_undo_property(object, count_property, count);
+ for (int i = 0; i < (int)properties_as_array.size(); i++) {
+ Dictionary d = Dictionary(properties_as_array[i]);
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_undo_property(object, vformat(key, i), d[key]);
+ }
+ }
+
+ // Change the array size then set the properties.
+ undo_redo->add_do_property(object, count_property, 0);
+ }
+ undo_redo->commit_action();
+
+ // Handle page change and update counts.
+ emit_signal("page_change_request", 0);
+ count = 0;
+ begin_array_index = 0;
+ end_array_index = 0;
+ max_page = 0;
+}
+
+void EditorInspectorArray::_resize_array(int p_size) {
+ ERR_FAIL_COND(p_size < 0);
+ if (p_size == count) {
+ return;
+ }
+
+ undo_redo->create_action(vformat("Resize property array with prefix %s.", array_element_prefix));
+ if (p_size > count) {
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ for (int i = count; i < p_size; i++) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, -1, -1 };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ undo_redo->add_undo_property(object, count_property, count);
+ undo_redo->add_do_property(object, count_property, p_size);
+ }
+ } else {
+ if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
+ for (int i = count - 1; i > p_size - 1; i--) {
+ // Call the function.
+ Callable move_function = EditorNode::get_singleton()->get_editor_data().get_move_array_element_function(object->get_class_name());
+ if (move_function.is_valid()) {
+ Variant args[] = { (Object *)undo_redo, object, array_element_prefix, i, -1 };
+ const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
+ Variant return_value;
+ Callable::CallError call_error;
+ move_function.call(args_p, 5, return_value, call_error);
+ } else {
+ WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
+ }
+ }
+ } else if (mode == MODE_USE_COUNT_PROPERTY) {
+ List<PropertyInfo> object_property_list;
+ object->get_property_list(&object_property_list);
+
+ Array properties_as_array = _extract_properties_as_array(object_property_list);
+ properties_as_array.resize(count);
+
+ // For undoing things
+ undo_redo->add_undo_property(object, count_property, count);
+ for (int i = count - 1; i > p_size - 1; i--) {
+ Dictionary d = Dictionary(properties_as_array[i]);
+ Array keys = d.keys();
+ for (int j = 0; j < keys.size(); j++) {
+ String key = keys[j];
+ undo_redo->add_undo_property(object, vformat(key, i), d[key]);
+ }
+ }
+
+ // Change the array size then set the properties.
+ undo_redo->add_do_property(object, count_property, p_size);
+ }
+ }
+ undo_redo->commit_action();
+
+ // Handle page change and update counts.
+ emit_signal("page_change_request", 0);
+ /*
+ count = 0;
+ begin_array_index = 0;
+ end_array_index = 0;
+ max_page = 0;
+ */
+}
+
+Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo> &p_list) {
+ Array output;
+
+ for (const PropertyInfo &pi : p_list) {
+ if (pi.name.begins_with(array_element_prefix)) {
+ String str = pi.name.trim_prefix(array_element_prefix);
+
+ int to_char_index = 0;
+ while (to_char_index < str.length()) {
+ if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ break;
+ }
+ to_char_index++;
+ }
+ if (to_char_index > 0) {
+ int array_index = str.left(to_char_index).to_int();
+ Error error = OK;
+ if (array_index >= output.size()) {
+ error = output.resize(array_index + 1);
+ }
+ if (error == OK) {
+ String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index);
+ Dictionary dict = output[array_index];
+ dict[format_string] = object->get(pi.name);
+ output[array_index] = dict;
+ } else {
+ WARN_PRINT(vformat("Array element %s has an index too high. Array allocaiton failed.", pi.name));
+ }
+ }
+ }
+ }
+ return output;
+}
+
+int EditorInspectorArray::_drop_position() const {
+ for (int i = 0; i < (int)array_elements.size(); i++) {
+ const ArrayElement &ae = array_elements[i];
+
+ Size2 size = ae.panel->get_size();
+ Vector2 mp = ae.panel->get_local_mouse_position();
+
+ if (Rect2(Vector2(), size).has_point(mp)) {
+ if (mp.y < size.y / 2) {
+ return i;
+ } else {
+ return i + 1;
+ }
+ }
+ }
+ return -1;
+}
+
+void EditorInspectorArray::_new_size_line_edit_text_changed(String p_text) {
+ bool valid = false;
+ ;
+ if (p_text.is_valid_int()) {
+ int val = p_text.to_int();
+ if (val > 0 && val != count) {
+ valid = true;
+ }
+ }
+ resize_dialog->get_ok_button()->set_disabled(!valid);
+}
+
+void EditorInspectorArray::_new_size_line_edit_text_submitted(String p_text) {
+ bool valid = false;
+ ;
+ if (p_text.is_valid_int()) {
+ int val = p_text.to_int();
+ if (val > 0 && val != count) {
+ new_size = val;
+ valid = true;
+ }
+ }
+ if (valid) {
+ resize_dialog->hide();
+ _resize_array(new_size);
+ } else {
+ new_size_line_edit->set_text(Variant(new_size));
+ }
+}
+
+void EditorInspectorArray::_resize_dialog_confirmed() {
+ _new_size_line_edit_text_submitted(new_size_line_edit->get_text());
+}
+
+void EditorInspectorArray::_setup() {
+ // Setup counts.
+ count = _get_array_count();
+ begin_array_index = page * page_lenght;
+ end_array_index = MIN(count, (page + 1) * page_lenght);
+ max_page = MAX(0, count - 1) / page_lenght;
+ array_elements.resize(MAX(0, end_array_index - begin_array_index));
+ if (page < 0 || page > max_page) {
+ WARN_PRINT(vformat("Invalid page number %d", page));
+ page = CLAMP(page, 0, max_page);
+ }
+
+ for (int i = 0; i < (int)array_elements.size(); i++) {
+ ArrayElement &ae = array_elements[i];
+
+ // Panel and its hbox.
+ ae.panel = memnew(PanelContainer);
+ ae.panel->set_focus_mode(FOCUS_ALL);
+ ae.panel->set_mouse_filter(MOUSE_FILTER_PASS);
+ ae.panel->set_drag_forwarding(this);
+ ae.panel->set_meta("index", begin_array_index + i);
+ ae.panel->set_tooltip(vformat(TTR("Element %d: %s%d*"), i, array_element_prefix, i));
+ ae.panel->connect("focus_entered", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update));
+ ae.panel->connect("focus_exited", callable_mp((CanvasItem *)ae.panel, &PanelContainer::update));
+ ae.panel->connect("draw", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_draw), i));
+ ae.panel->connect("gui_input", callable_bind(callable_mp(this, &EditorInspectorArray::_panel_gui_input), i));
+ ae.panel->add_theme_style_override(SNAME("panel"), i % 2 ? odd_style : even_style);
+ elements_vbox->add_child(ae.panel);
+
+ ae.margin = memnew(MarginContainer);
+ ae.margin->set_mouse_filter(MOUSE_FILTER_PASS);
+ if (is_inside_tree()) {
+ Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size();
+ ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
+ ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
+ }
+ ae.panel->add_child(ae.margin);
+
+ ae.hbox = memnew(HBoxContainer);
+ ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL);
+ ae.margin->add_child(ae.hbox);
+
+ // Move button.
+ ae.move_texture_rect = memnew(TextureRect);
+ ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
+ if (is_inside_tree()) {
+ ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons")));
+ }
+ ae.hbox->add_child(ae.move_texture_rect);
+
+ // Right vbox.
+ ae.vbox = memnew(VBoxContainer);
+ ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL);
+ ae.hbox->add_child(ae.vbox);
+ }
+
+ // Hide/show the add button.
+ add_button->set_visible(page == max_page);
+
+ if (max_page == 0) {
+ hbox_pagination->hide();
+ } else {
+ // Update buttons.
+ first_page_button->set_disabled(page == 0);
+ prev_page_button->set_disabled(page == 0);
+ next_page_button->set_disabled(page == max_page);
+ last_page_button->set_disabled(page == max_page);
+
+ // Update page number and page count.
+ page_line_edit->set_text(vformat("%d", page + 1));
+ page_count_label->set_text(vformat("/ %d", max_page + 1));
+ }
+}
+
+Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
+ int index = p_from->get_meta("index");
+ Dictionary dict;
+ dict["type"] = "property_array_element";
+ dict["property_array_prefix"] = array_element_prefix;
+ dict["index"] = index;
+
+ return dict;
+}
+
+void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
+ Dictionary dict = p_data;
+
+ int to_drop = dict["index"];
+ int drop_position = _drop_position();
+ if (drop_position < 0) {
+ return;
+ }
+ _move_element(to_drop, begin_array_index + drop_position);
+}
+
+bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
+ // First, update drawing.
+ control_dropping->update();
+
+ if (p_data.get_type() != Variant::DICTIONARY) {
+ return false;
+ }
+ Dictionary dict = p_data;
+ int drop_position = _drop_position();
+ if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) {
+ return false;
+ }
+
+ // Check in dropping at the given index does indeed move the item.
+ int moved_array_index = (int)dict["index"];
+ int drop_array_index = begin_array_index + drop_position;
+
+ return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index;
+}
+
+void EditorInspectorArray::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ Color color = get_theme_color(SNAME("dark_color_1"), SNAME("Editor"));
+ odd_style->set_bg_color(color.lightened(0.15));
+ even_style->set_bg_color(color.darkened(0.15));
+
+ for (int i = 0; i < (int)array_elements.size(); i++) {
+ ArrayElement &ae = array_elements[i];
+ ae.move_texture_rect->set_texture(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons")));
+
+ Size2 min_size = get_theme_stylebox("Focus", "EditorStyles")->get_minimum_size();
+ ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
+ ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
+ ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
+ }
+
+ add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+ first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons")));
+ prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons")));
+ next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons")));
+ last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons")));
+ minimum_size_changed();
+ } break;
+ case NOTIFICATION_DRAG_BEGIN: {
+ Dictionary dict = get_viewport()->gui_get_drag_data();
+ if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) {
+ dropping = true;
+ control_dropping->update();
+ }
+ } break;
+ case NOTIFICATION_DRAG_END: {
+ if (dropping) {
+ dropping = false;
+ control_dropping->update();
+ }
+ } break;
+ }
+}
+
+void EditorInspectorArray::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &EditorInspectorArray::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &EditorInspectorArray::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &EditorInspectorArray::drop_data_fw);
+
+ ADD_SIGNAL(MethodInfo("page_change_request"));
+}
+
+void EditorInspectorArray::set_undo_redo(UndoRedo *p_undo_redo) {
+ undo_redo = p_undo_redo;
+}
+
+void EditorInspectorArray::setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) {
+ count_property = "";
+ mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION;
+ array_element_prefix = p_array_element_prefix;
+ page = p_page;
+
+ EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable);
+
+ _setup();
+}
+
+void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable) {
+ count_property = p_count_property;
+ mode = MODE_USE_COUNT_PROPERTY;
+ array_element_prefix = p_array_element_prefix;
+ page = p_page;
+
+ EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable);
+
+ _setup();
+}
+
+VBoxContainer *EditorInspectorArray::get_vbox(int p_index) {
+ if (p_index >= begin_array_index && p_index < end_array_index) {
+ return array_elements[p_index - begin_array_index].vbox;
+ } else if (p_index < 0) {
+ return vbox;
+ } else {
+ return nullptr;
+ }
+}
+
+EditorInspectorArray::EditorInspectorArray() {
+ set_mouse_filter(Control::MOUSE_FILTER_STOP);
+
+ odd_style.instantiate();
+ even_style.instantiate();
+
+ rmb_popup = memnew(PopupMenu);
+ rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP);
+ rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN);
+ rmb_popup->add_separator();
+ rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE);
+ rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER);
+ rmb_popup->add_separator();
+ rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE);
+ rmb_popup->add_separator();
+ rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY);
+ rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY);
+ rmb_popup->connect("id_pressed", callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed));
+ add_child(rmb_popup);
+
+ elements_vbox = memnew(VBoxContainer);
+ elements_vbox->add_theme_constant_override("separation", 0);
+ vbox->add_child(elements_vbox);
+
+ add_button = memnew(Button);
+ add_button->set_text(TTR("Add Element"));
+ add_button->set_text_align(Button::ALIGN_CENTER);
+ add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed));
+ vbox->add_child(add_button);
+
+ hbox_pagination = memnew(HBoxContainer);
+ hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL);
+ hbox_pagination->set_alignment(HBoxContainer::ALIGN_CENTER);
+ vbox->add_child(hbox_pagination);
+
+ first_page_button = memnew(Button);
+ first_page_button->set_flat(true);
+ first_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_first_page_button_pressed));
+ hbox_pagination->add_child(first_page_button);
+
+ prev_page_button = memnew(Button);
+ prev_page_button->set_flat(true);
+ prev_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_prev_page_button_pressed));
+ hbox_pagination->add_child(prev_page_button);
+
+ page_line_edit = memnew(LineEdit);
+ page_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_page_line_edit_text_submitted));
+ page_line_edit->add_theme_constant_override("minimum_character_width", 2);
+ hbox_pagination->add_child(page_line_edit);
+
+ page_count_label = memnew(Label);
+ hbox_pagination->add_child(page_count_label);
+ next_page_button = memnew(Button);
+ next_page_button->set_flat(true);
+ next_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_next_page_button_pressed));
+ hbox_pagination->add_child(next_page_button);
+
+ last_page_button = memnew(Button);
+ last_page_button->set_flat(true);
+ last_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_last_page_button_pressed));
+ hbox_pagination->add_child(last_page_button);
+
+ control_dropping = memnew(Control);
+ control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw));
+ control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ add_child(control_dropping);
+
+ resize_dialog = memnew(AcceptDialog);
+ resize_dialog->set_title(TTRC("Resize Array"));
+ resize_dialog->add_cancel_button();
+ resize_dialog->connect("confirmed", callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed));
+ add_child(resize_dialog);
+
+ VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer);
+ resize_dialog->add_child(resize_dialog_vbox);
+
+ new_size_line_edit = memnew(LineEdit);
+ new_size_line_edit->connect("text_changed", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_changed));
+ new_size_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_new_size_line_edit_text_submitted));
+ resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_line_edit);
+
+ vbox->connect("visibility_changed", callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed));
+}
+
+////////////////////////////////////////////////
+////////////////////////////////////////////////
Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS];
int EditorInspector::inspector_plugin_count = 0;
@@ -1585,18 +2389,17 @@ void EditorInspector::update_tree() {
valid_plugins.push_back(inspector_plugins[i]);
}
+ // Decide if properties should be drawn in red.
bool draw_red = false;
-
if (is_inside_tree()) {
Node *nod = Object::cast_to<Node>(object);
Node *es = EditorNode::get_singleton()->get_edited_scene();
if (nod && es != nod && nod->get_owner() != es) {
+ // Draw in red edited nodes that are not in the currently edited scene.
draw_red = true;
}
}
- // TreeItem *current_category = nullptr;
-
String filter = search_box ? search_box->get_text() : "";
String group;
String group_base;
@@ -1608,30 +2411,30 @@ void EditorInspector::update_tree() {
object->get_property_list(&plist, true);
_update_script_class_properties(*object, plist);
- HashMap<String, VBoxContainer *> item_path;
- Map<VBoxContainer *, EditorInspectorSection *> section_map;
-
- item_path[""] = main_vbox;
+ Map<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path;
+ Map<String, EditorInspectorArray *> editor_inspector_array_per_prefix;
Color sscolor = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
+ // Get the lists of editors to add the beginning.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_begin(object);
_parse_added_editors(main_vbox, ped);
}
- for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) {
- PropertyInfo &p = I->get();
-
- //make sure the property can be edited
+ // Get the lists of editors for properties.
+ for (List<PropertyInfo>::Element *E_property = plist.front(); E_property; E_property = E_property->next()) {
+ PropertyInfo &p = E_property->get();
if (p.usage & PROPERTY_USAGE_SUBGROUP) {
+ // Setup a property sub-group.
subgroup = p.name;
subgroup_base = p.hint_string;
continue;
} else if (p.usage & PROPERTY_USAGE_GROUP) {
+ // Setup a property group.
group = p.name;
group_base = p.hint_string;
subgroup = "";
@@ -1640,6 +2443,7 @@ void EditorInspector::update_tree() {
continue;
} else if (p.usage & PROPERTY_USAGE_CATEGORY) {
+ // Setup a property category.
group = "";
group_base = "";
subgroup = "";
@@ -1649,9 +2453,9 @@ void EditorInspector::update_tree() {
continue;
}
- List<PropertyInfo>::Element *N = I->next();
+ // Iterate over remaining properties. If no properties in category, skip the category.
+ List<PropertyInfo>::Element *N = E_property->next();
bool valid = true;
- //if no properties in category, skip
while (N) {
if (N->get().usage & PROPERTY_USAGE_EDITOR && (!restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {
break;
@@ -1663,28 +2467,32 @@ void EditorInspector::update_tree() {
N = N->next();
}
if (!valid) {
- continue; //empty, ignore
+ continue; // Empty, ignore it.
}
+ // Create an EditorInspectorCategory and add it to the inspector.
EditorInspectorCategory *category = memnew(EditorInspectorCategory);
main_vbox->add_child(category);
category_vbox = nullptr; //reset
String type = p.name;
+
+ // Set the category icon.
if (!ClassDB::class_exists(type) && !ScriptServer::is_global_class(type) && p.hint_string.length() && FileAccess::exists(p.hint_string)) {
- Ref<Script> s = ResourceLoader::load(p.hint_string, "Script");
+ // If we have a category inside a script, search for the first script with a valid icon.
+ Ref<Script> script = ResourceLoader::load(p.hint_string, "Script");
String base_type;
- if (s.is_valid()) {
- base_type = s->get_instance_base_type();
+ if (script.is_valid()) {
+ base_type = script->get_instance_base_type();
}
- while (s.is_valid()) {
- StringName name = EditorNode::get_editor_data().script_class_get_name(s->get_path());
+ while (script.is_valid()) {
+ StringName name = EditorNode::get_editor_data().script_class_get_name(script->get_path());
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
if (name != StringName() && icon_path.length()) {
category->icon = ResourceLoader::load(icon_path, "Texture");
break;
}
- s = s->get_base_script();
+ script = script->get_base_script();
}
if (category->icon.is_null() && has_theme_icon(base_type, SNAME("EditorIcons"))) {
category->icon = get_theme_icon(base_type, SNAME("EditorIcons"));
@@ -1695,9 +2503,12 @@ void EditorInspector::update_tree() {
category->icon = EditorNode::get_singleton()->get_class_icon(type, "Object");
}
}
+
+ // Set the category label.
category->label = type;
if (use_doc_hints) {
+ // Sets the category tooltip to show documentation.
StringName type2 = p.name;
if (!class_descr_cache.has(type2)) {
String descr;
@@ -1712,6 +2523,7 @@ void EditorInspector::update_tree() {
category->set_tooltip(p.name + "::" + (class_descr_cache[type2] == "" ? "" : class_descr_cache[type2]));
}
+ // Add editors at the start of a category.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_category(object, p.name);
_parse_added_editors(main_vbox, ped);
@@ -1720,134 +2532,215 @@ void EditorInspector::update_tree() {
continue;
} else if (!(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) || (restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {
+ // Ignore properties that are not supposed to be in the inspector.
continue;
}
if (p.name == "script") {
- category_vbox = nullptr; // script should go into its own category
+ // Script should go into its own category.
+ category_vbox = nullptr;
}
if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) {
- continue; //do not show this property in low end gfx
+ // Do not show this property in low end gfx.
+ continue;
}
if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) {
+ // Hide script variables from inspector if required.
continue;
}
- String basename = p.name;
+ // Get the path for property.
+ String path = p.name;
- if (subgroup != "") {
- if (subgroup_base != "") {
- if (basename.begins_with(subgroup_base)) {
- basename = basename.replace_first(subgroup_base, "");
- } else if (subgroup_base.begins_with(basename)) {
- //keep it, this is used pretty often
- } else {
- subgroup = ""; //no longer using subgroup base, clear
+ // First check if we have an array that fits the prefix.
+ String array_prefix = "";
+ int array_index = -1;
+ for (Map<String, EditorInspectorArray *>::Element *E = editor_inspector_array_per_prefix.front(); E; E = E->next()) {
+ if (p.name.begins_with(E->key()) && E->key().length() > array_prefix.length()) {
+ array_prefix = E->key();
+ }
+ }
+
+ if (!array_prefix.is_empty()) {
+ // If we have an array element, find the according index in array.
+ String str = p.name.trim_prefix(array_prefix);
+ int to_char_index = 0;
+ while (to_char_index < str.length()) {
+ if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ break;
}
+ to_char_index++;
+ }
+ if (to_char_index > 0) {
+ array_index = str.left(to_char_index).to_int();
+ } else {
+ array_prefix = "";
}
}
- if (group != "") {
- if (group_base != "" && subgroup == "") {
- if (basename.begins_with(group_base)) {
- basename = basename.replace_first(group_base, "");
- } else if (group_base.begins_with(basename)) {
- //keep it, this is used pretty often
+
+ if (!array_prefix.is_empty()) {
+ path = path.trim_prefix(array_prefix);
+ int char_index = path.find("/");
+ if (char_index >= 0) {
+ path = path.right(-char_index - 1);
+ } else {
+ path = vformat(TTR("Element %s"), array_index);
+ }
+ } else {
+ // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string.
+ if (subgroup != "" && subgroup_base != "") {
+ if (path.begins_with(subgroup_base)) {
+ path = path.trim_prefix(subgroup_base);
+ } else if (subgroup_base.begins_with(path)) {
+ // Keep it, this is used pretty often.
+ } else {
+ subgroup = ""; // The prefix changed, we are no longer in the subgroup.
+ }
+ }
+
+ // Check if we exit or not a group. If there is a prefix, remove it from the property label string.
+ if (group != "" && group_base != "" && subgroup == "") {
+ if (path.begins_with(group_base)) {
+ path = path.trim_prefix(group_base);
+ } else if (group_base.begins_with(path)) {
+ // Keep it, this is used pretty often.
} else {
- group = ""; //no longer using group base, clear
+ group = ""; // The prefix changed, we are no longer in the group.
subgroup = "";
}
}
- }
- if (subgroup != "") {
- basename = subgroup + "/" + basename;
- }
- if (group != "") {
- basename = group + "/" + basename;
- }
- String name = (basename.find("/") != -1) ? basename.substr(basename.rfind("/") + 1) : basename;
+ // Add the group and subgroup to the path.
+ if (subgroup != "") {
+ path = subgroup + "/" + path;
+ }
+ if (group != "") {
+ path = group + "/" + path;
+ }
+ }
+ // Get the property label's string.
+ String property_label_string = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path;
if (capitalize_paths) {
- int dot = name.find(".");
+ // Capitalize paths.
+ int dot = property_label_string.find(".");
if (dot != -1) {
- String ov = name.substr(dot);
- name = name.substr(0, dot);
- name = name.capitalize();
- name += ov;
-
+ String ov = property_label_string.substr(dot);
+ property_label_string = property_label_string.substr(0, dot);
+ property_label_string = property_label_string.capitalize();
+ property_label_string += ov;
} else {
- name = name.capitalize();
+ property_label_string = property_label_string.capitalize();
}
}
- String path;
- {
- int idx = basename.rfind("/");
- if (idx > -1) {
- path = basename.left(idx);
- }
+ // Remove the property from the path.
+ int idx = path.rfind("/");
+ if (idx > -1) {
+ path = path.left(idx);
+ } else {
+ path = "";
}
+ // Ignore properties that do not fit the filter.
if (use_filter && filter != "") {
- String cat = path;
-
- if (capitalize_paths) {
- cat = cat.capitalize();
- }
-
- if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name) && property_prefix.to_lower().find(filter.to_lower()) == -1) {
+ if (!filter.is_subsequence_ofi(path) && !filter.is_subsequence_ofi(property_label_string) && property_prefix.to_lower().find(filter.to_lower()) == -1) {
continue;
}
}
+ // Recreate the category vbox if it was reset.
if (category_vbox == nullptr) {
category_vbox = memnew(VBoxContainer);
main_vbox->add_child(category_vbox);
}
- VBoxContainer *current_vbox = main_vbox;
+ // Find the correct section/vbox to add the property editor to.
+ VBoxContainer *root_vbox = array_prefix.is_empty() ? main_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index);
+ if (!root_vbox) {
+ continue;
+ }
- {
- String acc_path = "";
- int level = 1;
- for (int i = 0; i < path.get_slice_count("/"); i++) {
- String path_name = path.get_slice("/", i);
- if (i > 0) {
- acc_path += "/";
- }
- acc_path += path_name;
- if (!item_path.has(acc_path)) {
- EditorInspectorSection *section = memnew(EditorInspectorSection);
- current_vbox->add_child(section);
- sections.push_back(section);
-
- if (capitalize_paths) {
- path_name = path_name.capitalize();
- }
+ if (!vbox_per_path.has(root_vbox)) {
+ vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>();
+ vbox_per_path[root_vbox][""] = root_vbox;
+ }
+
+ VBoxContainer *current_vbox = root_vbox;
+ String acc_path = "";
+ int level = 1;
- Color c = sscolor;
- c.a /= level;
- section->setup(acc_path, path_name, object, c, use_folding);
+ Vector<String> components = path.split("/");
+ for (int i = 0; i < components.size(); i++) {
+ String component = components[i];
+ acc_path += (i > 0) ? "/" + component : component;
- VBoxContainer *vb = section->get_vbox();
- item_path[acc_path] = vb;
- section_map[vb] = section;
+ if (!vbox_per_path[root_vbox].has(acc_path)) {
+ // If the section does not exists, create it.
+ EditorInspectorSection *section = memnew(EditorInspectorSection);
+ current_vbox->add_child(section);
+ sections.push_back(section);
+
+ if (capitalize_paths) {
+ component = component.capitalize();
}
- current_vbox = item_path[acc_path];
- level = (MIN(level + 1, 4));
- }
- if (current_vbox == main_vbox) {
- //do not add directly to the main vbox, given it has no spacing
- if (category_vbox == nullptr) {
- category_vbox = memnew(VBoxContainer);
+ Color c = sscolor;
+ c.a /= level;
+ section->setup(acc_path, component, object, c, use_folding);
+
+ vbox_per_path[root_vbox][acc_path] = section->get_vbox();
+ }
+
+ current_vbox = vbox_per_path[root_vbox][acc_path];
+ level = (MIN(level + 1, 4));
+ }
+
+ // If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly).
+ if (current_vbox == main_vbox) {
+ current_vbox = category_vbox;
+ }
+
+ // Check if the property is an array counter, if so create a dedicated array editor for the array.
+ if (p.usage & PROPERTY_USAGE_ARRAY) {
+ EditorInspectorArray *editor_inspector_array = nullptr;
+ StringName array_element_prefix;
+ Color c = sscolor;
+ c.a /= level;
+ if (p.type == Variant::NIL) {
+ // Setup the array to use a method to create/move/delete elements.
+ array_element_prefix = p.class_name;
+ editor_inspector_array = memnew(EditorInspectorArray);
+
+ String array_label = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path;
+ array_label = property_label_string.capitalize();
+ int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;
+ editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding);
+ editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix));
+ editor_inspector_array->set_undo_redo(undo_redo);
+ } else if (p.type == Variant::INT) {
+ // Setup the array to use the count property and built-in functions to create/move/delete elements.
+ Vector<String> class_name_components = String(p.class_name).split(",");
+ if (class_name_components.size() == 2) {
+ array_element_prefix = class_name_components[1];
+ editor_inspector_array = memnew(EditorInspectorArray);
+ int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;
+ editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, use_folding);
+ editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request), varray(array_element_prefix));
+ editor_inspector_array->set_undo_redo(undo_redo);
}
- current_vbox = category_vbox;
}
+
+ if (editor_inspector_array) {
+ current_vbox->add_child(editor_inspector_array);
+ editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array;
+ }
+ continue;
}
+ // Checkable and checked properties.
bool checkable = false;
bool checked = false;
if (p.usage & PROPERTY_USAGE_CHECKABLE) {
@@ -1855,6 +2748,9 @@ void EditorInspector::update_tree() {
checked = p.usage & PROPERTY_USAGE_CHECKED;
}
+ bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only;
+
+ // Mark properties that would require an editor restart (mostly when editing editor settings).
if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) {
restart_request_props.insert(p.name);
}
@@ -1862,14 +2758,19 @@ void EditorInspector::update_tree() {
String doc_hint;
if (use_doc_hints) {
+ // Build the doc hint, to use as tooltip.
+
+ // Get the class name.
StringName classname = object->get_class_name();
if (object_class != String()) {
classname = object_class;
}
+
StringName propname = property_prefix + p.name;
String descr;
bool found = false;
+ // Search for the property description in the cache.
Map<StringName, Map<StringName, String>>::Element *E = descr_cache.find(classname);
if (E) {
Map<StringName, String>::Element *F = E->get().find(propname);
@@ -1880,6 +2781,7 @@ void EditorInspector::update_tree() {
}
if (!found) {
+ // Build the property description String and add it to the cache.
DocTools *dd = EditorHelp::get_doc_data();
Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(classname);
while (F && descr == String()) {
@@ -1912,17 +2814,18 @@ void EditorInspector::update_tree() {
doc_hint = descr;
}
+ // Seach for the inspector plugin that will handle the properties. Then add the correct property editor to it.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors);
- List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector
+ List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; // Make a copy, since plugins may be used again in a sub-inspector.
ped->added_editors.clear();
for (const EditorInspectorPlugin::AddedEditor &F : editors) {
EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor);
if (ep) {
- //set all this before the control gets the ENTER_TREE notification
+ // Set all this before the control gets the ENTER_TREE notification.
ep->object = object;
if (F.properties.size()) {
@@ -1937,7 +2840,7 @@ void EditorInspector::update_tree() {
ep->set_label(F.label);
} else {
// Use the existing one.
- ep->set_label(name);
+ ep->set_label(property_label_string);
}
for (int i = 0; i < F.properties.size(); i++) {
String prop = F.properties[i];
@@ -1953,18 +2856,16 @@ void EditorInspector::update_tree() {
ep->set_checkable(checkable);
ep->set_checked(checked);
ep->set_keying(keying);
-
- ep->set_read_only(read_only);
+ ep->set_read_only(property_read_only);
ep->set_deletable(deletable_properties);
}
current_vbox->add_child(F.property_editor);
if (ep) {
- ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed));
- if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) {
- ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed_update_all), varray(), CONNECT_DEFERRED);
- }
+ // Eventually, set other properties/signals after the property editor got added to the tree.
+ bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED);
+ ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed), varray(update_all));
ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));
ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), varray(), CONNECT_DEFERRED);
ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
@@ -1989,17 +2890,17 @@ void EditorInspector::update_tree() {
}
if (exclusive) {
+ // If we know the plugin is exclusive, we don't need to go through other plugins.
break;
}
}
}
+ // Get the lists of to add at the end.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_end();
_parse_added_editors(main_vbox, ped);
}
-
- //see if this property exists and should be kept
}
void EditorInspector::update_property(const String &p_prop) {
@@ -2038,6 +2939,7 @@ void EditorInspector::edit(Object *p_object) {
_clear();
object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
}
+ per_array_page.clear();
object = p_object;
@@ -2183,6 +3085,15 @@ void EditorInspector::set_use_deletable_properties(bool p_enabled) {
deletable_properties = p_enabled;
}
+void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) {
+ int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0;
+ int new_page = MAX(0, p_new_page);
+ if (new_page != prev_page) {
+ per_array_page[p_array_prefix] = new_page;
+ update_tree_pending = true;
+ }
+}
+
void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) {
if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore
return;
@@ -2283,14 +3194,14 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo
}
}
-void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) {
+void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) {
// The "changing" variable must be true for properties that trigger events as typing occurs,
// like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc.
if (p_changing) {
this->changing++;
}
- _edit_set(p_path, p_value, false, p_name);
+ _edit_set(p_path, p_value, p_update_all, p_name);
if (p_changing) {
this->changing--;
@@ -2301,11 +3212,7 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v
}
}
-void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) {
- update_tree();
-}
-
-void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) {
+void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) {
ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0);
ERR_FAIL_COND(p_paths.size() != p_values.size());
String names;
@@ -2322,9 +3229,13 @@ void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array
emit_signal(SNAME("restart_requested"));
}
}
- changing++;
+ if (p_changing) {
+ changing++;
+ }
undo_redo->commit_action();
- changing--;
+ if (p_changing) {
+ changing--;
+ }
}
void EditorInspector::_property_keyed(const String &p_path, bool p_advance) {
@@ -2601,13 +3512,15 @@ void EditorInspector::_update_script_class_properties(const Object &p_object, Li
}
// NodeC -> C props... -> NodeB..C..
- r_list.erase(script_variables);
- List<PropertyInfo>::Element *to_delete = bottom->next();
- while (to_delete && !(to_delete->get().usage & PROPERTY_USAGE_CATEGORY)) {
- r_list.erase(to_delete);
- to_delete = bottom->next();
+ if (script_variables) {
+ r_list.erase(script_variables);
+ List<PropertyInfo>::Element *to_delete = bottom->next();
+ while (to_delete && !(to_delete->get().usage & PROPERTY_USAGE_CATEGORY)) {
+ r_list.erase(to_delete);
+ to_delete = bottom->next();
+ }
+ r_list.erase(bottom);
}
- r_list.erase(bottom);
}
void EditorInspector::set_restrict_to_basic_settings(bool p_restrict) {
@@ -2615,6 +3528,14 @@ void EditorInspector::set_restrict_to_basic_settings(bool p_restrict) {
update_tree();
}
+void EditorInspector::set_property_clipboard(const Variant &p_value) {
+ property_clipboard = p_value;
+}
+
+Variant EditorInspector::get_property_clipboard() const {
+ return property_clipboard;
+}
+
void EditorInspector::_bind_methods() {
ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change);
@@ -2657,6 +3578,7 @@ EditorInspector::EditorInspector() {
property_focusable = -1;
sub_inspector = false;
deletable_properties = false;
+ property_clipboard = Variant();
get_v_scrollbar()->connect("value_changed", callable_mp(this, &EditorInspector::_vscroll_changed));
update_scroll_request = -1;
@@ -2666,4 +3588,8 @@ EditorInspector::EditorInspector() {
//used when class is created by the docgen to dump default values of everything bindable, editorsettings may not be created
refresh_countdown = 0.33;
}
+
+ ED_SHORTCUT("property_editor/copy_property", TTR("Copy Property"), KEY_MASK_CMD | KEY_C);
+ ED_SHORTCUT("property_editor/paste_property", TTR("Paste Property"), KEY_MASK_CMD | KEY_V);
+ ED_SHORTCUT("property_editor/copy_property_path", TTR("Copy Property Path"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_C);
}
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 71e31dd711..b71efe8f19 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -32,8 +32,12 @@
#define EDITOR_INSPECTOR_H
#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/dialogs.h"
#include "scene/gui/line_edit.h"
+#include "scene/gui/panel_container.h"
#include "scene/gui/scroll_container.h"
+#include "scene/gui/texture_rect.h"
class UndoRedo;
@@ -51,6 +55,13 @@ public:
class EditorProperty : public Container {
GDCLASS(EditorProperty, Container);
+public:
+ enum MenuItems {
+ MENU_COPY_PROPERTY,
+ MENU_PASTE_PROPERTY,
+ MENU_COPY_PROPERTY_PATH,
+ };
+
private:
String label;
int text_size;
@@ -84,6 +95,7 @@ private:
bool use_folding;
bool draw_top_bg;
+ void _ensure_popup();
bool _is_property_different(const Variant &p_current, const Variant &p_orig);
bool _get_instantiated_node_original_property(const StringName &p_prop, Variant &value);
void _focusable_focused(int p_index);
@@ -97,6 +109,7 @@ private:
Vector<Control *> focusables;
Control *label_reference;
Control *bottom_editor;
+ PopupMenu *menu;
mutable String tooltip_text;
@@ -106,8 +119,11 @@ private:
protected:
void _notification(int p_what);
static void _bind_methods();
+ virtual void _set_read_only(bool p_read_only);
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
+ const Color *_get_property_colors();
public:
void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false);
@@ -175,6 +191,8 @@ public:
bool can_revert_to_default() const { return can_revert; }
+ void menu_option(int p_option);
+
EditorProperty();
};
@@ -238,9 +256,7 @@ class EditorInspectorSection : public Container {
String label;
String section;
- Object *object;
- VBoxContainer *vbox;
- bool vbox_added; //optimization
+ bool vbox_added; // Optimization.
Color bg_color;
bool foldable;
@@ -250,6 +266,9 @@ class EditorInspectorSection : public Container {
void _test_unfold();
protected:
+ Object *object;
+ VBoxContainer *vbox;
+
void _notification(int p_what);
static void _bind_methods();
virtual void gui_input(const Ref<InputEvent> &p_event) override;
@@ -268,6 +287,118 @@ public:
~EditorInspectorSection();
};
+class EditorInspectorArray : public EditorInspectorSection {
+ GDCLASS(EditorInspectorArray, EditorInspectorSection);
+
+ UndoRedo *undo_redo;
+
+ enum Mode {
+ MODE_NONE,
+ MODE_USE_COUNT_PROPERTY,
+ MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION,
+ } mode;
+ StringName count_property;
+ StringName array_element_prefix;
+
+ int count = 0;
+
+ VBoxContainer *elements_vbox;
+
+ Control *control_dropping;
+ bool dropping = false;
+
+ Button *add_button;
+
+ AcceptDialog *resize_dialog;
+ int new_size = 0;
+ LineEdit *new_size_line_edit;
+
+ // Pagination
+ int page_lenght = 5;
+ int page = 0;
+ int max_page = 0;
+ int begin_array_index = 0;
+ int end_array_index = 0;
+ HBoxContainer *hbox_pagination;
+ Button *first_page_button;
+ Button *prev_page_button;
+ LineEdit *page_line_edit;
+ Label *page_count_label;
+ Button *next_page_button;
+ Button *last_page_button;
+
+ enum MenuOptions {
+ OPTION_MOVE_UP = 0,
+ OPTION_MOVE_DOWN,
+ OPTION_NEW_BEFORE,
+ OPTION_NEW_AFTER,
+ OPTION_REMOVE,
+ OPTION_CLEAR_ARRAY,
+ OPTION_RESIZE_ARRAY,
+ };
+ int popup_array_index_pressed = -1;
+ PopupMenu *rmb_popup;
+
+ struct ArrayElement {
+ PanelContainer *panel;
+ MarginContainer *margin;
+ HBoxContainer *hbox;
+ TextureRect *move_texture_rect;
+ VBoxContainer *vbox;
+ };
+ LocalVector<ArrayElement> array_elements;
+
+ Ref<StyleBoxFlat> odd_style;
+ Ref<StyleBoxFlat> even_style;
+
+ int _get_array_count();
+ void _add_button_pressed();
+
+ void _first_page_button_pressed();
+ void _prev_page_button_pressed();
+ void _page_line_edit_text_submitted(String p_text);
+ void _next_page_button_pressed();
+ void _last_page_button_pressed();
+
+ void _rmb_popup_id_pressed(int p_id);
+
+ void _control_dropping_draw();
+
+ void _vbox_visibility_changed();
+
+ void _panel_draw(int p_index);
+ void _panel_gui_input(Ref<InputEvent> p_event, int p_index);
+ void _move_element(int p_element_index, int p_to_pos);
+ void _clear_array();
+ void _resize_array(int p_size);
+ Array _extract_properties_as_array(const List<PropertyInfo> &p_list);
+ int _drop_position() const;
+
+ void _new_size_line_edit_text_changed(String p_text);
+ void _new_size_line_edit_text_submitted(String p_text);
+ void _resize_dialog_confirmed();
+
+ void _update_elements_visibility();
+ void _setup();
+
+ Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
+ void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
+ bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_undo_redo(UndoRedo *p_undo_redo);
+
+ void setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable);
+ void setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable);
+ VBoxContainer *get_vbox(int p_index);
+
+ EditorInspectorArray();
+};
+
class EditorInspector : public ScrollContainer {
GDCLASS(EditorInspector, ScrollContainer);
@@ -321,14 +452,14 @@ class EditorInspector : public ScrollContainer {
String property_prefix; //used for sectioned inspector
String object_class;
+ Variant property_clipboard;
bool restrict_to_basic = false;
void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field);
- void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false);
- void _property_changed_update_all(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false);
- void _multiple_properties_changed(Vector<String> p_paths, Array p_values);
+ void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false, bool p_update_all = false);
+ void _multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing = false);
void _property_keyed(const String &p_path, bool p_advance);
void _property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance);
void _property_deleted(const String &p_path);
@@ -341,6 +472,9 @@ class EditorInspector : public ScrollContainer {
void _node_removed(Node *p_node);
+ Map<StringName, int> per_array_page;
+ void _page_change_request(int p_new_page, const StringName &p_array_prefix);
+
void _changed_callback();
void _edit_request_change(Object *p_object, const String &p_prop);
@@ -412,6 +546,8 @@ public:
void set_use_deletable_properties(bool p_enabled);
void set_restrict_to_basic_settings(bool p_restrict);
+ void set_property_clipboard(const Variant &p_value);
+ Variant get_property_clipboard() const;
EditorInspector();
};
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index 296a33d917..f91cb7f607 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -195,7 +195,7 @@ void EditorLog::add_message(const String &p_msg, MessageType p_type) {
// get grouped together and sent to the editor log as one message. This can mess with the
// search functionality (see the comments on the PR above for more details). This behaviour
// also matches that of other IDE's.
- Vector<String> lines = p_msg.split("\n", false);
+ Vector<String> lines = p_msg.split("\n", true);
for (int i = 0; i < lines.size(); i++) {
_process_message(lines[i], p_type);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 1c2b449449..812bbd9b7d 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -97,10 +97,14 @@
#include "editor/editor_translation_parser.h"
#include "editor/export_template_manager.h"
#include "editor/filesystem_dock.h"
+#include "editor/import/dynamicfont_import_settings.h"
#include "editor/import/editor_import_collada.h"
#include "editor/import/resource_importer_bitmask.h"
+#include "editor/import/resource_importer_bmfont.h"
#include "editor/import/resource_importer_csv_translation.h"
+#include "editor/import/resource_importer_dynamicfont.h"
#include "editor/import/resource_importer_image.h"
+#include "editor/import/resource_importer_imagefont.h"
#include "editor/import/resource_importer_layered_texture.h"
#include "editor/import/resource_importer_obj.h"
#include "editor/import/resource_importer_scene.h"
@@ -520,6 +524,26 @@ void EditorNode::_update_from_settings() {
RS::get_singleton()->light_projectors_set_filter(RS::LightProjectorFilter(int(GLOBAL_GET("rendering/textures/light_projectors/filter"))));
}
+void EditorNode::_select_default_main_screen_plugin() {
+ if (EDITOR_3D < main_editor_buttons.size() && main_editor_buttons[EDITOR_3D]->is_visible()) {
+ // If the 3D editor is enabled, use this as the default.
+ _editor_select(EDITOR_3D);
+ return;
+ }
+
+ // Switch to the first main screen plugin that is enabled. Usually this is
+ // 2D, but may be subsequent ones if 2D is disabled in the feature profile.
+ for (int i = 0; i < main_editor_buttons.size(); i++) {
+ Button *editor_button = main_editor_buttons[i];
+ if (editor_button->is_visible()) {
+ _editor_select(i);
+ return;
+ }
+ }
+
+ _editor_select(-1);
+}
+
void EditorNode::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PROCESS: {
@@ -591,29 +615,12 @@ void EditorNode::_notification(int p_what) {
} break;
case NOTIFICATION_READY: {
- {
- _initializing_addons = true;
- Vector<String> addons;
- if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) {
- addons = ProjectSettings::get_singleton()->get("editor_plugins/enabled");
- }
-
- for (int i = 0; i < addons.size(); i++) {
- set_addon_plugin_enabled(addons[i], true);
- }
- _initializing_addons = false;
- }
-
RenderingServer::get_singleton()->viewport_set_disable_2d(get_scene_root()->get_viewport_rid(), true);
RenderingServer::get_singleton()->viewport_set_disable_environment(get_viewport()->get_viewport_rid(), true);
feature_profile_manager->notify_changed();
- if (!main_editor_buttons[EDITOR_3D]->is_visible()) { //may be hidden due to feature profile
- _editor_select(EDITOR_2D);
- } else {
- _editor_select(EDITOR_3D);
- }
+ _select_default_main_screen_plugin();
// Save the project after opening to mark it as last modified, except in headless mode.
if (DisplayServer::get_singleton()->window_can_draw()) {
@@ -966,6 +973,18 @@ void EditorNode::_sources_changed(bool p_exist) {
load_scene(defer_load_scene);
defer_load_scene = "";
}
+
+ // Only enable addons once resources have been imported
+ _initializing_addons = true;
+ Vector<String> addons;
+ if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) {
+ addons = ProjectSettings::get_singleton()->get("editor_plugins/enabled");
+ }
+
+ for (int i = 0; i < addons.size(); i++) {
+ set_addon_plugin_enabled(addons[i], true);
+ }
+ _initializing_addons = false;
}
}
@@ -1852,7 +1871,7 @@ void EditorNode::_dialog_action(String p_file) {
ml = Ref<MeshLibrary>(memnew(MeshLibrary));
}
- MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true);
+ MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true, file_export_lib_apply_xforms->is_pressed());
Error err = ResourceSaver::save(p_file, ml);
if (err) {
@@ -2088,7 +2107,6 @@ void EditorNode::_edit_current() {
Object *prev_inspected_object = get_inspector()->get_edited_object();
- bool capitalize = bool(EDITOR_GET("interface/inspector/capitalize_properties"));
bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding"));
bool is_resource = current_obj->is_class("Resource");
bool is_node = current_obj->is_class("Node");
@@ -2146,7 +2164,6 @@ void EditorNode::_edit_current() {
if (current_obj->is_class("EditorDebuggerRemoteObject")) {
editable_warning = TTR("This is a remote object, so changes to it won't be kept.\nPlease read the documentation relevant to debugging to better understand this workflow.");
- capitalize = false;
disable_folding = true;
} else if (current_obj->is_class("MultiNodeEdit")) {
Node *scene = get_edited_scene();
@@ -2183,10 +2200,6 @@ void EditorNode::_edit_current() {
inspector_dock->set_warning(editable_warning);
- if (get_inspector()->is_capitalize_paths_enabled() != capitalize) {
- get_inspector()->set_enable_capitalize_paths(capitalize);
- }
-
if (get_inspector()->is_using_folding() == disable_folding) {
get_inspector()->set_use_folding(!disable_folding);
}
@@ -3102,9 +3115,10 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed
tb->set_flat(true);
tb->set_toggle_mode(true);
tb->connect("pressed", callable_mp(singleton, &EditorNode::_editor_select), varray(singleton->main_editor_buttons.size()));
+ tb->set_name(p_editor->get_name());
tb->set_text(p_editor->get_name());
- Ref<Texture2D> icon = p_editor->get_icon();
+ Ref<Texture2D> icon = p_editor->get_icon();
if (icon.is_valid()) {
tb->set_icon(icon);
} else if (singleton->gui_base->has_theme_icon(p_editor->get_name(), "EditorIcons")) {
@@ -3114,7 +3128,6 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed
tb->add_theme_font_override("font", singleton->gui_base->get_theme_font(SNAME("main_button_font"), SNAME("EditorFonts")));
tb->add_theme_font_size_override("font_size", singleton->gui_base->get_theme_font_size(SNAME("main_button_font_size"), SNAME("EditorFonts")));
- tb->set_name(p_editor->get_name());
singleton->main_editor_buttons.push_back(tb);
singleton->main_editor_button_vb->add_child(tb);
singleton->editor_table.push_back(p_editor);
@@ -4766,6 +4779,10 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
return true;
}
+Error EditorNode::run_play_native(int p_idx, int p_platform) {
+ return run_native->run_native(p_idx, p_platform);
+}
+
void EditorNode::run_play() {
_menu_option_confirm(RUN_STOP, true);
_run(false);
@@ -4800,6 +4817,32 @@ String EditorNode::get_run_playing_scene() const {
return run_filename;
}
+void EditorNode::_immediate_dialog_confirmed() {
+ immediate_dialog_confirmed = true;
+}
+bool EditorNode::immediate_confirmation_dialog(const String &p_text, const String &p_ok_text, const String &p_cancel_text) {
+ ConfirmationDialog *cd = memnew(ConfirmationDialog);
+ cd->set_text(p_text);
+ cd->get_ok_button()->set_text(p_ok_text);
+ cd->get_cancel_button()->set_text(p_cancel_text);
+ cd->connect("confirmed", callable_mp(singleton, &EditorNode::_immediate_dialog_confirmed));
+ singleton->gui_base->add_child(cd);
+
+ cd->popup_centered();
+
+ while (true) {
+ OS::get_singleton()->delay_usec(1);
+ DisplayServer::get_singleton()->process_events();
+ Main::iteration();
+ if (singleton->immediate_dialog_confirmed || !cd->is_visible()) {
+ break;
+ }
+ }
+
+ memdelete(cd);
+ return singleton->immediate_dialog_confirmed;
+}
+
int EditorNode::get_current_tab() {
return scene_tabs->get_current_tab();
}
@@ -5827,6 +5870,18 @@ EditorNode::EditorNode() {
import_texture_atlas.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_texture_atlas);
+ Ref<ResourceImporterDynamicFont> import_font_data_dynamic;
+ import_font_data_dynamic.instantiate();
+ ResourceFormatImporter::get_singleton()->add_importer(import_font_data_dynamic);
+
+ Ref<ResourceImporterBMFont> import_font_data_bmfont;
+ import_font_data_bmfont.instantiate();
+ ResourceFormatImporter::get_singleton()->add_importer(import_font_data_bmfont);
+
+ Ref<ResourceImporterImageFont> import_font_data_image;
+ import_font_data_image.instantiate();
+ ResourceFormatImporter::get_singleton()->add_importer(import_font_data_image);
+
Ref<ResourceImporterCSVTranslation> import_csv_translation;
import_csv_translation.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_csv_translation);
@@ -6232,6 +6287,9 @@ EditorNode::EditorNode() {
scene_import_settings = memnew(SceneImportSettings);
gui_base->add_child(scene_import_settings);
+ fontdata_import_settings = memnew(DynamicFontImportSettings);
+ gui_base->add_child(fontdata_import_settings);
+
export_template_manager = memnew(ExportTemplateManager);
gui_base->add_child(export_template_manager);
@@ -6768,6 +6826,10 @@ EditorNode::EditorNode() {
file_export_lib_merge->set_text(TTR("Merge With Existing"));
file_export_lib_merge->set_pressed(true);
file_export_lib->get_vbox()->add_child(file_export_lib_merge);
+ file_export_lib_apply_xforms = memnew(CheckBox);
+ file_export_lib_apply_xforms->set_text(TTR("Apply MeshInstance Transforms"));
+ file_export_lib_apply_xforms->set_pressed(false);
+ file_export_lib->get_vbox()->add_child(file_export_lib_apply_xforms);
gui_base->add_child(file_export_lib);
file_script = memnew(EditorFileDialog);
@@ -6793,7 +6855,6 @@ EditorNode::EditorNode() {
preview_gen = memnew(AudioStreamPreviewGenerator);
add_child(preview_gen);
- //plugin stuff
add_editor_plugin(memnew(DebuggerEditorPlugin(this, debug_menu)));
add_editor_plugin(memnew(DebugAdapterServer()));
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 5ff28f322a..2e8b850c7b 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -32,7 +32,6 @@
#define EDITOR_NODE_H
#include "core/templates/safe_refcount.h"
-#include "editor/editor_command_palette.h"
#include "editor/editor_data.h"
#include "editor/editor_export.h"
#include "editor/editor_folding.h"
@@ -92,6 +91,8 @@ class VSplitContainer;
class Window;
class SubViewport;
class SceneImportSettings;
+class EditorExtensionManager;
+class DynamicFontImportSettings;
class EditorNode : public Node {
GDCLASS(EditorNode, Node);
@@ -333,6 +334,7 @@ private:
EditorFileDialog *file_script;
EditorFileDialog *file_android_build_source;
CheckBox *file_export_lib_merge;
+ CheckBox *file_export_lib_apply_xforms;
String current_path;
MenuButton *update_spinner;
@@ -421,6 +423,7 @@ private:
EditorResourcePreview *resource_preview;
EditorFolding editor_folding;
+ DynamicFontImportSettings *fontdata_import_settings;
SceneImportSettings *scene_import_settings;
struct BottomPanelItem {
String name;
@@ -675,6 +678,11 @@ private:
void _pick_main_scene_custom_action(const String &p_custom_action_name);
+ bool immediate_dialog_confirmed = false;
+ void _immediate_dialog_confirmed();
+
+ void _select_default_main_screen_plugin();
+
protected:
void _notification(int p_what);
@@ -892,12 +900,15 @@ public:
bool ensure_main_scene(bool p_from_native);
+ Error run_play_native(int p_idx, int p_platform);
void run_play();
void run_play_current();
void run_play_custom(const String &p_custom);
void run_stop();
bool is_run_playing() const;
String get_run_playing_scene() const;
+
+ static bool immediate_confirmation_dialog(const String &p_text, const String &p_ok_text = TTR("Ok"), const String &p_cancel_text = TTR("Cancel"));
};
struct EditorProgress {
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index 73ea4fb5ef..5baffb6f9d 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -30,6 +30,7 @@
#include "editor_plugin.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_export.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index cb7fffe592..c1e60e141c 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -51,6 +51,10 @@ EditorPropertyNil::EditorPropertyNil() {
///////////////////// TEXT /////////////////////////
+void EditorPropertyText::_set_read_only(bool p_read_only) {
+ text->set_editable(!p_read_only);
+};
+
void EditorPropertyText::_text_submitted(const String &p_string) {
if (updating) {
return;
@@ -68,9 +72,9 @@ void EditorPropertyText::_text_changed(const String &p_string) {
}
if (string_name) {
- emit_changed(get_edited_property(), StringName(p_string), "", true);
+ emit_changed(get_edited_property(), StringName(p_string));
} else {
- emit_changed(get_edited_property(), p_string, "", true);
+ emit_changed(get_edited_property(), p_string);
}
}
@@ -108,6 +112,11 @@ EditorPropertyText::EditorPropertyText() {
///////////////////// MULTILINE TEXT /////////////////////////
+void EditorPropertyMultilineText::_set_read_only(bool p_read_only) {
+ text->set_editable(!p_read_only);
+ open_big_text->set_disabled(p_read_only);
+};
+
void EditorPropertyMultilineText::_big_text_changed() {
text->set_text(big_text->get_text());
emit_changed(get_edited_property(), big_text->get_text(), "", true);
@@ -180,6 +189,11 @@ EditorPropertyMultilineText::EditorPropertyMultilineText() {
///////////////////// TEXT ENUM /////////////////////////
+void EditorPropertyTextEnum::_set_read_only(bool p_read_only) {
+ option_button->set_disabled(p_read_only);
+ edit_button->set_disabled(p_read_only);
+};
+
void EditorPropertyTextEnum::_emit_changed_value(String p_string) {
if (string_name) {
emit_changed(get_edited_property(), StringName(p_string));
@@ -328,6 +342,11 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() {
///////////////////// PATH /////////////////////////
+void EditorPropertyPath::_set_read_only(bool p_read_only) {
+ path->set_editable(!p_read_only);
+ path_edit->set_disabled(p_read_only);
+};
+
void EditorPropertyPath::_path_selected(const String &p_path) {
emit_changed(get_edited_property(), p_path);
update_property();
@@ -420,6 +439,10 @@ EditorPropertyPath::EditorPropertyPath() {
///////////////////// CLASS NAME /////////////////////////
+void EditorPropertyClassName::_set_read_only(bool p_read_only) {
+ property->set_disabled(p_read_only);
+};
+
void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) {
base_type = p_base_type;
dialog->set_base_type(base_type);
@@ -461,6 +484,10 @@ EditorPropertyClassName::EditorPropertyClassName() {
///////////////////// MEMBER /////////////////////////
+void EditorPropertyMember::_set_read_only(bool p_read_only) {
+ property->set_disabled(p_read_only);
+};
+
void EditorPropertyMember::_property_selected(const String &p_selected) {
emit_changed(get_edited_property(), p_selected);
update_property();
@@ -557,6 +584,11 @@ EditorPropertyMember::EditorPropertyMember() {
}
///////////////////// CHECK /////////////////////////
+
+void EditorPropertyCheck::_set_read_only(bool p_read_only) {
+ checkbox->set_disabled(p_read_only);
+};
+
void EditorPropertyCheck::_checkbox_pressed() {
emit_changed(get_edited_property(), checkbox->is_pressed());
}
@@ -580,6 +612,10 @@ EditorPropertyCheck::EditorPropertyCheck() {
///////////////////// ENUM /////////////////////////
+void EditorPropertyEnum::_set_read_only(bool p_read_only) {
+ options->set_disabled(p_read_only);
+};
+
void EditorPropertyEnum::_option_selected(int p_which) {
int64_t val = options->get_item_metadata(p_which);
emit_changed(get_edited_property(), val);
@@ -628,6 +664,12 @@ EditorPropertyEnum::EditorPropertyEnum() {
///////////////////// FLAGS /////////////////////////
+void EditorPropertyFlags::_set_read_only(bool p_read_only) {
+ for (CheckBox *check : flags) {
+ check->set_disabled(p_read_only);
+ }
+};
+
void EditorPropertyFlags::_flag_toggled() {
uint32_t value = 0;
for (int i = 0; i < flags.size(); i++) {
@@ -698,6 +740,7 @@ private:
bool expanded = false;
int expansion_rows = 0;
int hovered_index = -1;
+ bool read_only = false;
Size2 get_grid_size() const {
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
@@ -712,6 +755,10 @@ public:
Vector<String> names;
Vector<String> tooltips;
+ void set_read_only(bool p_read_only) {
+ read_only = p_read_only;
+ }
+
virtual Size2 get_minimum_size() const override {
Size2 min_size = get_grid_size();
@@ -736,6 +783,9 @@ public:
}
void gui_input(const Ref<InputEvent> &p_ev) override {
+ if (read_only) {
+ return;
+ }
const Ref<InputEventMouseMotion> mm = p_ev;
if (mm.is_valid()) {
bool expand_was_hovered = expand_hovered;
@@ -799,12 +849,12 @@ public:
const int bsize = (grid_size.height * 80 / 100) / 2;
const int h = bsize * 2 + 1;
- Color color = get_theme_color(SNAME("highlight_color"), SNAME("Editor"));
+ Color color = get_theme_color(read_only ? SNAME("disabled_highlight_color") : SNAME("highlight_color"), SNAME("Editor"));
- Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
+ Color text_color = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_color"), SNAME("Editor"));
text_color.a *= 0.5;
- Color text_color_on = get_theme_color(SNAME("font_hover_color"), SNAME("Editor"));
+ Color text_color_on = get_theme_color(read_only ? SNAME("disabled_font_color") : SNAME("font_hover_color"), SNAME("Editor"));
text_color_on.a *= 0.7;
const int vofs = (grid_size.height - h) / 2;
@@ -935,6 +985,11 @@ public:
}
};
+void EditorPropertyLayers::_set_read_only(bool p_read_only) {
+ button->set_disabled(p_read_only);
+ grid->set_read_only(p_read_only);
+};
+
void EditorPropertyLayers::_grid_changed(uint32_t p_grid) {
emit_changed(get_edited_property(), p_grid);
}
@@ -1071,6 +1126,10 @@ EditorPropertyLayers::EditorPropertyLayers() {
///////////////////// INT /////////////////////////
+void EditorPropertyInteger::_set_read_only(bool p_read_only) {
+ spin->set_read_only(p_read_only);
+};
+
void EditorPropertyInteger::_value_changed(int64_t val) {
if (setting) {
return;
@@ -1113,6 +1172,10 @@ EditorPropertyInteger::EditorPropertyInteger() {
///////////////////// OBJECT ID /////////////////////////
+void EditorPropertyObjectID::_set_read_only(bool p_read_only) {
+ edit->set_disabled(p_read_only);
+};
+
void EditorPropertyObjectID::_edit_pressed() {
emit_signal(SNAME("object_id_selected"), get_edited_property(), get_edited_object()->get(get_edited_property()));
}
@@ -1151,6 +1214,10 @@ EditorPropertyObjectID::EditorPropertyObjectID() {
///////////////////// FLOAT /////////////////////////
+void EditorPropertyFloat::_set_read_only(bool p_read_only) {
+ spin->set_read_only(p_read_only);
+};
+
void EditorPropertyFloat::_value_changed(double val) {
if (setting) {
return;
@@ -1197,7 +1264,14 @@ EditorPropertyFloat::EditorPropertyFloat() {
///////////////////// EASING /////////////////////////
+void EditorPropertyEasing::_set_read_only(bool p_read_only) {
+ spin->set_read_only(p_read_only);
+};
+
void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) {
+ if (is_read_only()) {
+ return;
+ }
const Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid()) {
if (mb->is_double_click() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
@@ -1271,12 +1345,12 @@ void EditorPropertyEasing::_draw_easing() {
const Ref<Font> f = get_theme_font(SNAME("font"), SNAME("Label"));
int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
- const Color font_color = get_theme_color(SNAME("font_color"), SNAME("Label"));
+ const Color font_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit"));
Color line_color;
if (dragging) {
line_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
} else {
- line_color = get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.9);
+ line_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit")) * Color(1, 1, 1, 0.9);
}
Vector<Point2> points;
@@ -1409,6 +1483,12 @@ EditorPropertyEasing::EditorPropertyEasing() {
///////////////////// VECTOR2 /////////////////////////
+void EditorPropertyVector2::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 2; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyVector2::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -1430,11 +1510,9 @@ void EditorPropertyVector2::update_property() {
void EditorPropertyVector2::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 2; i++) {
- Color c = base;
- c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i]);
}
}
}
@@ -1492,6 +1570,12 @@ EditorPropertyVector2::EditorPropertyVector2(bool p_force_wide) {
///////////////////// RECT2 /////////////////////////
+void EditorPropertyRect2::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 4; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyRect2::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -1517,11 +1601,9 @@ void EditorPropertyRect2::update_property() {
void EditorPropertyRect2::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 4; i++) {
- Color c = base;
- c.set_hsv(float(i % 2) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i % 2]);
}
}
}
@@ -1589,6 +1671,12 @@ EditorPropertyRect2::EditorPropertyRect2(bool p_force_wide) {
///////////////////// VECTOR3 /////////////////////////
+void EditorPropertyVector3::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 3; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyVector3::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -1639,11 +1727,9 @@ Vector3 EditorPropertyVector3::get_vector() {
void EditorPropertyVector3::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 3; i++) {
- Color c = base;
- c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i]);
}
}
}
@@ -1701,6 +1787,12 @@ EditorPropertyVector3::EditorPropertyVector3(bool p_force_wide) {
///////////////////// VECTOR2i /////////////////////////
+void EditorPropertyVector2i::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 2; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyVector2i::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -1722,11 +1814,9 @@ void EditorPropertyVector2i::update_property() {
void EditorPropertyVector2i::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 2; i++) {
- Color c = base;
- c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i]);
}
}
}
@@ -1784,6 +1874,12 @@ EditorPropertyVector2i::EditorPropertyVector2i(bool p_force_wide) {
///////////////////// RECT2i /////////////////////////
+void EditorPropertyRect2i::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 4; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyRect2i::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -1809,11 +1905,9 @@ void EditorPropertyRect2i::update_property() {
void EditorPropertyRect2i::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 4; i++) {
- Color c = base;
- c.set_hsv(float(i % 2) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i % 2]);
}
}
}
@@ -1881,6 +1975,12 @@ EditorPropertyRect2i::EditorPropertyRect2i(bool p_force_wide) {
///////////////////// VECTOR3i /////////////////////////
+void EditorPropertyVector3i::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 3; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyVector3i::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -1904,11 +2004,9 @@ void EditorPropertyVector3i::update_property() {
void EditorPropertyVector3i::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 3; i++) {
- Color c = base;
- c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i]);
}
}
}
@@ -1965,6 +2063,12 @@ EditorPropertyVector3i::EditorPropertyVector3i(bool p_force_wide) {
///////////////////// PLANE /////////////////////////
+void EditorPropertyPlane::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 4; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyPlane::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -1990,11 +2094,9 @@ void EditorPropertyPlane::update_property() {
void EditorPropertyPlane::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
- for (int i = 0; i < 3; i++) {
- Color c = base;
- c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ const Color *colors = _get_property_colors();
+ for (int i = 0; i < 4; i++) {
+ spin[i]->set_custom_label_color(true, colors[i]);
}
}
}
@@ -2052,6 +2154,12 @@ EditorPropertyPlane::EditorPropertyPlane(bool p_force_wide) {
///////////////////// QUATERNION /////////////////////////
+void EditorPropertyQuaternion::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 4; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyQuaternion::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -2077,11 +2185,9 @@ void EditorPropertyQuaternion::update_property() {
void EditorPropertyQuaternion::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
- for (int i = 0; i < 3; i++) {
- Color c = base;
- c.set_hsv(float(i) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ const Color *colors = _get_property_colors();
+ for (int i = 0; i < 4; i++) {
+ spin[i]->set_custom_label_color(true, colors[i]);
}
}
}
@@ -2136,6 +2242,12 @@ EditorPropertyQuaternion::EditorPropertyQuaternion() {
///////////////////// AABB /////////////////////////
+void EditorPropertyAABB::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 6; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyAABB::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -2167,11 +2279,9 @@ void EditorPropertyAABB::update_property() {
void EditorPropertyAABB::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 6; i++) {
- Color c = base;
- c.set_hsv(float(i % 3) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i % 3]);
}
}
}
@@ -2213,6 +2323,12 @@ EditorPropertyAABB::EditorPropertyAABB() {
///////////////////// TRANSFORM2D /////////////////////////
+void EditorPropertyTransform2D::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 6; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyTransform2D::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -2220,10 +2336,10 @@ void EditorPropertyTransform2D::_value_changed(double val, const String &p_name)
Transform2D p;
p[0][0] = spin[0]->get_value();
- p[0][1] = spin[1]->get_value();
- p[1][0] = spin[2]->get_value();
- p[1][1] = spin[3]->get_value();
- p[2][0] = spin[4]->get_value();
+ p[1][0] = spin[1]->get_value();
+ p[2][0] = spin[2]->get_value();
+ p[0][1] = spin[3]->get_value();
+ p[1][1] = spin[4]->get_value();
p[2][1] = spin[5]->get_value();
emit_changed(get_edited_property(), p, p_name);
@@ -2233,10 +2349,10 @@ void EditorPropertyTransform2D::update_property() {
Transform2D val = get_edited_object()->get(get_edited_property());
setting = true;
spin[0]->set_value(val[0][0]);
- spin[1]->set_value(val[0][1]);
- spin[2]->set_value(val[1][0]);
- spin[3]->set_value(val[1][1]);
- spin[4]->set_value(val[2][0]);
+ spin[1]->set_value(val[1][0]);
+ spin[2]->set_value(val[2][0]);
+ spin[3]->set_value(val[0][1]);
+ spin[4]->set_value(val[1][1]);
spin[5]->set_value(val[2][1]);
setting = false;
@@ -2244,11 +2360,14 @@ void EditorPropertyTransform2D::update_property() {
void EditorPropertyTransform2D::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 6; i++) {
- Color c = base;
- c.set_hsv(float(i % 2) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ // For Transform2D, use the 4th color (cyan) for the origin vector.
+ if (i % 3 == 2) {
+ spin[i]->set_custom_label_color(true, colors[3]);
+ } else {
+ spin[i]->set_custom_label_color(true, colors[i % 3]);
+ }
}
}
}
@@ -2268,17 +2387,19 @@ void EditorPropertyTransform2D::setup(double p_min, double p_max, double p_step,
}
}
-EditorPropertyTransform2D::EditorPropertyTransform2D() {
+EditorPropertyTransform2D::EditorPropertyTransform2D(bool p_include_origin) {
GridContainer *g = memnew(GridContainer);
- g->set_columns(2);
+ g->set_columns(p_include_origin ? 3 : 2);
add_child(g);
- static const char *desc[6] = { "x", "y", "x", "y", "x", "y" };
+ static const char *desc[6] = { "xx", "xy", "xo", "yx", "yy", "yo" };
for (int i = 0; i < 6; i++) {
spin[i] = memnew(EditorSpinSlider);
spin[i]->set_label(desc[i]);
spin[i]->set_flat(true);
- g->add_child(spin[i]);
+ if (p_include_origin || i % 3 != 2) {
+ g->add_child(spin[i]);
+ }
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
add_focusable(spin[i]);
spin[i]->connect("value_changed", callable_mp(this, &EditorPropertyTransform2D::_value_changed), varray(desc[i]));
@@ -2289,6 +2410,12 @@ EditorPropertyTransform2D::EditorPropertyTransform2D() {
///////////////////// BASIS /////////////////////////
+void EditorPropertyBasis::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 9; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyBasis::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -2296,13 +2423,13 @@ void EditorPropertyBasis::_value_changed(double val, const String &p_name) {
Basis p;
p[0][0] = spin[0]->get_value();
- p[1][0] = spin[1]->get_value();
- p[2][0] = spin[2]->get_value();
- p[0][1] = spin[3]->get_value();
+ p[0][1] = spin[1]->get_value();
+ p[0][2] = spin[2]->get_value();
+ p[1][0] = spin[3]->get_value();
p[1][1] = spin[4]->get_value();
- p[2][1] = spin[5]->get_value();
- p[0][2] = spin[6]->get_value();
- p[1][2] = spin[7]->get_value();
+ p[1][2] = spin[5]->get_value();
+ p[2][0] = spin[6]->get_value();
+ p[2][1] = spin[7]->get_value();
p[2][2] = spin[8]->get_value();
emit_changed(get_edited_property(), p, p_name);
@@ -2312,13 +2439,13 @@ void EditorPropertyBasis::update_property() {
Basis val = get_edited_object()->get(get_edited_property());
setting = true;
spin[0]->set_value(val[0][0]);
- spin[1]->set_value(val[1][0]);
- spin[2]->set_value(val[2][0]);
- spin[3]->set_value(val[0][1]);
+ spin[1]->set_value(val[0][1]);
+ spin[2]->set_value(val[0][2]);
+ spin[3]->set_value(val[1][0]);
spin[4]->set_value(val[1][1]);
- spin[5]->set_value(val[2][1]);
- spin[6]->set_value(val[0][2]);
- spin[7]->set_value(val[1][2]);
+ spin[5]->set_value(val[1][2]);
+ spin[6]->set_value(val[2][0]);
+ spin[7]->set_value(val[2][1]);
spin[8]->set_value(val[2][2]);
setting = false;
@@ -2326,11 +2453,9 @@ void EditorPropertyBasis::update_property() {
void EditorPropertyBasis::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 9; i++) {
- Color c = base;
- c.set_hsv(float(i % 3) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i % 3]);
}
}
}
@@ -2355,7 +2480,7 @@ EditorPropertyBasis::EditorPropertyBasis() {
g->set_columns(3);
add_child(g);
- static const char *desc[9] = { "x", "y", "z", "x", "y", "z", "x", "y", "z" };
+ static const char *desc[9] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz" };
for (int i = 0; i < 9; i++) {
spin[i] = memnew(EditorSpinSlider);
spin[i]->set_label(desc[i]);
@@ -2371,6 +2496,12 @@ EditorPropertyBasis::EditorPropertyBasis() {
///////////////////// TRANSFORM /////////////////////////
+void EditorPropertyTransform3D::_set_read_only(bool p_read_only) {
+ for (int i = 0; i < 12; i++) {
+ spin[i]->set_read_only(p_read_only);
+ }
+};
+
void EditorPropertyTransform3D::_value_changed(double val, const String &p_name) {
if (setting) {
return;
@@ -2378,16 +2509,16 @@ void EditorPropertyTransform3D::_value_changed(double val, const String &p_name)
Transform3D p;
p.basis[0][0] = spin[0]->get_value();
- p.basis[1][0] = spin[1]->get_value();
- p.basis[2][0] = spin[2]->get_value();
- p.basis[0][1] = spin[3]->get_value();
- p.basis[1][1] = spin[4]->get_value();
- p.basis[2][1] = spin[5]->get_value();
- p.basis[0][2] = spin[6]->get_value();
- p.basis[1][2] = spin[7]->get_value();
- p.basis[2][2] = spin[8]->get_value();
- p.origin[0] = spin[9]->get_value();
- p.origin[1] = spin[10]->get_value();
+ p.basis[0][1] = spin[1]->get_value();
+ p.basis[0][2] = spin[2]->get_value();
+ p.origin[0] = spin[3]->get_value();
+ p.basis[1][0] = spin[4]->get_value();
+ p.basis[1][1] = spin[5]->get_value();
+ p.basis[1][2] = spin[6]->get_value();
+ p.origin[1] = spin[7]->get_value();
+ p.basis[2][0] = spin[8]->get_value();
+ p.basis[2][1] = spin[9]->get_value();
+ p.basis[2][2] = spin[10]->get_value();
p.origin[2] = spin[11]->get_value();
emit_changed(get_edited_property(), p, p_name);
@@ -2400,27 +2531,25 @@ void EditorPropertyTransform3D::update_property() {
void EditorPropertyTransform3D::update_using_transform(Transform3D p_transform) {
setting = true;
spin[0]->set_value(p_transform.basis[0][0]);
- spin[1]->set_value(p_transform.basis[1][0]);
- spin[2]->set_value(p_transform.basis[2][0]);
- spin[3]->set_value(p_transform.basis[0][1]);
- spin[4]->set_value(p_transform.basis[1][1]);
- spin[5]->set_value(p_transform.basis[2][1]);
- spin[6]->set_value(p_transform.basis[0][2]);
- spin[7]->set_value(p_transform.basis[1][2]);
- spin[8]->set_value(p_transform.basis[2][2]);
- spin[9]->set_value(p_transform.origin[0]);
- spin[10]->set_value(p_transform.origin[1]);
+ spin[1]->set_value(p_transform.basis[0][1]);
+ spin[2]->set_value(p_transform.basis[0][2]);
+ spin[3]->set_value(p_transform.origin[0]);
+ spin[4]->set_value(p_transform.basis[1][0]);
+ spin[5]->set_value(p_transform.basis[1][1]);
+ spin[6]->set_value(p_transform.basis[1][2]);
+ spin[7]->set_value(p_transform.origin[1]);
+ spin[8]->set_value(p_transform.basis[2][0]);
+ spin[9]->set_value(p_transform.basis[2][1]);
+ spin[10]->set_value(p_transform.basis[2][2]);
spin[11]->set_value(p_transform.origin[2]);
setting = false;
}
void EditorPropertyTransform3D::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
- Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
+ const Color *colors = _get_property_colors();
for (int i = 0; i < 12; i++) {
- Color c = base;
- c.set_hsv(float(i % 3) / 3.0 + 0.05, c.get_s() * 0.75, c.get_v());
- spin[i]->set_custom_label_color(true, c);
+ spin[i]->set_custom_label_color(true, colors[i % 4]);
}
}
}
@@ -2442,10 +2571,10 @@ void EditorPropertyTransform3D::setup(double p_min, double p_max, double p_step,
EditorPropertyTransform3D::EditorPropertyTransform3D() {
GridContainer *g = memnew(GridContainer);
- g->set_columns(3);
+ g->set_columns(4);
add_child(g);
- static const char *desc[12] = { "x", "y", "z", "x", "y", "z", "x", "y", "z", "x", "y", "z" };
+ static const char *desc[12] = { "xx", "xy", "xz", "xo", "yx", "yy", "yz", "yo", "zx", "zy", "zz", "zo" };
for (int i = 0; i < 12; i++) {
spin[i] = memnew(EditorSpinSlider);
spin[i]->set_label(desc[i]);
@@ -2461,6 +2590,10 @@ EditorPropertyTransform3D::EditorPropertyTransform3D() {
////////////// COLOR PICKER //////////////////////
+void EditorPropertyColor::_set_read_only(bool p_read_only) {
+ picker->set_disabled(p_read_only);
+};
+
void EditorPropertyColor::_color_changed(const Color &p_color) {
// Cancel the color change if the current color is identical to the new one.
if (get_edited_object()->get(get_edited_property()) == p_color) {
@@ -2533,6 +2666,11 @@ EditorPropertyColor::EditorPropertyColor() {
////////////// NODE PATH //////////////////////
+void EditorPropertyNodePath::_set_read_only(bool p_read_only) {
+ assign->set_disabled(p_read_only);
+ clear->set_disabled(p_read_only);
+};
+
void EditorPropertyNodePath::_node_selected(const NodePath &p_path) {
NodePath path = p_path;
Node *base_node = nullptr;
@@ -2678,6 +2816,10 @@ EditorPropertyRID::EditorPropertyRID() {
////////////// RESOURCE //////////////////////
+void EditorPropertyResource::_set_read_only(bool p_read_only) {
+ resource_picker->set_editable(!p_read_only);
+};
+
void EditorPropertyResource::_resource_selected(const RES &p_resource) {
if (use_sub_inspector) {
bool unfold = !get_edited_object()->editor_is_section_unfolded(get_edited_property());
@@ -3289,7 +3431,6 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, default_float_step);
editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, hint.suffix);
return editor;
-
} break;
case Variant::PLANE: {
EditorPropertyPlane *editor = memnew(EditorPropertyPlane(p_wide));
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index 0cb21bb391..9a687f1a72 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -59,6 +59,7 @@ class EditorPropertyText : public EditorProperty {
void _text_submitted(const String &p_string);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -81,6 +82,7 @@ class EditorPropertyMultilineText : public EditorProperty {
void _open_big_text();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -115,6 +117,7 @@ class EditorPropertyTextEnum : public EditorProperty {
void _custom_value_cancelled();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
void _notification(int p_what);
@@ -139,6 +142,7 @@ class EditorPropertyPath : public EditorProperty {
void _path_focus_exited();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
void _notification(int p_what);
@@ -161,6 +165,7 @@ private:
void _dialog_created();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -182,7 +187,6 @@ public:
MEMBER_PROPERTY_OF_BASE_TYPE, ///< a property of a base type
MEMBER_PROPERTY_OF_INSTANCE, ///< a property of an instance
MEMBER_PROPERTY_OF_SCRIPT, ///< a property of a script & base
-
};
private:
@@ -195,6 +199,7 @@ private:
void _property_select();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -210,6 +215,7 @@ class EditorPropertyCheck : public EditorProperty {
void _checkbox_pressed();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -224,6 +230,7 @@ class EditorPropertyEnum : public EditorProperty {
void _option_selected(int p_which);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -242,6 +249,7 @@ class EditorPropertyFlags : public EditorProperty {
void _flag_toggled();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -276,6 +284,7 @@ private:
void _menu_pressed(int p_menu);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -291,6 +300,7 @@ class EditorPropertyInteger : public EditorProperty {
void _value_changed(int64_t p_val);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -306,6 +316,7 @@ class EditorPropertyObjectID : public EditorProperty {
void _edit_pressed();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -322,6 +333,7 @@ class EditorPropertyFloat : public EditorProperty {
void _value_changed(double p_val);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -363,6 +375,7 @@ class EditorPropertyEasing : public EditorProperty {
void _notification(int p_what);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -378,6 +391,7 @@ class EditorPropertyVector2 : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -394,6 +408,7 @@ class EditorPropertyRect2 : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -411,6 +426,7 @@ class EditorPropertyVector3 : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -429,6 +445,7 @@ class EditorPropertyVector2i : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -445,6 +462,7 @@ class EditorPropertyRect2i : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -461,6 +479,7 @@ class EditorPropertyVector3i : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -477,6 +496,7 @@ class EditorPropertyPlane : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -493,6 +513,7 @@ class EditorPropertyQuaternion : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -509,6 +530,7 @@ class EditorPropertyAABB : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -525,13 +547,14 @@ class EditorPropertyTransform2D : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
public:
virtual void update_property() override;
void setup(double p_min, double p_max, double p_step, bool p_no_slider, const String &p_suffix = String());
- EditorPropertyTransform2D();
+ EditorPropertyTransform2D(bool p_include_origin = true);
};
class EditorPropertyBasis : public EditorProperty {
@@ -541,6 +564,7 @@ class EditorPropertyBasis : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -557,6 +581,7 @@ class EditorPropertyTransform3D : public EditorProperty {
void _value_changed(double p_val, const String &p_name);
protected:
+ virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
static void _bind_methods();
@@ -578,6 +603,7 @@ class EditorPropertyColor : public EditorProperty {
Color last_color;
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
public:
@@ -600,6 +626,7 @@ class EditorPropertyNodePath : public EditorProperty {
void _node_clear();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
void _notification(int p_what);
@@ -644,6 +671,7 @@ class EditorPropertyResource : public EditorProperty {
void _update_property_bg();
protected:
+ virtual void _set_read_only(bool p_read_only) override;
static void _bind_methods();
void _notification(int p_what);
diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp
index e115cd77e1..5828549bdc 100644
--- a/editor/editor_run_native.cpp
+++ b/editor/editor_run_native.cpp
@@ -52,8 +52,8 @@ void EditorRunNative::_notification(int p_what) {
small_icon.instantiate();
small_icon->create_from_image(im);
MenuButton *mb = memnew(MenuButton);
- mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::_run_native), varray(i));
- mb->connect("pressed", callable_mp(this, &EditorRunNative::_run_native), varray(-1, i));
+ mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native), varray(i));
+ mb->connect("pressed", callable_mp(this, &EditorRunNative::run_native), varray(-1, i));
mb->set_icon(small_icon);
add_child(mb);
menus[i] = mb;
@@ -93,22 +93,22 @@ void EditorRunNative::_notification(int p_what) {
}
}
-void EditorRunNative::_run_native(int p_idx, int p_platform) {
+Error EditorRunNative::run_native(int p_idx, int p_platform) {
if (!EditorNode::get_singleton()->ensure_main_scene(true)) {
resume_idx = p_idx;
resume_platform = p_platform;
- return;
+ return OK;
}
Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(p_platform);
- ERR_FAIL_COND(eep.is_null());
+ ERR_FAIL_COND_V(eep.is_null(), ERR_UNAVAILABLE);
if (p_idx == -1) {
if (eep->get_options_count() == 1) {
menus[p_platform]->get_popup()->hide();
p_idx = 0;
} else {
- return;
+ return ERR_INVALID_PARAMETER;
}
}
@@ -124,7 +124,7 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
if (preset.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("No runnable export preset found for this platform.\nPlease add a runnable preset in the Export menu or define an existing preset as runnable."));
- return;
+ return ERR_UNAVAILABLE;
}
emit_signal(SNAME("native_run"), preset);
@@ -149,11 +149,11 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
}
- eep->run(preset, p_idx, flags);
+ return eep->run(preset, p_idx, flags);
}
void EditorRunNative::resume_run_native() {
- _run_native(resume_idx, resume_platform);
+ run_native(resume_idx, resume_platform);
}
void EditorRunNative::_bind_methods() {
diff --git a/editor/editor_run_native.h b/editor/editor_run_native.h
index 3516f668c6..97f6fc005a 100644
--- a/editor/editor_run_native.h
+++ b/editor/editor_run_native.h
@@ -43,13 +43,12 @@ class EditorRunNative : public HBoxContainer {
int resume_idx;
int resume_platform;
- void _run_native(int p_idx, int p_platform);
-
protected:
static void _bind_methods();
void _notification(int p_what);
public:
+ Error run_native(int p_idx, int p_platform);
bool is_deploy_debug_remote_enabled() const;
void resume_run_native();
diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp
index 3f65b101f7..8cd636ddf3 100644
--- a/editor/editor_spin_slider.cpp
+++ b/editor/editor_spin_slider.cpp
@@ -221,7 +221,7 @@ void EditorSpinSlider::_draw_spin_slider() {
bool rtl = is_layout_rtl();
Vector2 size = get_size();
- Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"));
+ Ref<StyleBox> sb = get_theme_stylebox(is_read_only() ? SNAME("read_only") : SNAME("normal"), SNAME("LineEdit"));
if (!flat) {
draw_style_box(sb, Rect2(Vector2(), size));
}
@@ -233,7 +233,7 @@ void EditorSpinSlider::_draw_spin_slider() {
int label_width = font->get_string_size(label, font_size).width;
int number_width = size.width - sb->get_minimum_size().width - label_width - sep;
- Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox"));
+ Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
if (get_step() == 1) {
number_width -= updown->get_width();
@@ -243,7 +243,7 @@ void EditorSpinSlider::_draw_spin_slider() {
int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
- Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit"));
+ Color fc = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SNAME("font_color"), SNAME("LineEdit"));
Color lc;
if (use_custom_label_color) {
lc = custom_label_color;
@@ -299,7 +299,7 @@ void EditorSpinSlider::_draw_spin_slider() {
TS->free(num_rid);
if (get_step() == 1) {
- Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox"));
+ Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
int updown_vofs = (size.height - updown2->get_height()) / 2;
if (rtl) {
updown_offset = sb->get_margin(SIDE_LEFT);
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 8c348731d6..6e5b94dc07 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -395,12 +395,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
const Color separator_color = Color(mono_color.r, mono_color.g, mono_color.b, 0.1);
const Color highlight_color = Color(accent_color.r, accent_color.g, accent_color.b, 0.275);
+ const Color disabled_highlight_color = highlight_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5);
float prev_icon_saturation = theme->has_color("icon_saturation", "Editor") ? theme->get_color("icon_saturation", "Editor").r : 1.0;
theme->set_color("icon_saturation", "Editor", Color(icon_saturation, icon_saturation, icon_saturation)); //can't save single float in theme, so using color
theme->set_color("accent_color", "Editor", accent_color);
theme->set_color("highlight_color", "Editor", highlight_color);
+ theme->set_color("disabled_highlight_color", "Editor", disabled_highlight_color);
theme->set_color("base_color", "Editor", base_color);
theme->set_color("dark_color_1", "Editor", dark_color_1);
theme->set_color("dark_color_2", "Editor", dark_color_2);
@@ -424,6 +426,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
Color warning_color = Color(1, 0.87, 0.4);
Color error_color = Color(1, 0.47, 0.42);
Color property_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.5);
+ Color readonly_color = property_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5);
+ Color readonly_error_color = error_color.lerp(dark_theme ? Color(0, 0, 0) : Color(1, 1, 1), 0.5);
if (!dark_theme) {
// Darken some colors to be readable on a light background
@@ -436,6 +440,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_color("warning_color", "Editor", warning_color);
theme->set_color("error_color", "Editor", error_color);
theme->set_color("property_color", "Editor", property_color);
+ theme->set_color("readonly_color", "Editor", readonly_color);
+ theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color);
if (!dark_theme) {
theme->set_color("vulkan_color", "Editor", Color::hex(0xad1128ff));
@@ -696,6 +702,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("unchecked", "CheckBox", theme->get_icon("GuiUnchecked", "EditorIcons"));
theme->set_icon("radio_checked", "CheckBox", theme->get_icon("GuiRadioChecked", "EditorIcons"));
theme->set_icon("radio_unchecked", "CheckBox", theme->get_icon("GuiRadioUnchecked", "EditorIcons"));
+ theme->set_icon("checked_disabled", "CheckBox", theme->get_icon("GuiCheckedDisabled", "EditorIcons"));
+ theme->set_icon("unchecked_disabled", "CheckBox", theme->get_icon("GuiUncheckedDisabled", "EditorIcons"));
+ theme->set_icon("radio_checked_disabled", "CheckBox", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons"));
+ theme->set_icon("radio_unchecked_disabled", "CheckBox", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons"));
theme->set_color("font_color", "CheckBox", font_color);
theme->set_color("font_hover_color", "CheckBox", font_hover_color);
@@ -742,6 +752,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons"));
theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiRadioChecked", "EditorIcons"));
theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiRadioUnchecked", "EditorIcons"));
+ theme->set_icon("checked_disabled", "PopupMenu", theme->get_icon("GuiCheckedDisabled", "EditorIcons"));
+ theme->set_icon("unchecked_disabled", "PopupMenu", theme->get_icon("GuiUncheckedDisabled", "EditorIcons"));
+ theme->set_icon("radio_checked_disabled", "PopupMenu", theme->get_icon("GuiRadioCheckedDisabled", "EditorIcons"));
+ theme->set_icon("radio_unchecked_disabled", "PopupMenu", theme->get_icon("GuiRadioUncheckedDisabled", "EditorIcons"));
theme->set_icon("submenu", "PopupMenu", theme->get_icon("ArrowRight", "EditorIcons"));
theme->set_icon("submenu_mirrored", "PopupMenu", theme->get_icon("ArrowLeft", "EditorIcons"));
theme->set_icon("visibility_hidden", "PopupMenu", theme->get_icon("GuiVisibilityHidden", "EditorIcons"));
@@ -803,6 +817,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_constant("vseparation", "EditorProperty", (extra_spacing + default_margin_size) * EDSCALE);
theme->set_color("error_color", "EditorProperty", error_color);
theme->set_color("property_color", "EditorProperty", property_color);
+ theme->set_color("readonly_color", "EditorProperty", readonly_color);
+ theme->set_color("readonly_error_color", "EditorProperty", readonly_error_color);
Color inspector_section_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.35);
theme->set_color("font_color", "EditorInspectorSection", inspector_section_color);
@@ -1052,7 +1068,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("tab", "TextEdit", theme->get_icon("GuiTab", "EditorIcons"));
theme->set_icon("space", "TextEdit", theme->get_icon("GuiSpace", "EditorIcons"));
theme->set_color("font_color", "TextEdit", font_color);
- theme->set_color("font_readonly_color", "LineEdit", font_readonly_color);
+ theme->set_color("font_readonly_color", "TextEdit", font_readonly_color);
theme->set_color("caret_color", "TextEdit", font_color);
theme->set_color("selection_color", "TextEdit", selection_color);
theme->set_constant("line_spacing", "TextEdit", 4 * EDSCALE);
@@ -1138,8 +1154,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("increment", "HScrollBar", empty_icon);
theme->set_icon("increment_highlight", "HScrollBar", empty_icon);
+ theme->set_icon("increment_pressed", "HScrollBar", empty_icon);
theme->set_icon("decrement", "HScrollBar", empty_icon);
theme->set_icon("decrement_highlight", "HScrollBar", empty_icon);
+ theme->set_icon("decrement_pressed", "HScrollBar", empty_icon);
// VScrollBar
theme->set_stylebox("scroll", "VScrollBar", make_stylebox(theme->get_icon("GuiScrollBg", "EditorIcons"), 5, 5, 5, 5, 0, 0, 0, 0));
@@ -1150,8 +1168,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("increment", "VScrollBar", empty_icon);
theme->set_icon("increment_highlight", "VScrollBar", empty_icon);
+ theme->set_icon("increment_pressed", "VScrollBar", empty_icon);
theme->set_icon("decrement", "VScrollBar", empty_icon);
theme->set_icon("decrement_highlight", "VScrollBar", empty_icon);
+ theme->set_icon("decrement_pressed", "VScrollBar", empty_icon);
// HSlider
theme->set_icon("grabber_highlight", "HSlider", theme->get_icon("GuiSliderGrabberHl", "EditorIcons"));
@@ -1201,11 +1221,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// TooltipPanel
Ref<StyleBoxFlat> style_tooltip = style_popup->duplicate();
style_tooltip->set_shadow_size(0);
- style_tooltip->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE);
+ style_tooltip->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE * 0.5);
style_tooltip->set_default_margin(SIDE_TOP, default_margin_size * EDSCALE * 0.5);
- style_tooltip->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE);
+ style_tooltip->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE * 0.5);
style_tooltip->set_default_margin(SIDE_BOTTOM, default_margin_size * EDSCALE * 0.5);
- style_tooltip->set_bg_color(mono_color.inverted() * Color(1, 1, 1, 0.9));
+ style_tooltip->set_bg_color(dark_color_3 * Color(0.8, 0.8, 0.8, 0.9));
style_tooltip->set_border_width_all(0);
theme->set_color("font_color", "TooltipLabel", font_hover_color);
theme->set_color("font_color_shadow", "TooltipLabel", Color(0, 0, 0, 0));
@@ -1216,6 +1236,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// SpinBox
theme->set_icon("updown", "SpinBox", theme->get_icon("GuiSpinboxUpdown", "EditorIcons"));
+ theme->set_icon("updown_disabled", "SpinBox", theme->get_icon("GuiSpinboxUpdownDisabled", "EditorIcons"));
// ProgressBar
theme->set_stylebox("bg", "ProgressBar", make_stylebox(theme->get_icon("GuiProgressBar", "EditorIcons"), 4, 4, 4, 4, 0, 0, 0, 0));
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index daee61c2dd..5dd5c050e0 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2158,6 +2158,14 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da
return true;
}
+ if (drag_data.has("type") && String(drag_data["type"]) == "nodes") {
+ // Save branch as scene.
+ String to_dir;
+ bool favorite;
+ _get_drag_target_folder(to_dir, favorite, p_point, p_from);
+ return !favorite && Array(drag_data["nodes"]).size() == 1;
+ }
+
return false;
}
@@ -2296,6 +2304,13 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data,
_update_tree(_compute_uncollapsed_paths());
}
}
+
+ if (drag_data.has("type") && String(drag_data["type"]) == "nodes") {
+ String to_dir;
+ bool favorite;
+ _get_drag_target_folder(to_dir, favorite, p_point, p_from);
+ EditorNode::get_singleton()->get_scene_tree_dock()->save_branch_to_file(to_dir);
+ }
}
void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favorites, const Point2 &p_point, Control *p_from) const {
diff --git a/editor/icons/AnimatableBody2D.svg b/editor/icons/AnimatableBody2D.svg
new file mode 100644
index 0000000000..f4fed813c9
--- /dev/null
+++ b/editor/icons/AnimatableBody2D.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#8da5f3" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#8da5f3"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg>
diff --git a/editor/icons/AnimatableBody3D.svg b/editor/icons/AnimatableBody3D.svg
new file mode 100644
index 0000000000..2e472f0625
--- /dev/null
+++ b/editor/icons/AnimatableBody3D.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc7f7f" fill-opacity=".99" stroke-width="1.08904"><path d="m10.86822576 4.28299076h1.99947744v1.99947744h-1.99947744z"/><path d="m10.86822576 10.84273776h1.99947744v1.99947744h-1.99947744z"/><path d="m4.71256576 10.84273776h1.99947744v1.99947744h-1.99947744z"/></g><g fill="none" stroke="#fc7f7f"><path d="m1.635 8.161v4.848c0 .713.579 1.293 1.292 1.293h9.857c.713 0 1.291-.58 1.291-1.293v-9.854c0-.714-.578-1.293-1.291-1.293h-5.526" stroke-width="1.07" transform="matrix(.939225 0 0 .938055 1.27996 1.07595)"/><path d="m1.339 1.364 2.539 2.539" stroke-width=".74" transform="matrix(2.04823 .655864 .655864 2.04823 -1.51683 -1.5267)"/><path d="m1.436 1.461 1.168 1.168" stroke-width="1.18" transform="matrix(1.69185 0 0 1.69185 4.50755 -.792876)"/><path d="m1.385 1.41 1.219 1.219" stroke-width="1.22" transform="matrix(1.63859 0 0 1.63859 -.688679 4.82985)"/></g></svg>
diff --git a/editor/icons/GuiCheckedDisabled.svg b/editor/icons/GuiCheckedDisabled.svg
new file mode 100644
index 0000000000..6252176241
--- /dev/null
+++ b/editor/icons/GuiCheckedDisabled.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080"/><path d="m11.501 3.734-5.612 5.612-1.704-1.681-1.5 1.5 3.204 3.181 7.111-7.113z" fill="#b3b3b3"/></svg>
diff --git a/editor/icons/GuiRadioCheckedDisabled.svg b/editor/icons/GuiRadioCheckedDisabled.svg
new file mode 100644
index 0000000000..94a1fffa0b
--- /dev/null
+++ b/editor/icons/GuiRadioCheckedDisabled.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080"/><path d="m12 8c0 2.209-1.791 4-4 4s-4-1.791-4-4 1.791-4 4-4 4 1.791 4 4" fill="#b3b3b3"/></svg>
diff --git a/editor/icons/GuiRadioUncheckedDisabled.svg b/editor/icons/GuiRadioUncheckedDisabled.svg
new file mode 100644
index 0000000000..3a75797c4d
--- /dev/null
+++ b/editor/icons/GuiRadioUncheckedDisabled.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m15 8c0 3.866-3.134 7-7 7s-7-3.134-7-7 3.134-7 7-7 7 3.134 7 7" fill="#808080" fill-opacity=".1882"/></svg>
diff --git a/editor/icons/GuiSpinboxUpdownDisabled.svg b/editor/icons/GuiSpinboxUpdownDisabled.svg
new file mode 100644
index 0000000000..332c5e7bf8
--- /dev/null
+++ b/editor/icons/GuiSpinboxUpdownDisabled.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7.984 1.002c-.259.004-.507.108-.691.291l-4 4c-.398.383-.41 1.016-.027 1.414s1.016.41 1.414.027l.027-.027 3.293-3.293 3.293 3.293c.383.398 1.016.41 1.414.027s.41-1.016.027-1.414c-.01-.009-.018-.018-.027-.027l-4-4c-.191-.19-.452-.296-.723-.291zm4.006 7.984c-.264.006-.514.117-.697.307l-3.293 3.293-3.293-3.293c-.188-.193-.447-.303-.717-.303-.552 0-1 .448-1 1 0 .271.109.529.303.717l4 4c.391.391 1.023.391 1.414 0l4-4c.398-.383.41-1.016.027-1.414-.193-.202-.463-.313-.744-.307z" fill="#b3b3b3" fill-opacity=".7843"/></svg>
diff --git a/editor/icons/GuiUncheckedDisabled.svg b/editor/icons/GuiUncheckedDisabled.svg
new file mode 100644
index 0000000000..2b6515f9d7
--- /dev/null
+++ b/editor/icons/GuiUncheckedDisabled.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.333 1c-1.289 0-2.333 1.045-2.333 2.333v9.333c0 1.289 1.044 2.334 2.333 2.334h9.333c1.289 0 2.334-1.045 2.334-2.334v-9.333c0-1.289-1.045-2.333-2.334-2.333z" fill="#808080" fill-opacity=".1882"/></svg>
diff --git a/editor/icons/Listener2D.svg b/editor/icons/Listener2D.svg
new file mode 100644
index 0000000000..db84dcfed7
--- /dev/null
+++ b/editor/icons/Listener2D.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 1a5 5 0 0 0 -5 5h2a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.75-.54175 2.3583-1.1406 2.8574-.29944.2495-.62954.44071-.97656.69141-.17351.1253-.35729.26529-.53711.49219-.17982.227-.3457.58398-.3457.95898 0 1.2778-.31632 1.5742-.63867 1.7676-.32236.1934-.86133.23242-1.3613.23242h-1v2h1c.5 0 1.461.038922 2.3887-.51758.87316-.5239 1.4826-1.6633 1.5566-3.2266.011365-.0098.027247-.024684.10938-.083984.21547-.1556.63537-.40194 1.0859-.77734.90112-.751 1.8594-2.1445 1.8594-4.3945a5 5 0 0 0 -5-5zm7.9277 1-1.7383 1.0039a6 6 0 0 1 .81055 2.9961 6 6 0 0 1 -.80859 2.998l1.7363 1.002a8 8 0 0 0 0-8z" fill="#a5b7f3"/></svg>
diff --git a/editor/icons/PageFirst.svg b/editor/icons/PageFirst.svg
new file mode 100644
index 0000000000..76078691ef
--- /dev/null
+++ b/editor/icons/PageFirst.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PageFirst.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="74.25"
+ inkscape:cx="18.053872"
+ inkscape:cy="6.5252525"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="M 6,9 3,6 6,3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+ <path
+ d="M 9,9 V 3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2211"
+ sodipodi:nodetypes="cc" />
+</svg>
diff --git a/editor/icons/PageLast.svg b/editor/icons/PageLast.svg
new file mode 100644
index 0000000000..17c874e8c9
--- /dev/null
+++ b/editor/icons/PageLast.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PageLast.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="74.25"
+ inkscape:cx="18.053872"
+ inkscape:cy="6.5252525"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="m 6.0000414,9 3,-3 -3,-3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+ <path
+ d="M 3.0000414,9 V 3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2211"
+ sodipodi:nodetypes="cc" />
+</svg>
diff --git a/editor/icons/PageNext.svg b/editor/icons/PageNext.svg
new file mode 100644
index 0000000000..89ff6219bb
--- /dev/null
+++ b/editor/icons/PageNext.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PageNext.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="105.00536"
+ inkscape:cx="4.5854803"
+ inkscape:cy="5.9377923"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="m 4.5000207,9 3,-3 -3,-3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+</svg>
diff --git a/editor/icons/PagePrevious.svg b/editor/icons/PagePrevious.svg
new file mode 100644
index 0000000000..a2fa84da0c
--- /dev/null
+++ b/editor/icons/PagePrevious.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ height="12"
+ viewBox="0 0 12 12"
+ width="12"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="PagePrevious.svg"
+ inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="true"
+ inkscape:zoom="105.00536"
+ inkscape:cx="4.5854803"
+ inkscape:cy="5.9377923"
+ inkscape:window-width="3838"
+ inkscape:window-height="1582"
+ inkscape:window-x="0"
+ inkscape:window-y="16"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid989" />
+ </sodipodi:namedview>
+ <path
+ d="m 7.4999793,9 -3,-3 3,-3"
+ style="fill:none;stroke:#e0e0e0;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path2" />
+</svg>
diff --git a/editor/icons/SeparationRayShape2D.svg b/editor/icons/SeparationRayShape2D.svg
new file mode 100644
index 0000000000..aa8cee1210
--- /dev/null
+++ b/editor/icons/SeparationRayShape2D.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1a1 1 0 0 0 -1 1v9.5859l-1.293-1.293a1 1 0 0 0 -.7207-.29102 1 1 0 0 0 -.69336.29102 1 1 0 0 0 0 1.4141l3 3a1.0001 1.0001 0 0 0 .0039062.003907 1 1 0 0 0 .050781.044921 1.0001 1.0001 0 0 0 .03125.027344 1 1 0 0 0 .048828.035156 1.0001 1.0001 0 0 0 .023438.015625 1 1 0 0 0 .076172.044922 1.0001 1.0001 0 0 0 .0058593.003906 1 1 0 0 0 .013672.007813 1.0001 1.0001 0 0 0 .078125.035156 1 1 0 0 0 .074219.025391 1.0001 1.0001 0 0 0 .025391.009766 1 1 0 0 0 .039062.009765 1.0001 1.0001 0 0 0 .068359.013672 1.0001 1.0001 0 0 0 .097656.011719 1.0001 1.0001 0 0 0 .0078125 0 1 1 0 0 0 .0625.003906 1 1 0 0 0 .015625-.001953 1.0001 1.0001 0 0 0 .083984-.003906 1 1 0 0 0 .015625-.001953 1.0001 1.0001 0 0 0 .083984-.013672 1.0001 1.0001 0 0 0 .052734-.013672 1 1 0 0 0 .058594-.015625 1.0001 1.0001 0 0 0 .078125-.029297 1 1 0 0 0 .013672-.00586 1.0001 1.0001 0 0 0 .076172-.037109 1 1 0 0 0 .013672-.007812 1.0001 1.0001 0 0 0 .072266-.044922 1 1 0 0 0 .011719-.007813 1.0001 1.0001 0 0 0 .068359-.052734 1 1 0 0 0 .011719-.009766 1.0001 1.0001 0 0 0 .050781-.046875l.0097657-.011719 2.9902-2.9883a1 1 0 0 0 0-1.4141 1 1 0 0 0 -1.4141 0l-1.293 1.293v-9.5859a1 1 0 0 0 -1-1z" fill="#68b6ff" fill-rule="evenodd"/></svg>
diff --git a/editor/icons/SeparationRayShape3D.svg b/editor/icons/SeparationRayShape3D.svg
new file mode 100644
index 0000000000..44d32fe83b
--- /dev/null
+++ b/editor/icons/SeparationRayShape3D.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m8 1-6 5 4 2.666v4.334l2 2v-5-2z" fill="#a2d2ff"/><path d="m8 1v7 2l-2-1.334v1.334l2 1.5v3.5l2-2v-4.334l4-2.666z" fill="#2998ff"/></g></svg>
diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp
index 71930e1e59..b6d0927ce6 100644
--- a/editor/import/collada.cpp
+++ b/editor/import/collada.cpp
@@ -287,7 +287,7 @@ void Collada::_parse_image(XMLParser &parser) {
if (state.version < State::Version(1, 4, 0)) {
/* <1.4 */
String path = parser.get_attribute_value("source").strip_edges();
- if (path.find("://") == -1 && path.is_rel_path()) {
+ if (path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path.uri_decode()));
}
@@ -300,7 +300,7 @@ void Collada::_parse_image(XMLParser &parser) {
parser.read();
String path = parser.get_node_data().strip_edges().uri_decode();
- if (path.find("://") == -1 && path.is_rel_path()) {
+ if (path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path));
diff --git a/editor/import/dynamicfont_import_settings.cpp b/editor/import/dynamicfont_import_settings.cpp
new file mode 100644
index 0000000000..37ca40287f
--- /dev/null
+++ b/editor/import/dynamicfont_import_settings.cpp
@@ -0,0 +1,1889 @@
+/*************************************************************************/
+/* dynamicfont_import_settings.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#include "dynamicfont_import_settings.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+
+/*************************************************************************/
+/* Settings data */
+/*************************************************************************/
+
+class DynamicFontImportSettingsData : public RefCounted {
+ GDCLASS(DynamicFontImportSettingsData, RefCounted)
+ friend class DynamicFontImportSettings;
+
+ Map<StringName, Variant> settings;
+ Map<StringName, Variant> defaults;
+ List<ResourceImporter::ImportOption> options;
+ DynamicFontImportSettings *owner = nullptr;
+
+ bool _set(const StringName &p_name, const Variant &p_value) {
+ if (defaults.has(p_name) && defaults[p_name] == p_value) {
+ settings.erase(p_name);
+ } else {
+ settings[p_name] = p_value;
+ }
+ return true;
+ }
+
+ bool _get(const StringName &p_name, Variant &r_ret) const {
+ if (settings.has(p_name)) {
+ r_ret = settings[p_name];
+ return true;
+ }
+ if (defaults.has(p_name)) {
+ r_ret = defaults[p_name];
+ return true;
+ }
+ return false;
+ }
+
+ void _get_property_list(List<PropertyInfo> *p_list) const {
+ for (const List<ResourceImporter::ImportOption>::Element *E = options.front(); E; E = E->next()) {
+ if (owner && owner->import_settings_data.is_valid()) {
+ if (owner->import_settings_data->get("multichannel_signed_distance_field") && (E->get().option.name == "size" || E->get().option.name == "outline_size" || E->get().option.name == "oversampling")) {
+ continue;
+ }
+ if (!owner->import_settings_data->get("multichannel_signed_distance_field") && (E->get().option.name == "msdf_pixel_range" || E->get().option.name == "msdf_size")) {
+ continue;
+ }
+ }
+ p_list->push_back(E->get().option);
+ }
+ }
+};
+
+/*************************************************************************/
+/* Glyph ranges */
+/*************************************************************************/
+
+struct UniRange {
+ int32_t start;
+ int32_t end;
+ String name;
+};
+
+static UniRange unicode_ranges[] = {
+ { 0x0000, 0x007F, U"Basic Latin" },
+ { 0x0080, 0x00FF, U"Latin-1 Supplement" },
+ { 0x0100, 0x017F, U"Latin Extended-A" },
+ { 0x0180, 0x024F, U"Latin Extended-B" },
+ { 0x0250, 0x02AF, U"IPA Extensions" },
+ { 0x02B0, 0x02FF, U"Spacing Modifier Letters" },
+ { 0x0300, 0x036F, U"Combining Diacritical Marks" },
+ { 0x0370, 0x03FF, U"Greek and Coptic" },
+ { 0x0400, 0x04FF, U"Cyrillic" },
+ { 0x0500, 0x052F, U"Cyrillic Supplement" },
+ { 0x0530, 0x058F, U"Armenian" },
+ { 0x0590, 0x05FF, U"Hebrew" },
+ { 0x0600, 0x06FF, U"Arabic" },
+ { 0x0700, 0x074F, U"Syriac" },
+ { 0x0750, 0x077F, U"Arabic Supplement" },
+ { 0x0780, 0x07BF, U"Thaana" },
+ { 0x07C0, 0x07FF, U"N'Ko" },
+ { 0x0800, 0x083F, U"Samaritan" },
+ { 0x0840, 0x085F, U"Mandaic" },
+ { 0x0860, 0x086F, U"Syriac Supplement" },
+ { 0x08A0, 0x08FF, U"Arabic Extended-A" },
+ { 0x0900, 0x097F, U"Devanagari" },
+ { 0x0980, 0x09FF, U"Bengali" },
+ { 0x0A00, 0x0A7F, U"Gurmukhi" },
+ { 0x0A80, 0x0AFF, U"Gujarati" },
+ { 0x0B00, 0x0B7F, U"Oriya" },
+ { 0x0B80, 0x0BFF, U"Tamil" },
+ { 0x0C00, 0x0C7F, U"Telugu" },
+ { 0x0C80, 0x0CFF, U"Kannada" },
+ { 0x0D00, 0x0D7F, U"Malayalam" },
+ { 0x0D80, 0x0DFF, U"Sinhala" },
+ { 0x0E00, 0x0E7F, U"Thai" },
+ { 0x0E80, 0x0EFF, U"Lao" },
+ { 0x0F00, 0x0FFF, U"Tibetan" },
+ { 0x1000, 0x109F, U"Myanmar" },
+ { 0x10A0, 0x10FF, U"Georgian" },
+ { 0x1100, 0x11FF, U"Hangul Jamo" },
+ { 0x1200, 0x137F, U"Ethiopic" },
+ { 0x1380, 0x139F, U"Ethiopic Supplement" },
+ { 0x13A0, 0x13FF, U"Cherokee" },
+ { 0x1400, 0x167F, U"Unified Canadian Aboriginal Syllabics" },
+ { 0x1680, 0x169F, U"Ogham" },
+ { 0x16A0, 0x16FF, U"Runic" },
+ { 0x1700, 0x171F, U"Tagalog" },
+ { 0x1720, 0x173F, U"Hanunoo" },
+ { 0x1740, 0x175F, U"Buhid" },
+ { 0x1760, 0x177F, U"Tagbanwa" },
+ { 0x1780, 0x17FF, U"Khmer" },
+ { 0x1800, 0x18AF, U"Mongolian" },
+ { 0x18B0, 0x18FF, U"Unified Canadian Aboriginal Syllabics Extended" },
+ { 0x1900, 0x194F, U"Limbu" },
+ { 0x1950, 0x197F, U"Tai Le" },
+ { 0x1980, 0x19DF, U"New Tai Lue" },
+ { 0x19E0, 0x19FF, U"Khmer Symbols" },
+ { 0x1A00, 0x1A1F, U"Buginese" },
+ { 0x1A20, 0x1AAF, U"Tai Tham" },
+ { 0x1AB0, 0x1AFF, U"Combining Diacritical Marks Extended" },
+ { 0x1B00, 0x1B7F, U"Balinese" },
+ { 0x1B80, 0x1BBF, U"Sundanese" },
+ { 0x1BC0, 0x1BFF, U"Batak" },
+ { 0x1C00, 0x1C4F, U"Lepcha" },
+ { 0x1C50, 0x1C7F, U"Ol Chiki" },
+ { 0x1C80, 0x1C8F, U"Cyrillic Extended-C" },
+ { 0x1C90, 0x1CBF, U"Georgian Extended" },
+ { 0x1CC0, 0x1CCF, U"Sundanese Supplement" },
+ { 0x1CD0, 0x1CFF, U"Vedic Extensions" },
+ { 0x1D00, 0x1D7F, U"Phonetic Extensions" },
+ { 0x1D80, 0x1DBF, U"Phonetic Extensions Supplement" },
+ { 0x1DC0, 0x1DFF, U"Combining Diacritical Marks Supplement" },
+ { 0x1E00, 0x1EFF, U"Latin Extended Additional" },
+ { 0x1F00, 0x1FFF, U"Greek Extended" },
+ { 0x2000, 0x206F, U"General Punctuation" },
+ { 0x2070, 0x209F, U"Superscripts and Subscripts" },
+ { 0x20A0, 0x20CF, U"Currency Symbols" },
+ { 0x20D0, 0x20FF, U"Combining Diacritical Marks for Symbols" },
+ { 0x2100, 0x214F, U"Letterlike Symbols" },
+ { 0x2150, 0x218F, U"Number Forms" },
+ { 0x2190, 0x21FF, U"Arrows" },
+ { 0x2200, 0x22FF, U"Mathematical Operators" },
+ { 0x2300, 0x23FF, U"Miscellaneous Technical" },
+ { 0x2400, 0x243F, U"Control Pictures" },
+ { 0x2440, 0x245F, U"Optical Character Recognition" },
+ { 0x2460, 0x24FF, U"Enclosed Alphanumerics" },
+ { 0x2500, 0x257F, U"Box Drawing" },
+ { 0x2580, 0x259F, U"Block Elements" },
+ { 0x25A0, 0x25FF, U"Geometric Shapes" },
+ { 0x2600, 0x26FF, U"Miscellaneous Symbols" },
+ { 0x2700, 0x27BF, U"Dingbats" },
+ { 0x27C0, 0x27EF, U"Miscellaneous Mathematical Symbols-A" },
+ { 0x27F0, 0x27FF, U"Supplemental Arrows-A" },
+ { 0x2800, 0x28FF, U"Braille Patterns" },
+ { 0x2900, 0x297F, U"Supplemental Arrows-B" },
+ { 0x2980, 0x29FF, U"Miscellaneous Mathematical Symbols-B" },
+ { 0x2A00, 0x2AFF, U"Supplemental Mathematical Operators" },
+ { 0x2B00, 0x2BFF, U"Miscellaneous Symbols and Arrows" },
+ { 0x2C00, 0x2C5F, U"Glagolitic" },
+ { 0x2C60, 0x2C7F, U"Latin Extended-C" },
+ { 0x2C80, 0x2CFF, U"Coptic" },
+ { 0x2D00, 0x2D2F, U"Georgian Supplement" },
+ { 0x2D30, 0x2D7F, U"Tifinagh" },
+ { 0x2D80, 0x2DDF, U"Ethiopic Extended" },
+ { 0x2DE0, 0x2DFF, U"Cyrillic Extended-A" },
+ { 0x2E00, 0x2E7F, U"Supplemental Punctuation" },
+ { 0x2E80, 0x2EFF, U"CJK Radicals Supplement" },
+ { 0x2F00, 0x2FDF, U"Kangxi Radicals" },
+ { 0x2FF0, 0x2FFF, U"Ideographic Description Characters" },
+ { 0x3000, 0x303F, U"CJK Symbols and Punctuation" },
+ { 0x3040, 0x309F, U"Hiragana" },
+ { 0x30A0, 0x30FF, U"Katakana" },
+ { 0x3100, 0x312F, U"Bopomofo" },
+ { 0x3130, 0x318F, U"Hangul Compatibility Jamo" },
+ { 0x3190, 0x319F, U"Kanbun" },
+ { 0x31A0, 0x31BF, U"Bopomofo Extended" },
+ { 0x31C0, 0x31EF, U"CJK Strokes" },
+ { 0x31F0, 0x31FF, U"Katakana Phonetic Extensions" },
+ { 0x3200, 0x32FF, U"Enclosed CJK Letters and Months" },
+ { 0x3300, 0x33FF, U"CJK Compatibility" },
+ { 0x3400, 0x4DBF, U"CJK Unified Ideographs Extension A" },
+ { 0x4DC0, 0x4DFF, U"Yijing Hexagram Symbols" },
+ { 0x4E00, 0x9FFF, U"CJK Unified Ideographs" },
+ { 0xA000, 0xA48F, U"Yi Syllables" },
+ { 0xA490, 0xA4CF, U"Yi Radicals" },
+ { 0xA4D0, 0xA4FF, U"Lisu" },
+ { 0xA500, 0xA63F, U"Vai" },
+ { 0xA640, 0xA69F, U"Cyrillic Extended-B" },
+ { 0xA6A0, 0xA6FF, U"Bamum" },
+ { 0xA700, 0xA71F, U"Modifier Tone Letters" },
+ { 0xA720, 0xA7FF, U"Latin Extended-D" },
+ { 0xA800, 0xA82F, U"Syloti Nagri" },
+ { 0xA830, 0xA83F, U"Common Indic Number Forms" },
+ { 0xA840, 0xA87F, U"Phags-pa" },
+ { 0xA880, 0xA8DF, U"Saurashtra" },
+ { 0xA8E0, 0xA8FF, U"Devanagari Extended" },
+ { 0xA900, 0xA92F, U"Kayah Li" },
+ { 0xA930, 0xA95F, U"Rejang" },
+ { 0xA960, 0xA97F, U"Hangul Jamo Extended-A" },
+ { 0xA980, 0xA9DF, U"Javanese" },
+ { 0xA9E0, 0xA9FF, U"Myanmar Extended-B" },
+ { 0xAA00, 0xAA5F, U"Cham" },
+ { 0xAA60, 0xAA7F, U"Myanmar Extended-A" },
+ { 0xAA80, 0xAADF, U"Tai Viet" },
+ { 0xAAE0, 0xAAFF, U"Meetei Mayek Extensions" },
+ { 0xAB00, 0xAB2F, U"Ethiopic Extended-A" },
+ { 0xAB30, 0xAB6F, U"Latin Extended-E" },
+ { 0xAB70, 0xABBF, U"Cherokee Supplement" },
+ { 0xABC0, 0xABFF, U"Meetei Mayek" },
+ { 0xD7B0, 0xD7FF, U"Hangul Jamo Extended-B" },
+ //{ 0xF800, 0xDFFF, U"Surrogates" },
+ { 0xE000, 0xE2FE, U"Private Use Area" },
+ { 0xF900, 0xFAFF, U"CJK Compatibility Ideographs" },
+ { 0xFB00, 0xFB4F, U"Alphabetic Presentation Forms" },
+ { 0xFB50, 0xFDFF, U"Arabic Presentation Forms-A" },
+ //{ 0xFE00, 0xFE0F, U"Variation Selectors" },
+ { 0xFE10, 0xFE1F, U"Vertical Forms" },
+ { 0xFE20, 0xFE2F, U"Combining Half Marks" },
+ { 0xFE30, 0xFE4F, U"CJK Compatibility Forms" },
+ { 0xFE50, 0xFE6F, U"Small Form Variants" },
+ { 0xFE70, 0xFEFF, U"Arabic Presentation Forms-B" },
+ { 0xFF00, 0xFFEF, U"Halfwidth and Fullwidth Forms" },
+ //{ 0xFFF0, 0xFFFF, U"Specials" },
+ { 0x10000, 0x1007F, U"Linear B Syllabary" },
+ { 0x10080, 0x100FF, U"Linear B Ideograms" },
+ { 0x10100, 0x1013F, U"Aegean Numbers" },
+ { 0x10140, 0x1018F, U"Ancient Greek Numbers" },
+ { 0x10190, 0x101CF, U"Ancient Symbols" },
+ { 0x101D0, 0x101FF, U"Phaistos Disc" },
+ { 0x10280, 0x1029F, U"Lycian" },
+ { 0x102A0, 0x102DF, U"Carian" },
+ { 0x102E0, 0x102FF, U"Coptic Epact Numbers" },
+ { 0x10300, 0x1032F, U"Old Italic" },
+ { 0x10330, 0x1034F, U"Gothic" },
+ { 0x10350, 0x1037F, U"Old Permic" },
+ { 0x10380, 0x1039F, U"Ugaritic" },
+ { 0x103A0, 0x103DF, U"Old Persian" },
+ { 0x10400, 0x1044F, U"Deseret" },
+ { 0x10450, 0x1047F, U"Shavian" },
+ { 0x10480, 0x104AF, U"Osmanya" },
+ { 0x104B0, 0x104FF, U"Osage" },
+ { 0x10500, 0x1052F, U"Elbasan" },
+ { 0x10530, 0x1056F, U"Caucasian Albanian" },
+ { 0x10600, 0x1077F, U"Linear A" },
+ { 0x10800, 0x1083F, U"Cypriot Syllabary" },
+ { 0x10840, 0x1085F, U"Imperial Aramaic" },
+ { 0x10860, 0x1087F, U"Palmyrene" },
+ { 0x10880, 0x108AF, U"Nabataean" },
+ { 0x108E0, 0x108FF, U"Hatran" },
+ { 0x10900, 0x1091F, U"Phoenician" },
+ { 0x10920, 0x1093F, U"Lydian" },
+ { 0x10980, 0x1099F, U"Meroitic Hieroglyphs" },
+ { 0x109A0, 0x109FF, U"Meroitic Cursive" },
+ { 0x10A00, 0x10A5F, U"Kharoshthi" },
+ { 0x10A60, 0x10A7F, U"Old South Arabian" },
+ { 0x10A80, 0x10A9F, U"Old North Arabian" },
+ { 0x10AC0, 0x10AFF, U"Manichaean" },
+ { 0x10B00, 0x10B3F, U"Avestan" },
+ { 0x10B40, 0x10B5F, U"Inscriptional Parthian" },
+ { 0x10B60, 0x10B7F, U"Inscriptional Pahlavi" },
+ { 0x10B80, 0x10BAF, U"Psalter Pahlavi" },
+ { 0x10C00, 0x10C4F, U"Old Turkic" },
+ { 0x10C80, 0x10CFF, U"Old Hungarian" },
+ { 0x10D00, 0x10D3F, U"Hanifi Rohingya" },
+ { 0x10E60, 0x10E7F, U"Rumi Numeral Symbols" },
+ { 0x10E80, 0x10EBF, U"Yezidi" },
+ { 0x10F00, 0x10F2F, U"Old Sogdian" },
+ { 0x10F30, 0x10F6F, U"Sogdian" },
+ { 0x10FB0, 0x10FDF, U"Chorasmian" },
+ { 0x10FE0, 0x10FFF, U"Elymaic" },
+ { 0x11000, 0x1107F, U"Brahmi" },
+ { 0x11080, 0x110CF, U"Kaithi" },
+ { 0x110D0, 0x110FF, U"Sora Sompeng" },
+ { 0x11100, 0x1114F, U"Chakma" },
+ { 0x11150, 0x1117F, U"Mahajani" },
+ { 0x11180, 0x111DF, U"Sharada" },
+ { 0x111E0, 0x111FF, U"Sinhala Archaic Numbers" },
+ { 0x11200, 0x1124F, U"Khojki" },
+ { 0x11280, 0x112AF, U"Multani" },
+ { 0x112B0, 0x112FF, U"Khudawadi" },
+ { 0x11300, 0x1137F, U"Grantha" },
+ { 0x11400, 0x1147F, U"Newa" },
+ { 0x11480, 0x114DF, U"Tirhuta" },
+ { 0x11580, 0x115FF, U"Siddham" },
+ { 0x11600, 0x1165F, U"Modi" },
+ { 0x11660, 0x1167F, U"Mongolian Supplement" },
+ { 0x11680, 0x116CF, U"Takri" },
+ { 0x11700, 0x1173F, U"Ahom" },
+ { 0x11800, 0x1184F, U"Dogra" },
+ { 0x118A0, 0x118FF, U"Warang Citi" },
+ { 0x11900, 0x1195F, U"Dives Akuru" },
+ { 0x119A0, 0x119FF, U"Nandinagari" },
+ { 0x11A00, 0x11A4F, U"Zanabazar Square" },
+ { 0x11A50, 0x11AAF, U"Soyombo" },
+ { 0x11AC0, 0x11AFF, U"Pau Cin Hau" },
+ { 0x11C00, 0x11C6F, U"Bhaiksuki" },
+ { 0x11C70, 0x11CBF, U"Marchen" },
+ { 0x11D00, 0x11D5F, U"Masaram Gondi" },
+ { 0x11D60, 0x11DAF, U"Gunjala Gondi" },
+ { 0x11EE0, 0x11EFF, U"Makasar" },
+ { 0x11FB0, 0x11FBF, U"Lisu Supplement" },
+ { 0x11FC0, 0x11FFF, U"Tamil Supplement" },
+ { 0x12000, 0x123FF, U"Cuneiform" },
+ { 0x12400, 0x1247F, U"Cuneiform Numbers and Punctuation" },
+ { 0x12480, 0x1254F, U"Early Dynastic Cuneiform" },
+ { 0x13000, 0x1342F, U"Egyptian Hieroglyphs" },
+ { 0x13430, 0x1343F, U"Egyptian Hieroglyph Format Controls" },
+ { 0x14400, 0x1467F, U"Anatolian Hieroglyphs" },
+ { 0x16800, 0x16A3F, U"Bamum Supplement" },
+ { 0x16A40, 0x16A6F, U"Mro" },
+ { 0x16AD0, 0x16AFF, U"Bassa Vah" },
+ { 0x16B00, 0x16B8F, U"Pahawh Hmong" },
+ { 0x16E40, 0x16E9F, U"Medefaidrin" },
+ { 0x16F00, 0x16F9F, U"Miao" },
+ { 0x16FE0, 0x16FFF, U"Ideographic Symbols and Punctuation" },
+ { 0x17000, 0x187FF, U"Tangut" },
+ { 0x18800, 0x18AFF, U"Tangut Components" },
+ { 0x18B00, 0x18CFF, U"Khitan Small Script" },
+ { 0x18D00, 0x18D8F, U"Tangut Supplement" },
+ { 0x1B000, 0x1B0FF, U"Kana Supplement" },
+ { 0x1B100, 0x1B12F, U"Kana Extended-A" },
+ { 0x1B130, 0x1B16F, U"Small Kana Extension" },
+ { 0x1B170, 0x1B2FF, U"Nushu" },
+ { 0x1BC00, 0x1BC9F, U"Duployan" },
+ { 0x1BCA0, 0x1BCAF, U"Shorthand Format Controls" },
+ { 0x1D000, 0x1D0FF, U"Byzantine Musical Symbols" },
+ { 0x1D100, 0x1D1FF, U"Musical Symbols" },
+ { 0x1D200, 0x1D24F, U"Ancient Greek Musical Notation" },
+ { 0x1D2E0, 0x1D2FF, U"Mayan Numerals" },
+ { 0x1D300, 0x1D35F, U"Tai Xuan Jing Symbols" },
+ { 0x1D360, 0x1D37F, U"Counting Rod Numerals" },
+ { 0x1D400, 0x1D7FF, U"Mathematical Alphanumeric Symbols" },
+ { 0x1D800, 0x1DAAF, U"Sutton SignWriting" },
+ { 0x1E000, 0x1E02F, U"Glagolitic Supplement" },
+ { 0x1E100, 0x1E14F, U"Nyiakeng Puachue Hmong" },
+ { 0x1E2C0, 0x1E2FF, U"Wancho" },
+ { 0x1E800, 0x1E8DF, U"Mende Kikakui" },
+ { 0x1E900, 0x1E95F, U"Adlam" },
+ { 0x1EC70, 0x1ECBF, U"Indic Siyaq Numbers" },
+ { 0x1ED00, 0x1ED4F, U"Ottoman Siyaq Numbers" },
+ { 0x1EE00, 0x1EEFF, U"Arabic Mathematical Alphabetic Symbols" },
+ { 0x1F000, 0x1F02F, U"Mahjong Tiles" },
+ { 0x1F030, 0x1F09F, U"Domino Tiles" },
+ { 0x1F0A0, 0x1F0FF, U"Playing Cards" },
+ { 0x1F100, 0x1F1FF, U"Enclosed Alphanumeric Supplement" },
+ { 0x1F200, 0x1F2FF, U"Enclosed Ideographic Supplement" },
+ { 0x1F300, 0x1F5FF, U"Miscellaneous Symbols and Pictographs" },
+ { 0x1F600, 0x1F64F, U"Emoticons" },
+ { 0x1F650, 0x1F67F, U"Ornamental Dingbats" },
+ { 0x1F680, 0x1F6FF, U"Transport and Map Symbols" },
+ { 0x1F700, 0x1F77F, U"Alchemical Symbols" },
+ { 0x1F780, 0x1F7FF, U"Geometric Shapes Extended" },
+ { 0x1F800, 0x1F8FF, U"Supplemental Arrows-C" },
+ { 0x1F900, 0x1F9FF, U"Supplemental Symbols and Pictographs" },
+ { 0x1FA00, 0x1FA6F, U"Chess Symbols" },
+ { 0x1FA70, 0x1FAFF, U"Symbols and Pictographs Extended-A" },
+ { 0x1FB00, 0x1FBFF, U"Symbols for Legacy Computing" },
+ { 0x20000, 0x2A6DF, U"CJK Unified Ideographs Extension B" },
+ { 0x2A700, 0x2B73F, U"CJK Unified Ideographs Extension C" },
+ { 0x2B740, 0x2B81F, U"CJK Unified Ideographs Extension D" },
+ { 0x2B820, 0x2CEAF, U"CJK Unified Ideographs Extension E" },
+ { 0x2CEB0, 0x2EBEF, U"CJK Unified Ideographs Extension F" },
+ { 0x2F800, 0x2FA1F, U"CJK Compatibility Ideographs Supplement" },
+ { 0x30000, 0x3134F, U"CJK Unified Ideographs Extension G" },
+ //{ 0xE0000, 0xE007F, U"Tags" },
+ //{ 0xE0100, 0xE01EF, U"Variation Selectors Supplement" },
+ { 0xF0000, 0xFFFFD, U"Supplementary Private Use Area-A" },
+ { 0x100000, 0x10FFFD, U"Supplementary Private Use Area-B" },
+ { 0x10FFFF, 0x10FFFF, String() }
+};
+
+void DynamicFontImportSettings::_add_glyph_range_item(int32_t p_start, int32_t p_end, const String &p_name) {
+ const int page_size = 512;
+ int pages = (p_end - p_start) / page_size;
+ int remain = (p_end - p_start) % page_size;
+
+ int32_t start = p_start;
+ for (int i = 0; i < pages; i++) {
+ TreeItem *item = glyph_tree->create_item(glyph_root);
+ ERR_FAIL_NULL(item);
+ item->set_text(0, _pad_zeros(String::num_int64(start, 16)) + " - " + _pad_zeros(String::num_int64(start + page_size, 16)));
+ item->set_text(1, p_name);
+ item->set_metadata(0, Vector2i(start, start + page_size));
+ start += page_size;
+ }
+ if (remain > 0) {
+ TreeItem *item = glyph_tree->create_item(glyph_root);
+ ERR_FAIL_NULL(item);
+ item->set_text(0, _pad_zeros(String::num_int64(start, 16)) + " - " + _pad_zeros(String::num_int64(p_end, 16)));
+ item->set_text(1, p_name);
+ item->set_metadata(0, Vector2i(start, p_end));
+ }
+}
+
+/*************************************************************************/
+/* Languages and scripts */
+/*************************************************************************/
+
+struct CodeInfo {
+ String name;
+ String code;
+};
+
+static CodeInfo langs[] = {
+ { U"Custom", U"xx" },
+ { U"-", U"-" },
+ { U"Abkhazian", U"ab" },
+ { U"Afar", U"aa" },
+ { U"Afrikaans", U"af" },
+ { U"Akan", U"ak" },
+ { U"Albanian", U"sq" },
+ { U"Amharic", U"am" },
+ { U"Arabic", U"ar" },
+ { U"Aragonese", U"an" },
+ { U"Armenian", U"hy" },
+ { U"Assamese", U"as" },
+ { U"Avaric", U"av" },
+ { U"Avestan", U"ae" },
+ { U"Aymara", U"ay" },
+ { U"Azerbaijani", U"az" },
+ { U"Bambara", U"bm" },
+ { U"Bashkir", U"ba" },
+ { U"Basque", U"eu" },
+ { U"Belarusian", U"be" },
+ { U"Bengali", U"bn" },
+ { U"Bihari", U"bh" },
+ { U"Bislama", U"bi" },
+ { U"Bosnian", U"bs" },
+ { U"Breton", U"br" },
+ { U"Bulgarian", U"bg" },
+ { U"Burmese", U"my" },
+ { U"Catalan", U"ca" },
+ { U"Chamorro", U"ch" },
+ { U"Chechen", U"ce" },
+ { U"Chichewa", U"ny" },
+ { U"Chinese", U"zh" },
+ { U"Chuvash", U"cv" },
+ { U"Cornish", U"kw" },
+ { U"Corsican", U"co" },
+ { U"Cree", U"cr" },
+ { U"Croatian", U"hr" },
+ { U"Czech", U"cs" },
+ { U"Danish", U"da" },
+ { U"Divehi", U"dv" },
+ { U"Dutch", U"nl" },
+ { U"Dzongkha", U"dz" },
+ { U"English", U"en" },
+ { U"Esperanto", U"eo" },
+ { U"Estonian", U"et" },
+ { U"Ewe", U"ee" },
+ { U"Faroese", U"fo" },
+ { U"Fijian", U"fj" },
+ { U"Finnish", U"fi" },
+ { U"French", U"fr" },
+ { U"Fulah", U"ff" },
+ { U"Galician", U"gl" },
+ { U"Georgian", U"ka" },
+ { U"German", U"de" },
+ { U"Greek", U"el" },
+ { U"Guarani", U"gn" },
+ { U"Gujarati", U"gu" },
+ { U"Haitian", U"ht" },
+ { U"Hausa", U"ha" },
+ { U"Hebrew", U"he" },
+ { U"Herero", U"hz" },
+ { U"Hindi", U"hi" },
+ { U"Hiri Motu", U"ho" },
+ { U"Hungarian", U"hu" },
+ { U"Interlingua", U"ia" },
+ { U"Indonesian", U"id" },
+ { U"Interlingue", U"ie" },
+ { U"Irish", U"ga" },
+ { U"Igbo", U"ig" },
+ { U"Inupiaq", U"ik" },
+ { U"Ido", U"io" },
+ { U"Icelandic", U"is" },
+ { U"Italian", U"it" },
+ { U"Inuktitut", U"iu" },
+ { U"Japanese", U"ja" },
+ { U"Javanese", U"jv" },
+ { U"Kalaallisut", U"kl" },
+ { U"Kannada", U"kn" },
+ { U"Kanuri", U"kr" },
+ { U"Kashmiri", U"ks" },
+ { U"Kazakh", U"kk" },
+ { U"Central Khmer", U"km" },
+ { U"Kikuyu", U"ki" },
+ { U"Kinyarwanda", U"rw" },
+ { U"Kirghiz", U"ky" },
+ { U"Komi", U"kv" },
+ { U"Kongo", U"kg" },
+ { U"Korean", U"ko" },
+ { U"Kurdish", U"ku" },
+ { U"Kuanyama", U"kj" },
+ { U"Latin", U"la" },
+ { U"Luxembourgish", U"lb" },
+ { U"Ganda", U"lg" },
+ { U"Limburgan", U"li" },
+ { U"Lingala", U"ln" },
+ { U"Lao", U"lo" },
+ { U"Lithuanian", U"lt" },
+ { U"Luba-Katanga", U"lu" },
+ { U"Latvian", U"lv" },
+ { U"Man", U"gv" },
+ { U"Macedonian", U"mk" },
+ { U"Malagasy", U"mg" },
+ { U"Malay", U"ms" },
+ { U"Malayalam", U"ml" },
+ { U"Maltese", U"mt" },
+ { U"Maori", U"mi" },
+ { U"Marathi", U"mr" },
+ { U"Marshallese", U"mh" },
+ { U"Mongolian", U"mn" },
+ { U"Nauru", U"na" },
+ { U"Navajo", U"nv" },
+ { U"North Ndebele", U"nd" },
+ { U"Nepali", U"ne" },
+ { U"Ndonga", U"ng" },
+ { U"Norwegian Bokmål", U"nb" },
+ { U"Norwegian Nynorsk", U"nn" },
+ { U"Norwegian", U"no" },
+ { U"Sichuan Yi, Nuosu", U"ii" },
+ { U"South Ndebele", U"nr" },
+ { U"Occitan", U"oc" },
+ { U"Ojibwa", U"oj" },
+ { U"Church Slavic", U"cu" },
+ { U"Oromo", U"om" },
+ { U"Oriya", U"or" },
+ { U"Ossetian", U"os" },
+ { U"Punjabi", U"pa" },
+ { U"Pali", U"pi" },
+ { U"Persian", U"fa" },
+ { U"Polish", U"pl" },
+ { U"Pashto", U"ps" },
+ { U"Portuguese", U"pt" },
+ { U"Quechua", U"qu" },
+ { U"Romansh", U"rm" },
+ { U"Rundi", U"rn" },
+ { U"Romanian", U"ro" },
+ { U"Russian", U"ru" },
+ { U"Sanskrit", U"sa" },
+ { U"Sardinian", U"sc" },
+ { U"Sindhi", U"sd" },
+ { U"Northern Sami", U"se" },
+ { U"Samoan", U"sm" },
+ { U"Sango", U"sg" },
+ { U"Serbian", U"sr" },
+ { U"Gaelic", U"gd" },
+ { U"Shona", U"sn" },
+ { U"Sinhala", U"si" },
+ { U"Slovak", U"sk" },
+ { U"Slovenian", U"sl" },
+ { U"Somali", U"so" },
+ { U"Southern Sotho", U"st" },
+ { U"Spanish", U"es" },
+ { U"Sundanese", U"su" },
+ { U"Swahili", U"sw" },
+ { U"Swati", U"ss" },
+ { U"Swedish", U"sv" },
+ { U"Tamil", U"ta" },
+ { U"Telugu", U"te" },
+ { U"Tajik", U"tg" },
+ { U"Thai", U"th" },
+ { U"Tigrinya", U"ti" },
+ { U"Tibetan", U"bo" },
+ { U"Turkmen", U"tk" },
+ { U"Tagalog", U"tl" },
+ { U"Tswana", U"tn" },
+ { U"Tonga", U"to" },
+ { U"Turkish", U"tr" },
+ { U"Tsonga", U"ts" },
+ { U"Tatar", U"tt" },
+ { U"Twi", U"tw" },
+ { U"Tahitian", U"ty" },
+ { U"Uighur", U"ug" },
+ { U"Ukrainian", U"uk" },
+ { U"Urdu", U"ur" },
+ { U"Uzbek", U"uz" },
+ { U"Venda", U"ve" },
+ { U"Vietnamese", U"vi" },
+ { U"Volapük", U"vo" },
+ { U"Walloon", U"wa" },
+ { U"Welsh", U"cy" },
+ { U"Wolof", U"wo" },
+ { U"Western Frisian", U"fy" },
+ { U"Xhosa", U"xh" },
+ { U"Yiddish", U"yi" },
+ { U"Yoruba", U"yo" },
+ { U"Zhuang", U"za" },
+ { U"Zulu", U"zu" },
+ { String(), String() }
+};
+
+static CodeInfo scripts[] = {
+ { U"Custom", U"Qaaa" },
+ { U"-", U"-" },
+ { U"Adlam", U"Adlm" },
+ { U"Afaka", U"Afak" },
+ { U"Caucasian Albanian", U"Aghb" },
+ { U"Ahom", U"Ahom" },
+ { U"Arabic", U"Arab" },
+ { U"Imperial Aramaic", U"Armi" },
+ { U"Armenian", U"Armn" },
+ { U"Avestan", U"Avst" },
+ { U"Balinese", U"Bali" },
+ { U"Bamum", U"Bamu" },
+ { U"Bassa Vah", U"Bass" },
+ { U"Batak", U"Batk" },
+ { U"Bengali", U"Beng" },
+ { U"Bhaiksuki", U"Bhks" },
+ { U"Blissymbols", U"Blis" },
+ { U"Bopomofo", U"Bopo" },
+ { U"Brahmi", U"Brah" },
+ { U"Braille", U"Brai" },
+ { U"Buginese", U"Bugi" },
+ { U"Buhid", U"Buhd" },
+ { U"Chakma", U"Cakm" },
+ { U"Unified Canadian Aboriginal", U"Cans" },
+ { U"Carian", U"Cari" },
+ { U"Cham", U"Cham" },
+ { U"Cherokee", U"Cher" },
+ { U"Chorasmian", U"Chrs" },
+ { U"Cirth", U"Cirt" },
+ { U"Coptic", U"Copt" },
+ { U"Cypro-Minoan", U"Cpmn" },
+ { U"Cypriot", U"Cprt" },
+ { U"Cyrillic", U"Cyrl" },
+ { U"Devanagari", U"Deva" },
+ { U"Dives Akuru", U"Diak" },
+ { U"Dogra", U"Dogr" },
+ { U"Deseret", U"Dsrt" },
+ { U"Duployan", U"Dupl" },
+ { U"Egyptian demotic", U"Egyd" },
+ { U"Egyptian hieratic", U"Egyh" },
+ { U"Egyptian hieroglyphs", U"Egyp" },
+ { U"Elbasan", U"Elba" },
+ { U"Elymaic", U"Elym" },
+ { U"Ethiopic", U"Ethi" },
+ { U"Khutsuri", U"Geok" },
+ { U"Georgian", U"Geor" },
+ { U"Glagolitic", U"Glag" },
+ { U"Gunjala Gondi", U"Gong" },
+ { U"Masaram Gondi", U"Gonm" },
+ { U"Gothic", U"Goth" },
+ { U"Grantha", U"Gran" },
+ { U"Greek", U"Grek" },
+ { U"Gujarati", U"Gujr" },
+ { U"Gurmukhi", U"Guru" },
+ { U"Hangul", U"Hang" },
+ { U"Han", U"Hani" },
+ { U"Hanunoo", U"Hano" },
+ { U"Hatran", U"Hatr" },
+ { U"Hebrew", U"Hebr" },
+ { U"Hiragana", U"Hira" },
+ { U"Anatolian Hieroglyphs", U"Hluw" },
+ { U"Pahawh Hmong", U"Hmng" },
+ { U"Nyiakeng Puachue Hmong", U"Hmnp" },
+ { U"Old Hungarian", U"Hung" },
+ { U"Indus", U"Inds" },
+ { U"Old Italic", U"Ital" },
+ { U"Javanese", U"Java" },
+ { U"Jurchen", U"Jurc" },
+ { U"Kayah Li", U"Kali" },
+ { U"Katakana", U"Kana" },
+ { U"Kharoshthi", U"Khar" },
+ { U"Khmer", U"Khmr" },
+ { U"Khojki", U"Khoj" },
+ { U"Khitan large script", U"Kitl" },
+ { U"Khitan small script", U"Kits" },
+ { U"Kannada", U"Knda" },
+ { U"Kpelle", U"Kpel" },
+ { U"Kaithi", U"Kthi" },
+ { U"Tai Tham", U"Lana" },
+ { U"Lao", U"Laoo" },
+ { U"Latin", U"Latn" },
+ { U"Leke", U"Leke" },
+ { U"Lepcha", U"Lepc" },
+ { U"Limbu", U"Limb" },
+ { U"Linear A", U"Lina" },
+ { U"Linear B", U"Linb" },
+ { U"Lisu", U"Lisu" },
+ { U"Loma", U"Loma" },
+ { U"Lycian", U"Lyci" },
+ { U"Lydian", U"Lydi" },
+ { U"Mahajani", U"Mahj" },
+ { U"Makasar", U"Maka" },
+ { U"Mandaic", U"Mand" },
+ { U"Manichaean", U"Mani" },
+ { U"Marchen", U"Marc" },
+ { U"Mayan Hieroglyphs", U"Maya" },
+ { U"Medefaidrin", U"Medf" },
+ { U"Mende Kikakui", U"Mend" },
+ { U"Meroitic Cursive", U"Merc" },
+ { U"Meroitic Hieroglyphs", U"Mero" },
+ { U"Malayalam", U"Mlym" },
+ { U"Modi", U"Modi" },
+ { U"Mongolian", U"Mong" },
+ { U"Moon", U"Moon" },
+ { U"Mro", U"Mroo" },
+ { U"Meitei Mayek", U"Mtei" },
+ { U"Multani", U"Mult" },
+ { U"Myanmar (Burmese)", U"Mymr" },
+ { U"Nandinagari", U"Nand" },
+ { U"Old North Arabian", U"Narb" },
+ { U"Nabataean", U"Nbat" },
+ { U"Newa", U"Newa" },
+ { U"Naxi Dongba", U"Nkdb" },
+ { U"Nakhi Geba", U"Nkgb" },
+ { U"N’Ko", U"Nkoo" },
+ { U"Nüshu", U"Nshu" },
+ { U"Ogham", U"Ogam" },
+ { U"Ol Chiki", U"Olck" },
+ { U"Old Turkic", U"Orkh" },
+ { U"Oriya", U"Orya" },
+ { U"Osage", U"Osge" },
+ { U"Osmanya", U"Osma" },
+ { U"Old Uyghur", U"Ougr" },
+ { U"Palmyrene", U"Palm" },
+ { U"Pau Cin Hau", U"Pauc" },
+ { U"Proto-Cuneiform", U"Pcun" },
+ { U"Proto-Elamite", U"Pelm" },
+ { U"Old Permic", U"Perm" },
+ { U"Phags-pa", U"Phag" },
+ { U"Inscriptional Pahlavi", U"Phli" },
+ { U"Psalter Pahlavi", U"Phlp" },
+ { U"Book Pahlavi", U"Phlv" },
+ { U"Phoenician", U"Phnx" },
+ { U"Klingon", U"Piqd" },
+ { U"Miao", U"Plrd" },
+ { U"Inscriptional Parthian", U"Prti" },
+ { U"Proto-Sinaitic", U"Psin" },
+ { U"Ranjana", U"Ranj" },
+ { U"Rejang", U"Rjng" },
+ { U"Hanifi Rohingya", U"Rohg" },
+ { U"Rongorongo", U"Roro" },
+ { U"Runic", U"Runr" },
+ { U"Samaritan", U"Samr" },
+ { U"Sarati", U"Sara" },
+ { U"Old South Arabian", U"Sarb" },
+ { U"Saurashtra", U"Saur" },
+ { U"SignWriting", U"Sgnw" },
+ { U"Shavian", U"Shaw" },
+ { U"Sharada", U"Shrd" },
+ { U"Shuishu", U"Shui" },
+ { U"Siddham", U"Sidd" },
+ { U"Khudawadi", U"Sind" },
+ { U"Sinhala", U"Sinh" },
+ { U"Sogdian", U"Sogd" },
+ { U"Old Sogdian", U"Sogo" },
+ { U"Sora Sompeng", U"Sora" },
+ { U"Soyombo", U"Soyo" },
+ { U"Sundanese", U"Sund" },
+ { U"Syloti Nagri", U"Sylo" },
+ { U"Syriac", U"Syrc" },
+ { U"Tagbanwa", U"Tagb" },
+ { U"Takri", U"Takr" },
+ { U"Tai Le", U"Tale" },
+ { U"New Tai Lue", U"Talu" },
+ { U"Tamil", U"Taml" },
+ { U"Tangut", U"Tang" },
+ { U"Tai Viet", U"Tavt" },
+ { U"Telugu", U"Telu" },
+ { U"Tengwar", U"Teng" },
+ { U"Tifinagh", U"Tfng" },
+ { U"Tagalog", U"Tglg" },
+ { U"Thaana", U"Thaa" },
+ { U"Thai", U"Thai" },
+ { U"Tibetan", U"Tibt" },
+ { U"Tirhuta", U"Tirh" },
+ { U"Tangsa", U"Tnsa" },
+ { U"Toto", U"Toto" },
+ { U"Ugaritic", U"Ugar" },
+ { U"Vai", U"Vaii" },
+ { U"Visible Speech", U"Visp" },
+ { U"Vithkuqi", U"Vith" },
+ { U"Warang Citi", U"Wara" },
+ { U"Wancho", U"Wcho" },
+ { U"Woleai", U"Wole" },
+ { U"Old Persian", U"Xpeo" },
+ { U"Cuneiform", U"Xsux" },
+ { U"Yezidi", U"Yezi" },
+ { U"Yi", U"Yiii" },
+ { U"Zanabazar Square", U"Zanb" },
+ { String(), String() }
+};
+
+/*************************************************************************/
+/* Page 1 callbacks: Rendering Options */
+/*************************************************************************/
+
+void DynamicFontImportSettings::_main_prop_changed(const String &p_edited_property) {
+ // Update font preview.
+
+ if (p_edited_property == "antialiased") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_antialiased(import_settings_data->get("antialiased"));
+ }
+ } else if (p_edited_property == "multichannel_signed_distance_field") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_multichannel_signed_distance_field(import_settings_data->get("multichannel_signed_distance_field"));
+ }
+ _variation_selected();
+ _variations_validate();
+ } else if (p_edited_property == "msdf_pixel_range") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_msdf_pixel_range(import_settings_data->get("msdf_pixel_range"));
+ }
+ } else if (p_edited_property == "msdf_size") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_msdf_size(import_settings_data->get("msdf_size"));
+ }
+ } else if (p_edited_property == "force_autohinter") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_force_autohinter(import_settings_data->get("force_autohinter"));
+ }
+ } else if (p_edited_property == "hinting") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int());
+ }
+ } else if (p_edited_property == "oversampling") {
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling"));
+ }
+ }
+ font_preview_label->add_theme_font_override("font", font_preview);
+ font_preview_label->update();
+}
+
+/*************************************************************************/
+/* Page 2 callbacks: Configurations */
+/*************************************************************************/
+
+void DynamicFontImportSettings::_variation_add() {
+ TreeItem *vars_item = vars_list->create_item(vars_list_root);
+ ERR_FAIL_NULL(vars_item);
+
+ vars_item->set_text(0, TTR("New configuration"));
+ vars_item->set_editable(0, true);
+ vars_item->add_button(1, vars_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove Variation"));
+ vars_item->set_button_color(1, 0, Color(1, 1, 1, 0.75));
+
+ Ref<DynamicFontImportSettingsData> import_variation_data;
+ import_variation_data.instantiate();
+ import_variation_data->owner = this;
+ ERR_FAIL_NULL(import_variation_data);
+
+ for (List<ResourceImporter::ImportOption>::Element *E = options_variations.front(); E; E = E->next()) {
+ import_variation_data->defaults[E->get().option.name] = E->get().default_value;
+ }
+
+ import_variation_data->options = options_variations;
+ inspector_vars->edit(import_variation_data.ptr());
+ import_variation_data->notify_property_list_changed();
+
+ vars_item->set_metadata(0, import_variation_data);
+
+ _variations_validate();
+}
+
+void DynamicFontImportSettings::_variation_selected() {
+ TreeItem *vars_item = vars_list->get_selected();
+ if (vars_item) {
+ Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0);
+ ERR_FAIL_NULL(import_variation_data);
+
+ inspector_vars->edit(import_variation_data.ptr());
+ import_variation_data->notify_property_list_changed();
+ }
+}
+
+void DynamicFontImportSettings::_variation_remove(Object *p_item, int p_column, int p_id) {
+ TreeItem *vars_item = (TreeItem *)p_item;
+ ERR_FAIL_NULL(vars_item);
+
+ inspector_vars->edit(nullptr);
+
+ vars_list_root->remove_child(vars_item);
+ memdelete(vars_item);
+
+ if (vars_list_root->get_first_child()) {
+ Ref<DynamicFontImportSettingsData> import_variation_data = vars_list_root->get_first_child()->get_metadata(0);
+ inspector_vars->edit(import_variation_data.ptr());
+ import_variation_data->notify_property_list_changed();
+ }
+
+ _variations_validate();
+}
+
+void DynamicFontImportSettings::_variation_changed(const String &p_edited_property) {
+ _variations_validate();
+}
+
+void DynamicFontImportSettings::_variations_validate() {
+ String warn;
+ if (!vars_list_root->get_first_child()) {
+ warn = TTR("Warinig: There are no configurations specified, no glyphs will be pre-rendered.");
+ }
+ for (TreeItem *vars_item_a = vars_list_root->get_first_child(); vars_item_a; vars_item_a = vars_item_a->get_next()) {
+ Ref<DynamicFontImportSettingsData> import_variation_data_a = vars_item_a->get_metadata(0);
+ ERR_FAIL_NULL(import_variation_data_a);
+
+ for (TreeItem *vars_item_b = vars_list_root->get_first_child(); vars_item_b; vars_item_b = vars_item_b->get_next()) {
+ if (vars_item_b != vars_item_a) {
+ bool match = true;
+ for (Map<StringName, Variant>::Element *E = import_variation_data_a->settings.front(); E; E = E->next()) {
+ Ref<DynamicFontImportSettingsData> import_variation_data_b = vars_item_b->get_metadata(0);
+ ERR_FAIL_NULL(import_variation_data_b);
+ match = match && (import_variation_data_b->settings[E->key()] == E->get());
+ }
+ if (match) {
+ warn = TTR("Warinig: Multiple configurations have identical settings. Duplicates will be ignored.");
+ break;
+ }
+ }
+ }
+ }
+ if (warn.is_empty()) {
+ label_warn->set_text("");
+ label_warn->hide();
+ } else {
+ label_warn->set_text(warn);
+ label_warn->show();
+ }
+}
+
+/*************************************************************************/
+/* Page 3 callbacks: Text to select glyphs */
+/*************************************************************************/
+
+void DynamicFontImportSettings::_change_text_opts() {
+ Vector<String> ftr = ftr_edit->get_text().split(",");
+ for (int i = 0; i < ftr.size(); i++) {
+ Vector<String> tokens = ftr[i].split("=");
+ if (tokens.size() == 2) {
+ text_edit->set_opentype_feature(tokens[0], tokens[1].to_int());
+ } else if (tokens.size() == 1) {
+ text_edit->set_opentype_feature(tokens[0], 1);
+ }
+ }
+ text_edit->set_language(lang_edit->get_text());
+}
+
+void DynamicFontImportSettings::_glyph_clear() {
+ selected_glyphs.clear();
+ label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size()));
+ _range_selected();
+}
+
+void DynamicFontImportSettings::_glyph_text_selected() {
+ Dictionary ftrs;
+ Vector<String> ftr = ftr_edit->get_text().split(",");
+ for (int i = 0; i < ftr.size(); i++) {
+ Vector<String> tokens = ftr[i].split("=");
+ if (tokens.size() == 2) {
+ ftrs[tokens[0]] = tokens[1].to_int();
+ } else if (tokens.size() == 1) {
+ ftrs[tokens[0]] = 1;
+ }
+ }
+
+ RID text_rid = TS->create_shaped_text();
+ if (text_rid.is_valid()) {
+ TS->shaped_text_add_string(text_rid, text_edit->get_text(), font_main->get_rids(), 16, ftrs, text_edit->get_language());
+ TS->shaped_text_shape(text_rid);
+ const Vector<TextServer::Glyph> &gl = TS->shaped_text_get_glyphs(text_rid);
+
+ for (int i = 0; i < gl.size(); i++) {
+ if (gl[i].font_rid.is_valid() && gl[i].index != 0) {
+ selected_glyphs.insert(gl[i].index);
+ }
+ }
+ TS->free(text_rid);
+ label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size()));
+ }
+ _range_selected();
+}
+
+/*************************************************************************/
+/* Page 4 callbacks: Character map */
+/*************************************************************************/
+
+void DynamicFontImportSettings::_glyph_selected() {
+ TreeItem *item = glyph_table->get_selected();
+ ERR_FAIL_NULL(item);
+
+ Color scol = glyph_table->get_theme_color("box_selection_fill_color", "Editor");
+ Color fcol = glyph_table->get_theme_color("font_selected_color", "Editor");
+ scol.a = 1.f;
+
+ int32_t c = item->get_metadata(glyph_table->get_selected_column());
+ if (font_main->has_char(c)) {
+ if (_char_update(c)) {
+ item->set_custom_color(glyph_table->get_selected_column(), fcol);
+ item->set_custom_bg_color(glyph_table->get_selected_column(), scol);
+ } else {
+ item->clear_custom_color(glyph_table->get_selected_column());
+ item->clear_custom_bg_color(glyph_table->get_selected_column());
+ }
+ }
+ label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size()));
+}
+
+void DynamicFontImportSettings::_range_edited() {
+ TreeItem *item = glyph_tree->get_selected();
+ ERR_FAIL_NULL(item);
+ Vector2i range = item->get_metadata(0);
+ _range_update(range.x, range.y);
+}
+
+void DynamicFontImportSettings::_range_selected() {
+ TreeItem *item = glyph_tree->get_selected();
+ if (item) {
+ Vector2i range = item->get_metadata(0);
+ _edit_range(range.x, range.y);
+ }
+}
+
+void DynamicFontImportSettings::_edit_range(int32_t p_start, int32_t p_end) {
+ glyph_table->clear();
+
+ TreeItem *root = glyph_table->create_item();
+ ERR_FAIL_NULL(root);
+
+ Color scol = glyph_table->get_theme_color("box_selection_fill_color", "Editor");
+ Color fcol = glyph_table->get_theme_color("font_selected_color", "Editor");
+ scol.a = 1.f;
+
+ TreeItem *item = nullptr;
+ int col = 0;
+
+ for (int32_t c = p_start; c <= p_end; c++) {
+ if (col == 0) {
+ item = glyph_table->create_item(root);
+ ERR_FAIL_NULL(item);
+ item->set_text(0, _pad_zeros(String::num_int64(c, 16)));
+ item->set_text_align(0, TreeItem::ALIGN_LEFT);
+ item->set_selectable(0, false);
+ item->set_custom_bg_color(0, glyph_table->get_theme_color("dark_color_3", "Editor"));
+ }
+ if (font_main->has_char(c)) {
+ item->set_text(col + 1, String::chr(c));
+ item->set_custom_color(col + 1, Color(1, 1, 1));
+ if (selected_chars.has(c) || (font_main->get_data(0).is_valid() && selected_glyphs.has(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, c)))) {
+ item->set_custom_color(col + 1, fcol);
+ item->set_custom_bg_color(col + 1, scol);
+ } else {
+ item->clear_custom_color(col + 1);
+ item->clear_custom_bg_color(col + 1);
+ }
+ } else {
+ item->set_custom_bg_color(col + 1, glyph_table->get_theme_color("dark_color_2", "Editor"));
+ }
+ item->set_metadata(col + 1, c);
+ item->set_text_align(col + 1, TreeItem::ALIGN_CENTER);
+ item->set_selectable(col + 1, true);
+ item->set_custom_font(col + 1, font_main);
+ item->set_custom_font_size(col + 1, get_theme_font_size("font_size") * 2);
+
+ col++;
+ if (col == 16) {
+ col = 0;
+ }
+ }
+ label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size()));
+}
+
+bool DynamicFontImportSettings::_char_update(int32_t p_char) {
+ if (selected_chars.has(p_char)) {
+ selected_chars.erase(p_char);
+ return false;
+ } else if (font_main->get_data(0).is_valid() && selected_glyphs.has(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, p_char))) {
+ selected_glyphs.erase(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, p_char));
+ return false;
+ } else {
+ selected_chars.insert(p_char);
+ return true;
+ }
+ label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size()));
+}
+
+void DynamicFontImportSettings::_range_update(int32_t p_start, int32_t p_end) {
+ bool all_selected = true;
+ for (int32_t i = p_start; i <= p_end; i++) {
+ if (font_main->has_char(i)) {
+ if (font_main->get_data(0).is_valid()) {
+ all_selected = all_selected && (selected_chars.has(i) || (font_main->get_data(0).is_valid() && selected_glyphs.has(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, i))));
+ } else {
+ all_selected = all_selected && selected_chars.has(i);
+ }
+ }
+ }
+ for (int32_t i = p_start; i <= p_end; i++) {
+ if (font_main->has_char(i)) {
+ if (!all_selected) {
+ selected_chars.insert(i);
+ } else {
+ selected_chars.erase(i);
+ if (font_main->get_data(0).is_valid()) {
+ selected_glyphs.erase(font_main->get_data(0)->get_glyph_index(get_theme_font_size("font_size") * 2, i));
+ }
+ }
+ }
+ }
+ _edit_range(p_start, p_end);
+}
+
+/*************************************************************************/
+/* Page 5 callbacks: CMetadata override */
+/*************************************************************************/
+
+void DynamicFontImportSettings::_lang_add() {
+ menu_langs->set_position(lang_list->get_screen_transform().xform(lang_list->get_local_mouse_position()));
+ menu_langs->set_size(Vector2(1, 1));
+ menu_langs->popup();
+}
+
+void DynamicFontImportSettings::_lang_add_item(int p_option) {
+ TreeItem *lang_item = lang_list->create_item(lang_list_root);
+ ERR_FAIL_NULL(lang_item);
+
+ lang_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ lang_item->set_editable(0, true);
+ lang_item->set_checked(0, false);
+ lang_item->set_text(1, langs[p_option].code);
+ lang_item->set_editable(1, true);
+ lang_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
+ lang_item->set_button_color(2, 0, Color(1, 1, 1, 0.75));
+}
+
+void DynamicFontImportSettings::_lang_remove(Object *p_item, int p_column, int p_id) {
+ TreeItem *lang_item = (TreeItem *)p_item;
+ ERR_FAIL_NULL(lang_item);
+
+ lang_list_root->remove_child(lang_item);
+ memdelete(lang_item);
+}
+
+void DynamicFontImportSettings::_script_add() {
+ menu_scripts->set_position(script_list->get_screen_transform().xform(script_list->get_local_mouse_position()));
+ menu_scripts->set_size(Vector2(1, 1));
+ menu_scripts->popup();
+}
+
+void DynamicFontImportSettings::_script_add_item(int p_option) {
+ TreeItem *script_item = script_list->create_item(script_list_root);
+ ERR_FAIL_NULL(script_item);
+
+ script_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ script_item->set_editable(0, true);
+ script_item->set_checked(0, false);
+ script_item->set_text(1, scripts[p_option].code);
+ script_item->set_editable(1, true);
+ script_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
+ script_item->set_button_color(2, 0, Color(1, 1, 1, 0.75));
+}
+
+void DynamicFontImportSettings::_script_remove(Object *p_item, int p_column, int p_id) {
+ TreeItem *script_item = (TreeItem *)p_item;
+ ERR_FAIL_NULL(script_item);
+
+ script_list_root->remove_child(script_item);
+ memdelete(script_item);
+}
+
+/*************************************************************************/
+/* Common */
+/*************************************************************************/
+
+DynamicFontImportSettings *DynamicFontImportSettings::singleton = nullptr;
+
+String DynamicFontImportSettings::_pad_zeros(const String &p_hex) const {
+ int len = CLAMP(5 - p_hex.length(), 0, 5);
+ return String("0").repeat(len) + p_hex;
+}
+
+void DynamicFontImportSettings::_notification(int p_what) {
+ if (p_what == NOTIFICATION_READY) {
+ connect("confirmed", callable_mp(this, &DynamicFontImportSettings::_re_import));
+ } else if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ add_lang->set_icon(add_var->get_theme_icon("Add", "EditorIcons"));
+ add_script->set_icon(add_var->get_theme_icon("Add", "EditorIcons"));
+ add_var->set_icon(add_var->get_theme_icon("Add", "EditorIcons"));
+ }
+}
+
+void DynamicFontImportSettings::_re_import() {
+ Map<StringName, Variant> main_settings;
+
+ main_settings["antialiased"] = import_settings_data->get("antialiased");
+ main_settings["multichannel_signed_distance_field"] = import_settings_data->get("multichannel_signed_distance_field");
+ main_settings["msdf_pixel_range"] = import_settings_data->get("msdf_pixel_range");
+ main_settings["msdf_size"] = import_settings_data->get("msdf_size");
+ main_settings["force_autohinter"] = import_settings_data->get("force_autohinter");
+ main_settings["hinting"] = import_settings_data->get("hinting");
+ main_settings["oversampling"] = import_settings_data->get("oversampling");
+ main_settings["compress"] = import_settings_data->get("compress");
+
+ Vector<String> variations;
+ for (TreeItem *vars_item = vars_list_root->get_first_child(); vars_item; vars_item = vars_item->get_next()) {
+ String variation;
+ Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0);
+ ERR_FAIL_NULL(import_variation_data);
+
+ String name = vars_item->get_text(0);
+ variation += ("name=" + name);
+ for (Map<StringName, Variant>::Element *E = import_variation_data->settings.front(); E; E = E->next()) {
+ if (!variation.is_empty()) {
+ variation += ",";
+ }
+ variation += (String(E->key()) + "=" + String(E->get()));
+ }
+ variations.push_back(variation);
+ }
+ main_settings["preload/configurations"] = variations;
+
+ Vector<String> langs_enabled;
+ Vector<String> langs_disabled;
+ for (TreeItem *lang_item = lang_list_root->get_first_child(); lang_item; lang_item = lang_item->get_next()) {
+ bool selected = lang_item->is_checked(0);
+ String name = lang_item->get_text(1);
+ if (selected) {
+ langs_enabled.push_back(name);
+ } else {
+ langs_disabled.push_back(name);
+ }
+ }
+ main_settings["support_overrides/language_enabled"] = langs_enabled;
+ main_settings["support_overrides/language_disabled"] = langs_disabled;
+
+ Vector<String> scripts_enabled;
+ Vector<String> scripts_disabled;
+ for (TreeItem *script_item = script_list_root->get_first_child(); script_item; script_item = script_item->get_next()) {
+ bool selected = script_item->is_checked(0);
+ String name = script_item->get_text(1);
+ if (selected) {
+ scripts_enabled.push_back(name);
+ } else {
+ scripts_disabled.push_back(name);
+ }
+ }
+ main_settings["support_overrides/script_enabled"] = scripts_enabled;
+ main_settings["support_overrides/script_disabled"] = scripts_disabled;
+
+ if (!selected_chars.is_empty()) {
+ Vector<String> ranges;
+ char32_t start = selected_chars.front()->get();
+ for (Set<char32_t>::Element *E = selected_chars.front()->next(); E; E = E->next()) {
+ if (E->prev() && ((E->prev()->get() + 1) != E->get())) {
+ ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(E->prev()->get(), 16));
+ start = E->get();
+ }
+ }
+ ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(selected_chars.back()->get(), 16));
+ main_settings["preload/char_ranges"] = ranges;
+ }
+
+ if (!selected_glyphs.is_empty()) {
+ Vector<String> ranges;
+ int32_t start = selected_glyphs.front()->get();
+ for (Set<int32_t>::Element *E = selected_glyphs.front()->next(); E; E = E->next()) {
+ if (E->prev() && ((E->prev()->get() + 1) != E->get())) {
+ ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(E->prev()->get(), 16));
+ start = E->get();
+ }
+ }
+ ranges.push_back(String("0x") + String::num_int64(start, 16) + String("-0x") + String::num_int64(selected_glyphs.back()->get(), 16));
+ main_settings["preload/glyph_ranges"] = ranges;
+ }
+
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ print_line("Import settings:");
+ for (Map<StringName, Variant>::Element *E = main_settings.front(); E; E = E->next()) {
+ print_line(String(" ") + String(E->key()).utf8().get_data() + " == " + String(E->get()).utf8().get_data());
+ }
+ }
+
+ EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, "font_data_dynamic", main_settings);
+}
+
+void DynamicFontImportSettings::open_settings(const String &p_path) {
+ // Load base font data.
+ Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
+
+ // Load font for preview.
+ Ref<FontData> dfont_prev;
+ dfont_prev.instantiate();
+ dfont_prev->set_data(data);
+
+ font_preview.instantiate();
+ font_preview->add_data(dfont_prev);
+
+ String sample;
+ static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";
+ for (int i = 0; i < sample_base.length(); i++) {
+ if (dfont_prev->has_char(sample_base[i])) {
+ sample += sample_base[i];
+ }
+ }
+ if (sample.is_empty()) {
+ sample = dfont_prev->get_supported_chars().substr(0, 6);
+ }
+ font_preview_label->set_text(sample);
+
+ // Load second copy of font with MSDF disabled for the glyph table and metadata extraction.
+ Ref<FontData> dfont_main;
+ dfont_main.instantiate();
+ dfont_main->set_data(data);
+ dfont_main->set_multichannel_signed_distance_field(false);
+
+ font_main.instantiate();
+ font_main->add_data(dfont_main);
+ text_edit->add_theme_font_override("font", font_main);
+
+ base_path = p_path;
+
+ inspector_vars->edit(nullptr);
+ inspector_general->edit(nullptr);
+
+ int gww = get_theme_font("font")->get_string_size("00000", get_theme_font_size("font_size")).x + 50;
+ glyph_table->set_column_custom_minimum_width(0, gww);
+
+ glyph_table->clear();
+ vars_list->clear();
+ lang_list->clear();
+ script_list->clear();
+
+ selected_chars.clear();
+ selected_glyphs.clear();
+ text_edit->set_text(String());
+
+ vars_list_root = vars_list->create_item();
+ lang_list_root = lang_list->create_item();
+ script_list_root = script_list->create_item();
+
+ options_variations.clear();
+ Dictionary var_list = dfont_main->get_supported_variation_list();
+ for (int i = 0; i < var_list.size(); i++) {
+ int32_t tag = var_list.get_key_at_index(i);
+ Vector3i value = var_list.get_value_at_index(i);
+ options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, TS->tag_to_name(tag), PROPERTY_HINT_RANGE, itos(value.x) + "," + itos(value.y) + ",1"), value.z));
+ }
+ options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "size", PROPERTY_HINT_RANGE, "0,127,1"), 16));
+ options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "outline_size", PROPERTY_HINT_RANGE, "0,127,1"), 0));
+ options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "extra_spacing_glyph"), 0));
+ options_variations.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "extra_spacing_space"), 0));
+
+ import_settings_data->defaults.clear();
+ for (List<ResourceImporter::ImportOption>::Element *E = options_general.front(); E; E = E->next()) {
+ import_settings_data->defaults[E->get().option.name] = E->get().default_value;
+ }
+
+ Ref<ConfigFile> config;
+ config.instantiate();
+ ERR_FAIL_NULL(config);
+
+ Error err = config->load(p_path + ".import");
+ print_verbose("Loading import settings:");
+ if (err == OK) {
+ List<String> keys;
+ config->get_section_keys("params", &keys);
+ for (List<String>::Element *E = keys.front(); E; E = E->next()) {
+ String key = E->get();
+ print_verbose(String(" ") + key + " == " + String(config->get_value("params", key)));
+ if (key == "preload/char_ranges") {
+ Vector<String> ranges = config->get_value("params", key);
+ for (int i = 0; i < ranges.size(); i++) {
+ int32_t start, end;
+ Vector<String> tokens = ranges[i].split("-");
+ if (tokens.size() == 2) {
+ if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start) || !ResourceImporterDynamicFont::_decode_range(tokens[1], end)) {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ } else if (tokens.size() == 1) {
+ if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start)) {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ end = start;
+ } else {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ for (int32_t j = start; j <= end; j++) {
+ selected_chars.insert(j);
+ }
+ }
+ } else if (key == "preload/glyph_ranges") {
+ Vector<String> ranges = config->get_value("params", key);
+ for (int i = 0; i < ranges.size(); i++) {
+ int32_t start, end;
+ Vector<String> tokens = ranges[i].split("-");
+ if (tokens.size() == 2) {
+ if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start) || !ResourceImporterDynamicFont::_decode_range(tokens[1], end)) {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ } else if (tokens.size() == 1) {
+ if (!ResourceImporterDynamicFont::_decode_range(tokens[0], start)) {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ end = start;
+ } else {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ for (int32_t j = start; j <= end; j++) {
+ selected_glyphs.insert(j);
+ }
+ }
+ } else if (key == "preload/configurations") {
+ Vector<String> variations = config->get_value("params", key);
+ for (int i = 0; i < variations.size(); i++) {
+ TreeItem *vars_item = vars_list->create_item(vars_list_root);
+ ERR_FAIL_NULL(vars_item);
+
+ vars_item->set_text(0, TTR("Configuration") + " " + itos(i));
+ vars_item->set_editable(0, true);
+ vars_item->add_button(1, vars_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove Variation"));
+ vars_item->set_button_color(1, 0, Color(1, 1, 1, 0.75));
+
+ Ref<DynamicFontImportSettingsData> import_variation_data_custom;
+ import_variation_data_custom.instantiate();
+ import_variation_data_custom->owner = this;
+ ERR_FAIL_NULL(import_variation_data_custom);
+
+ for (List<ResourceImporter::ImportOption>::Element *F = options_variations.front(); F; F = F->next()) {
+ import_variation_data_custom->defaults[F->get().option.name] = F->get().default_value;
+ }
+
+ import_variation_data_custom->options = options_variations;
+
+ vars_item->set_metadata(0, import_variation_data_custom);
+ Vector<String> variation_tags = variations[i].split(",");
+ for (int j = 0; j < variation_tags.size(); j++) {
+ Vector<String> tokens = variation_tags[j].split("=");
+ if (tokens[0] == "name") {
+ vars_item->set_text(0, tokens[1]);
+ } else if (tokens[0] == "size" || tokens[0] == "outline_size" || tokens[0] == "extra_spacing_space" || tokens[0] == "extra_spacing_glyph") {
+ import_variation_data_custom->set(tokens[0], tokens[1].to_int());
+ } else {
+ import_variation_data_custom->set(tokens[0], tokens[1].to_float());
+ }
+ }
+ }
+ } else if (key == "support_overrides/language_enabled") {
+ PackedStringArray _langs = config->get_value("params", key);
+ for (int i = 0; i < _langs.size(); i++) {
+ TreeItem *lang_item = lang_list->create_item(lang_list_root);
+ ERR_FAIL_NULL(lang_item);
+
+ lang_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ lang_item->set_editable(0, true);
+ lang_item->set_checked(0, true);
+ lang_item->set_text(1, _langs[i]);
+ lang_item->set_editable(1, true);
+ lang_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
+ }
+ } else if (key == "support_overrides/language_disabled") {
+ PackedStringArray _langs = config->get_value("params", key);
+ for (int i = 0; i < _langs.size(); i++) {
+ TreeItem *lang_item = lang_list->create_item(lang_list_root);
+ ERR_FAIL_NULL(lang_item);
+
+ lang_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ lang_item->set_editable(0, true);
+ lang_item->set_checked(0, false);
+ lang_item->set_text(1, _langs[i]);
+ lang_item->set_editable(1, true);
+ lang_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
+ }
+ } else if (key == "support_overrides/script_enabled") {
+ PackedStringArray _scripts = config->get_value("params", key);
+ for (int i = 0; i < _scripts.size(); i++) {
+ TreeItem *script_item = script_list->create_item(script_list_root);
+ ERR_FAIL_NULL(script_item);
+
+ script_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ script_item->set_editable(0, true);
+ script_item->set_checked(0, true);
+ script_item->set_text(1, _scripts[i]);
+ script_item->set_editable(1, true);
+ script_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
+ }
+ } else if (key == "support_overrides/script_disabled") {
+ PackedStringArray _scripts = config->get_value("params", key);
+ for (int i = 0; i < _scripts.size(); i++) {
+ TreeItem *script_item = script_list->create_item(script_list_root);
+ ERR_FAIL_NULL(script_item);
+
+ script_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ script_item->set_editable(0, true);
+ script_item->set_checked(0, false);
+ script_item->set_text(1, _scripts[i]);
+ script_item->set_editable(1, true);
+ script_item->add_button(2, lang_list->get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE_VAR, false, TTR("Remove"));
+ }
+ } else {
+ Variant value = config->get_value("params", key);
+ import_settings_data->defaults[key] = value;
+ }
+ }
+ }
+ label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(selected_glyphs.size()));
+
+ import_settings_data->options = options_general;
+ inspector_general->edit(import_settings_data.ptr());
+ import_settings_data->notify_property_list_changed();
+
+ if (font_preview->get_data_count() > 0) {
+ font_preview->get_data(0)->set_antialiased(import_settings_data->get("antialiased"));
+ font_preview->get_data(0)->set_multichannel_signed_distance_field(import_settings_data->get("multichannel_signed_distance_field"));
+ font_preview->get_data(0)->set_msdf_pixel_range(import_settings_data->get("msdf_pixel_range"));
+ font_preview->get_data(0)->set_msdf_size(import_settings_data->get("msdf_size"));
+ font_preview->get_data(0)->set_force_autohinter(import_settings_data->get("force_autohinter"));
+ font_preview->get_data(0)->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int());
+ font_preview->get_data(0)->set_oversampling(import_settings_data->get("oversampling"));
+ }
+ font_preview_label->add_theme_font_override("font", font_preview);
+ font_preview_label->update();
+
+ _variations_validate();
+
+ popup_centered_ratio();
+
+ set_title(vformat(TTR("Advanced Import Settings for '%s'"), base_path.get_file()));
+}
+
+DynamicFontImportSettings *DynamicFontImportSettings::get_singleton() {
+ return singleton;
+}
+
+DynamicFontImportSettings::DynamicFontImportSettings() {
+ singleton = this;
+
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "antialiased"), true));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), 8));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0));
+ options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "compress", PROPERTY_HINT_NONE, ""), false));
+
+ // Popup menus
+
+ menu_langs = memnew(PopupMenu);
+ menu_langs->set_name("Language");
+ for (int i = 0; langs[i].name != String(); i++) {
+ if (langs[i].name == "-") {
+ menu_langs->add_separator();
+ } else {
+ menu_langs->add_item(langs[i].name + " (" + langs[i].code + ")", i);
+ }
+ }
+ add_child(menu_langs);
+ menu_langs->connect("id_pressed", callable_mp(this, &DynamicFontImportSettings::_lang_add_item));
+
+ menu_scripts = memnew(PopupMenu);
+ menu_scripts->set_name("Script");
+ for (int i = 0; scripts[i].name != String(); i++) {
+ if (scripts[i].name == "-") {
+ menu_scripts->add_separator();
+ } else {
+ menu_scripts->add_item(scripts[i].name + " (" + scripts[i].code + ")", i);
+ }
+ }
+ add_child(menu_scripts);
+ menu_scripts->connect("id_pressed", callable_mp(this, &DynamicFontImportSettings::_script_add_item));
+
+ Color warn_color = (EditorNode::get_singleton()) ? EditorNode::get_singleton()->get_gui_base()->get_theme_color("warning_color", "Editor") : Color(1, 1, 0);
+
+ // Root layout
+
+ VBoxContainer *root_vb = memnew(VBoxContainer);
+ add_child(root_vb);
+
+ main_pages = memnew(TabContainer);
+ main_pages->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ main_pages->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ root_vb->add_child(main_pages);
+
+ label_warn = memnew(Label);
+ label_warn->set_align(Label::ALIGN_CENTER);
+ label_warn->set_text("");
+ root_vb->add_child(label_warn);
+ label_warn->add_theme_color_override("font_color", warn_color);
+ label_warn->hide();
+
+ // Page 1 layout: Rendering Options
+
+ VBoxContainer *page1_vb = memnew(VBoxContainer);
+ page1_vb->set_meta("_tab_name", TTR("Rendering options"));
+ main_pages->add_child(page1_vb);
+
+ page1_description = memnew(Label);
+ page1_description->set_text(TTR("Select font rendering options:"));
+ page1_description->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page1_vb->add_child(page1_description);
+
+ HSplitContainer *page1_hb = memnew(HSplitContainer);
+ page1_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ page1_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page1_vb->add_child(page1_hb);
+
+ font_preview_label = memnew(Label);
+ font_preview_label->add_theme_font_size_override("font_size", 200 * EDSCALE);
+ font_preview_label->set_align(Label::ALIGN_CENTER);
+ font_preview_label->set_valign(Label::VALIGN_CENTER);
+ font_preview_label->set_autowrap_mode(Label::AUTOWRAP_ARBITRARY);
+ font_preview_label->set_clip_text(true);
+ font_preview_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ font_preview_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page1_hb->add_child(font_preview_label);
+
+ inspector_general = memnew(EditorInspector);
+ inspector_general->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ inspector_general->set_custom_minimum_size(Size2(300 * EDSCALE, 250 * EDSCALE));
+ inspector_general->connect("property_edited", callable_mp(this, &DynamicFontImportSettings::_main_prop_changed));
+ page1_hb->add_child(inspector_general);
+
+ // Page 2 layout: Configurations
+ VBoxContainer *page2_vb = memnew(VBoxContainer);
+ page2_vb->set_meta("_tab_name", TTR("Sizes and variations"));
+ main_pages->add_child(page2_vb);
+
+ page2_description = memnew(Label);
+ page2_description->set_text(TTR("Add font size, variation coordinates, and extra spacing combinations to pre-render:"));
+ page2_description->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page2_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
+ page2_vb->add_child(page2_description);
+
+ HSplitContainer *page2_hb = memnew(HSplitContainer);
+ page2_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ page2_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page2_vb->add_child(page2_hb);
+
+ VBoxContainer *page2_side_vb = memnew(VBoxContainer);
+ page2_hb->add_child(page2_side_vb);
+
+ HBoxContainer *page2_hb_vars = memnew(HBoxContainer);
+ page2_side_vb->add_child(page2_hb_vars);
+
+ label_vars = memnew(Label);
+ page2_hb_vars->add_child(label_vars);
+ label_vars->set_align(Label::ALIGN_CENTER);
+ label_vars->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ label_vars->set_text(TTR("Configuration:"));
+
+ add_var = memnew(Button);
+ page2_hb_vars->add_child(add_var);
+ add_var->set_tooltip(TTR("Add configuration"));
+ add_var->set_icon(add_var->get_theme_icon("Add", "EditorIcons"));
+ add_var->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_variation_add));
+
+ vars_list = memnew(Tree);
+ page2_side_vb->add_child(vars_list);
+ vars_list->set_custom_minimum_size(Size2(300 * EDSCALE, 0));
+ vars_list->set_hide_root(true);
+ vars_list->set_columns(2);
+ vars_list->set_column_expand(0, true);
+ vars_list->set_column_custom_minimum_width(0, 80 * EDSCALE);
+ vars_list->set_column_expand(1, false);
+ vars_list->set_column_custom_minimum_width(1, 50 * EDSCALE);
+ vars_list->connect("item_selected", callable_mp(this, &DynamicFontImportSettings::_variation_selected));
+ vars_list->connect("button_pressed", callable_mp(this, &DynamicFontImportSettings::_variation_remove));
+ vars_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ inspector_vars = memnew(EditorInspector);
+ inspector_vars->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ inspector_vars->connect("property_edited", callable_mp(this, &DynamicFontImportSettings::_variation_changed));
+ page2_hb->add_child(inspector_vars);
+
+ // Page 3 layout: Text to select glyphs
+ VBoxContainer *page3_vb = memnew(VBoxContainer);
+ page3_vb->set_meta("_tab_name", TTR("Glyphs from the text"));
+ main_pages->add_child(page3_vb);
+
+ page3_description = memnew(Label);
+ page3_description->set_text(TTR("Enter a text to shape and add all required glyphs to pre-render list:"));
+ page3_description->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page3_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
+ page3_vb->add_child(page3_description);
+
+ HBoxContainer *ot_hb = memnew(HBoxContainer);
+ page3_vb->add_child(ot_hb);
+ ot_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+
+ Label *label_ed_ftr = memnew(Label);
+ ot_hb->add_child(label_ed_ftr);
+ label_ed_ftr->set_text(TTR("OpenType features:"));
+
+ ftr_edit = memnew(LineEdit);
+ ot_hb->add_child(ftr_edit);
+ ftr_edit->connect("text_changed", callable_mp(this, &DynamicFontImportSettings::_change_text_opts));
+ ftr_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+
+ Label *label_ed_lang = memnew(Label);
+ ot_hb->add_child(label_ed_lang);
+ label_ed_lang->set_text(TTR("Text language:"));
+
+ lang_edit = memnew(LineEdit);
+ ot_hb->add_child(lang_edit);
+ lang_edit->connect("text_changed", callable_mp(this, &DynamicFontImportSettings::_change_text_opts));
+ lang_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+
+ text_edit = memnew(TextEdit);
+ page3_vb->add_child(text_edit);
+ text_edit->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ text_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+
+ HBoxContainer *text_hb = memnew(HBoxContainer);
+ page3_vb->add_child(text_hb);
+ text_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+
+ label_glyphs = memnew(Label);
+ text_hb->add_child(label_glyphs);
+ label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(0));
+ label_glyphs->set_custom_minimum_size(Size2(50 * EDSCALE, 0));
+
+ Button *btn_fill = memnew(Button);
+ text_hb->add_child(btn_fill);
+ btn_fill->set_text(TTR("Shape text and add glyphs"));
+ btn_fill->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_glyph_text_selected));
+
+ Button *btn_clear = memnew(Button);
+ text_hb->add_child(btn_clear);
+ btn_clear->set_text(TTR("Clear glyph list"));
+ btn_clear->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_glyph_clear));
+
+ // Page 4 layout: Character map
+ VBoxContainer *page4_vb = memnew(VBoxContainer);
+ page4_vb->set_meta("_tab_name", TTR("Glyphs from the character map"));
+ main_pages->add_child(page4_vb);
+
+ page4_description = memnew(Label);
+ page4_description->set_text(TTR("Add or remove additional glyphs from the character map to pre-render list:\nNote: Some stylistic alternatives and glyph variants do not have one-to-one correspondence to character, and not shown in this map, use \"Glyphs from the text\" to add these."));
+ page4_description->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page4_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
+ page4_vb->add_child(page4_description);
+
+ HSplitContainer *glyphs_split = memnew(HSplitContainer);
+ glyphs_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ glyphs_split->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page4_vb->add_child(glyphs_split);
+
+ glyph_table = memnew(Tree);
+ glyphs_split->add_child(glyph_table);
+ glyph_table->set_custom_minimum_size(Size2((30 * 16 + 100) * EDSCALE, 0));
+ glyph_table->set_columns(17);
+ glyph_table->set_column_expand(0, false);
+ glyph_table->set_hide_root(true);
+ glyph_table->set_allow_reselect(true);
+ glyph_table->set_select_mode(Tree::SELECT_SINGLE);
+ glyph_table->connect("item_activated", callable_mp(this, &DynamicFontImportSettings::_glyph_selected));
+ glyph_table->set_column_titles_visible(true);
+ for (int i = 0; i < 16; i++) {
+ glyph_table->set_column_title(i + 1, String::num_int64(i, 16));
+ }
+ glyph_table->add_theme_style_override("selected", glyph_table->get_theme_stylebox("bg"));
+ glyph_table->add_theme_style_override("selected_focus", glyph_table->get_theme_stylebox("bg"));
+ glyph_table->add_theme_constant_override("hseparation", 0);
+ glyph_table->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ glyph_table->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ glyph_tree = memnew(Tree);
+ glyphs_split->add_child(glyph_tree);
+ glyph_tree->set_custom_minimum_size(Size2(300 * EDSCALE, 0));
+ glyph_tree->set_columns(3);
+ glyph_tree->set_hide_root(true);
+ glyph_tree->set_column_expand(0, false);
+ glyph_tree->set_column_expand(1, true);
+ glyph_tree->set_column_custom_minimum_width(0, 120 * EDSCALE);
+ glyph_tree->connect("item_activated", callable_mp(this, &DynamicFontImportSettings::_range_edited));
+ glyph_tree->connect("item_selected", callable_mp(this, &DynamicFontImportSettings::_range_selected));
+ glyph_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ glyph_root = glyph_tree->create_item();
+ for (int i = 0; unicode_ranges[i].name != String(); i++) {
+ _add_glyph_range_item(unicode_ranges[i].start, unicode_ranges[i].end, unicode_ranges[i].name);
+ }
+
+ // Page 4 layout: Metadata override
+ VBoxContainer *page5_vb = memnew(VBoxContainer);
+ page5_vb->set_meta("_tab_name", TTR("Metadata override"));
+ main_pages->add_child(page5_vb);
+
+ page5_description = memnew(Label);
+ page5_description->set_text(TTR("Add or remove language and script support overrides, to control fallback font selection order:"));
+ page5_description->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ page5_description->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
+ page5_vb->add_child(page5_description);
+
+ HBoxContainer *hb_lang = memnew(HBoxContainer);
+ page5_vb->add_child(hb_lang);
+
+ label_langs = memnew(Label);
+ label_langs->set_align(Label::ALIGN_CENTER);
+ label_langs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ label_langs->set_text(TTR("Language support overrides"));
+ hb_lang->add_child(label_langs);
+
+ add_lang = memnew(Button);
+ hb_lang->add_child(add_lang);
+ add_lang->set_tooltip(TTR("Add language override"));
+ add_lang->set_icon(add_var->get_theme_icon("Add", "EditorIcons"));
+ add_lang->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_lang_add));
+
+ lang_list = memnew(Tree);
+ page5_vb->add_child(lang_list);
+ lang_list->set_hide_root(true);
+ lang_list->set_columns(3);
+ lang_list->set_column_expand(0, false); // Check
+ lang_list->set_column_custom_minimum_width(0, 50 * EDSCALE);
+ lang_list->set_column_expand(1, true);
+ lang_list->set_column_custom_minimum_width(1, 80 * EDSCALE);
+ lang_list->set_column_expand(2, false);
+ lang_list->set_column_custom_minimum_width(2, 50 * EDSCALE);
+ lang_list->connect("button_pressed", callable_mp(this, &DynamicFontImportSettings::_lang_remove));
+ lang_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ HBoxContainer *hb_script = memnew(HBoxContainer);
+ page5_vb->add_child(hb_script);
+
+ label_script = memnew(Label);
+ label_script->set_align(Label::ALIGN_CENTER);
+ label_script->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ label_script->set_text(TTR("Script support overrides"));
+ hb_script->add_child(label_script);
+
+ add_script = memnew(Button);
+ hb_script->add_child(add_script);
+ add_script->set_tooltip(TTR("Add script override"));
+ add_script->set_icon(add_var->get_theme_icon("Add", "EditorIcons"));
+ add_script->connect("pressed", callable_mp(this, &DynamicFontImportSettings::_script_add));
+
+ script_list = memnew(Tree);
+ page5_vb->add_child(script_list);
+ script_list->set_hide_root(true);
+ script_list->set_columns(3);
+ script_list->set_column_expand(0, false);
+ script_list->set_column_custom_minimum_width(0, 50 * EDSCALE);
+ script_list->set_column_expand(1, true);
+ script_list->set_column_custom_minimum_width(1, 80 * EDSCALE);
+ script_list->set_column_expand(2, false);
+ script_list->set_column_custom_minimum_width(2, 50 * EDSCALE);
+ script_list->connect("button_pressed", callable_mp(this, &DynamicFontImportSettings::_script_remove));
+ script_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ // Common
+
+ import_settings_data.instantiate();
+ import_settings_data->owner = this;
+
+ get_ok_button()->set_text(TTR("Reimport"));
+ get_cancel_button()->set_text(TTR("Close"));
+}
diff --git a/editor/import/dynamicfont_import_settings.h b/editor/import/dynamicfont_import_settings.h
new file mode 100644
index 0000000000..05f5e8e00b
--- /dev/null
+++ b/editor/import/dynamicfont_import_settings.h
@@ -0,0 +1,167 @@
+/*************************************************************************/
+/* dynamicfont_import_settings.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 FONTDATA_IMPORT_SETTINGS_H
+#define FONTDATA_IMPORT_SETTINGS_H
+
+#include "editor/editor_file_dialog.h"
+#include "editor/editor_inspector.h"
+
+#include "editor/import/resource_importer_dynamicfont.h"
+
+#include "scene/gui/dialogs.h"
+#include "scene/gui/item_list.h"
+#include "scene/gui/option_button.h"
+#include "scene/gui/split_container.h"
+#include "scene/gui/subviewport_container.h"
+#include "scene/gui/tab_container.h"
+#include "scene/gui/text_edit.h"
+#include "scene/gui/tree.h"
+
+#include "scene/resources/font.h"
+#include "servers/text_server.h"
+
+class DynamicFontImportSettingsData;
+
+class DynamicFontImportSettings : public ConfirmationDialog {
+ GDCLASS(DynamicFontImportSettings, ConfirmationDialog)
+ friend class DynamicFontImportSettingsData;
+
+ enum ItemButton {
+ BUTTON_ADD_VAR,
+ BUTTON_REMOVE_VAR,
+ };
+
+ static DynamicFontImportSettings *singleton;
+
+ String base_path;
+
+ Ref<DynamicFontImportSettingsData> import_settings_data;
+ List<ResourceImporter::ImportOption> options_variations;
+ List<ResourceImporter::ImportOption> options_general;
+
+ // Root layout
+ Label *label_warn = nullptr;
+ TabContainer *main_pages = nullptr;
+
+ // Page 1 layout: Rendering Options
+ Label *page1_description = nullptr;
+ Label *font_preview_label = nullptr;
+ EditorInspector *inspector_general = nullptr;
+
+ void _main_prop_changed(const String &p_edited_property);
+
+ // Page 2 layout: Configurations
+ Label *page2_description = nullptr;
+ Label *label_vars = nullptr;
+ Button *add_var = nullptr;
+ Tree *vars_list = nullptr;
+ TreeItem *vars_list_root = nullptr;
+ EditorInspector *inspector_vars = nullptr;
+
+ void _variation_add();
+ void _variation_selected();
+ void _variation_remove(Object *p_item, int p_column, int p_id);
+ void _variation_changed(const String &p_edited_property);
+ void _variations_validate();
+
+ // Page 3 layout: Text to select glyphs
+ Label *page3_description = nullptr;
+ Label *label_glyphs = nullptr;
+ TextEdit *text_edit = nullptr;
+ LineEdit *ftr_edit = nullptr;
+ LineEdit *lang_edit = nullptr;
+
+ void _change_text_opts();
+ void _glyph_text_selected();
+ void _glyph_clear();
+
+ // Page 4 layout: Character map
+ Label *page4_description = nullptr;
+ Tree *glyph_table = nullptr;
+ Tree *glyph_tree = nullptr;
+ TreeItem *glyph_root = nullptr;
+
+ void _glyph_selected();
+ void _range_edited();
+ void _range_selected();
+ void _edit_range(int32_t p_start, int32_t p_end);
+ bool _char_update(int32_t p_char);
+ void _range_update(int32_t p_start, int32_t p_end);
+
+ // Page 5 layout: Metadata override
+ Label *page5_description = nullptr;
+ Button *add_lang = nullptr;
+ Button *add_script = nullptr;
+
+ PopupMenu *menu_langs = nullptr;
+ PopupMenu *menu_scripts = nullptr;
+
+ Tree *lang_list = nullptr;
+ TreeItem *lang_list_root = nullptr;
+
+ Tree *script_list = nullptr;
+ TreeItem *script_list_root = nullptr;
+ Label *label_langs = nullptr;
+ Label *label_script = nullptr;
+
+ void _lang_add();
+ void _lang_add_item(int p_option);
+ void _lang_remove(Object *p_item, int p_column, int p_id);
+
+ void _script_add();
+ void _script_add_item(int p_option);
+ void _script_remove(Object *p_item, int p_column, int p_id);
+
+ // Common
+
+ void _add_glyph_range_item(int32_t p_start, int32_t p_end, const String &p_name);
+
+ Ref<Font> font_preview;
+ Ref<Font> font_main;
+
+ Set<char32_t> selected_chars;
+ Set<int32_t> selected_glyphs;
+
+ void _re_import();
+
+ String _pad_zeros(const String &p_hex) const;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void open_settings(const String &p_path);
+ static DynamicFontImportSettings *get_singleton();
+
+ DynamicFontImportSettings();
+};
+
+#endif // FONTDATA_IMPORT_SETTINGS_H
diff --git a/editor/import/resource_importer_bmfont.cpp b/editor/import/resource_importer_bmfont.cpp
new file mode 100644
index 0000000000..2e7ef1402b
--- /dev/null
+++ b/editor/import/resource_importer_bmfont.cpp
@@ -0,0 +1,797 @@
+/*************************************************************************/
+/* resource_importer_bmfont.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#include "resource_importer_bmfont.h"
+
+#include "core/io/image_loader.h"
+#include "core/io/resource_saver.h"
+
+String ResourceImporterBMFont::get_importer_name() const {
+ return "font_data_bmfont";
+}
+
+String ResourceImporterBMFont::get_visible_name() const {
+ return "Font Data (AngelCode BMFont)";
+}
+
+void ResourceImporterBMFont::get_recognized_extensions(List<String> *p_extensions) const {
+ if (p_extensions) {
+ p_extensions->push_back("font");
+ p_extensions->push_back("fnt");
+ }
+}
+
+String ResourceImporterBMFont::get_save_extension() const {
+ return "fontdata";
+}
+
+String ResourceImporterBMFont::get_resource_type() const {
+ return "FontData";
+}
+
+bool ResourceImporterBMFont::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+ return true;
+}
+
+void ResourceImporterBMFont::get_import_options(List<ImportOption> *r_options, int p_preset) const {
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
+}
+
+void _convert_packed_8bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) {
+ int w = p_source->get_width();
+ int h = p_source->get_height();
+
+ PackedByteArray imgdata = p_source->get_data();
+ const uint8_t *r = imgdata.ptr();
+
+ PackedByteArray imgdata_r;
+ imgdata_r.resize(w * h * 2);
+ uint8_t *wr = imgdata_r.ptrw();
+
+ PackedByteArray imgdata_g;
+ imgdata_g.resize(w * h * 2);
+ uint8_t *wg = imgdata_g.ptrw();
+
+ PackedByteArray imgdata_b;
+ imgdata_b.resize(w * h * 2);
+ uint8_t *wb = imgdata_b.ptrw();
+
+ PackedByteArray imgdata_a;
+ imgdata_a.resize(w * h * 2);
+ uint8_t *wa = imgdata_a.ptrw();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ int ofs_src = (i * w + j) * 4;
+ int ofs_dst = (i * w + j) * 2;
+ wr[ofs_dst + 0] = 255;
+ wr[ofs_dst + 1] = r[ofs_src + 0];
+ wg[ofs_dst + 0] = 255;
+ wg[ofs_dst + 1] = r[ofs_src + 1];
+ wb[ofs_dst + 0] = 255;
+ wb[ofs_dst + 1] = r[ofs_src + 2];
+ wa[ofs_dst + 0] = 255;
+ wa[ofs_dst + 1] = r[ofs_src + 3];
+ }
+ }
+ Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
+ Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
+ Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
+ Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
+}
+
+void _convert_packed_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) {
+ int w = p_source->get_width();
+ int h = p_source->get_height();
+
+ PackedByteArray imgdata = p_source->get_data();
+ const uint8_t *r = imgdata.ptr();
+
+ PackedByteArray imgdata_r;
+ imgdata_r.resize(w * h * 2);
+ uint8_t *wr = imgdata_r.ptrw();
+
+ PackedByteArray imgdata_g;
+ imgdata_g.resize(w * h * 2);
+ uint8_t *wg = imgdata_g.ptrw();
+
+ PackedByteArray imgdata_b;
+ imgdata_b.resize(w * h * 2);
+ uint8_t *wb = imgdata_b.ptrw();
+
+ PackedByteArray imgdata_a;
+ imgdata_a.resize(w * h * 2);
+ uint8_t *wa = imgdata_a.ptrw();
+
+ PackedByteArray imgdata_ro;
+ imgdata_ro.resize(w * h * 2);
+ uint8_t *wro = imgdata_ro.ptrw();
+
+ PackedByteArray imgdata_go;
+ imgdata_go.resize(w * h * 2);
+ uint8_t *wgo = imgdata_go.ptrw();
+
+ PackedByteArray imgdata_bo;
+ imgdata_bo.resize(w * h * 2);
+ uint8_t *wbo = imgdata_bo.ptrw();
+
+ PackedByteArray imgdata_ao;
+ imgdata_ao.resize(w * h * 2);
+ uint8_t *wao = imgdata_ao.ptrw();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ int ofs_src = (i * w + j) * 4;
+ int ofs_dst = (i * w + j) * 2;
+ wr[ofs_dst + 0] = 255;
+ wro[ofs_dst + 0] = 255;
+ if (r[ofs_src + 0] > 0x0F) {
+ wr[ofs_dst + 1] = (r[ofs_src + 0] - 0x0F) * 2;
+ wro[ofs_dst + 1] = 0;
+ } else {
+ wr[ofs_dst + 1] = 0;
+ wro[ofs_dst + 1] = r[ofs_src + 0] * 2;
+ }
+ wg[ofs_dst + 0] = 255;
+ wgo[ofs_dst + 0] = 255;
+ if (r[ofs_src + 1] > 0x0F) {
+ wg[ofs_dst + 1] = (r[ofs_src + 1] - 0x0F) * 2;
+ wgo[ofs_dst + 1] = 0;
+ } else {
+ wg[ofs_dst + 1] = 0;
+ wgo[ofs_dst + 1] = r[ofs_src + 1] * 2;
+ }
+ wb[ofs_dst + 0] = 255;
+ wbo[ofs_dst + 0] = 255;
+ if (r[ofs_src + 2] > 0x0F) {
+ wb[ofs_dst + 1] = (r[ofs_src + 2] - 0x0F) * 2;
+ wbo[ofs_dst + 1] = 0;
+ } else {
+ wb[ofs_dst + 1] = 0;
+ wbo[ofs_dst + 1] = r[ofs_src + 2] * 2;
+ }
+ wa[ofs_dst + 0] = 255;
+ wao[ofs_dst + 0] = 255;
+ if (r[ofs_src + 3] > 0x0F) {
+ wa[ofs_dst + 1] = (r[ofs_src + 3] - 0x0F) * 2;
+ wao[ofs_dst + 1] = 0;
+ } else {
+ wa[ofs_dst + 1] = 0;
+ wao[ofs_dst + 1] = r[ofs_src + 3] * 2;
+ }
+ }
+ }
+ Ref<Image> img_r = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_r));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 0, img_r);
+ Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 1, img_g);
+ Ref<Image> img_b = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_b));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 2, img_b);
+ Ref<Image> img_a = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_a));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page * 4 + 3, img_a);
+
+ Ref<Image> img_ro = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ro));
+ r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 0, img_ro);
+ Ref<Image> img_go = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_go));
+ r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 1, img_go);
+ Ref<Image> img_bo = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_bo));
+ r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 2, img_bo);
+ Ref<Image> img_ao = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_ao));
+ r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page * 4 + 3, img_ao);
+}
+
+void _convert_rgba_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_sz) {
+ int w = p_source->get_width();
+ int h = p_source->get_height();
+
+ PackedByteArray imgdata = p_source->get_data();
+ const uint8_t *r = imgdata.ptr();
+
+ PackedByteArray imgdata_g;
+ imgdata_g.resize(w * h * 4);
+ uint8_t *wg = imgdata_g.ptrw();
+
+ PackedByteArray imgdata_o;
+ imgdata_o.resize(w * h * 4);
+ uint8_t *wo = imgdata_o.ptrw();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ int ofs = (i * w + j) * 4;
+
+ if (r[ofs + 0] > 0x7F) {
+ wg[ofs + 0] = r[ofs + 0];
+ wo[ofs + 0] = 0;
+ } else {
+ wg[ofs + 0] = 0;
+ wo[ofs + 0] = r[ofs + 0] * 2;
+ }
+ if (r[ofs + 1] > 0x7F) {
+ wg[ofs + 1] = r[ofs + 1];
+ wo[ofs + 1] = 0;
+ } else {
+ wg[ofs + 1] = 0;
+ wo[ofs + 1] = r[ofs + 1] * 2;
+ }
+ if (r[ofs + 2] > 0x7F) {
+ wg[ofs + 2] = r[ofs + 2];
+ wo[ofs + 2] = 0;
+ } else {
+ wg[ofs + 2] = 0;
+ wo[ofs + 2] = r[ofs + 2] * 2;
+ }
+ if (r[ofs + 3] > 0x7F) {
+ wg[ofs + 3] = r[ofs + 3];
+ wo[ofs + 3] = 0;
+ } else {
+ wg[ofs + 3] = 0;
+ wo[ofs + 3] = r[ofs + 3] * 2;
+ }
+ }
+ }
+ Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_g));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
+
+ Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_RGBA8, imgdata_o));
+ r_font->set_texture_image(0, Vector2i(p_sz, 1), p_page, img_o);
+}
+
+void _convert_mono_8bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) {
+ int w = p_source->get_width();
+ int h = p_source->get_height();
+
+ PackedByteArray imgdata = p_source->get_data();
+ const uint8_t *r = imgdata.ptr();
+
+ int size = 4;
+ if (p_source->get_format() == Image::FORMAT_L8) {
+ size = 1;
+ p_ch = 0;
+ }
+
+ PackedByteArray imgdata_g;
+ imgdata_g.resize(w * h * 2);
+ uint8_t *wg = imgdata_g.ptrw();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ int ofs_src = (i * w + j) * size;
+ int ofs_dst = (i * w + j) * 2;
+ wg[ofs_dst + 0] = 255;
+ wg[ofs_dst + 1] = r[ofs_src + p_ch];
+ }
+ }
+ Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ r_font->set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_g);
+}
+
+void _convert_mono_4bit(Ref<FontData> &r_font, Ref<Image> &p_source, int p_page, int p_ch, int p_sz, int p_ol) {
+ int w = p_source->get_width();
+ int h = p_source->get_height();
+
+ PackedByteArray imgdata = p_source->get_data();
+ const uint8_t *r = imgdata.ptr();
+
+ int size = 4;
+ if (p_source->get_format() == Image::FORMAT_L8) {
+ size = 1;
+ p_ch = 0;
+ }
+
+ PackedByteArray imgdata_g;
+ imgdata_g.resize(w * h * 2);
+ uint8_t *wg = imgdata_g.ptrw();
+
+ PackedByteArray imgdata_o;
+ imgdata_o.resize(w * h * 2);
+ uint8_t *wo = imgdata_o.ptrw();
+
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ int ofs_src = (i * w + j) * size;
+ int ofs_dst = (i * w + j) * 2;
+ wg[ofs_dst + 0] = 255;
+ wo[ofs_dst + 0] = 255;
+ if (r[ofs_src + p_ch] > 0x7F) {
+ wg[ofs_dst + 1] = r[ofs_src + p_ch];
+ wo[ofs_dst + 1] = 0;
+ } else {
+ wg[ofs_dst + 1] = 0;
+ wo[ofs_dst + 1] = r[ofs_src + p_ch] * 2;
+ }
+ }
+ }
+ Ref<Image> img_g = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_g));
+ r_font->set_texture_image(0, Vector2i(p_sz, 0), p_page, img_g);
+
+ Ref<Image> img_o = memnew(Image(w, h, 0, Image::FORMAT_LA8, imgdata_o));
+ r_font->set_texture_image(0, Vector2i(p_sz, p_ol), p_page, img_o);
+}
+
+Error ResourceImporterBMFont::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
+ print_verbose("Importing BMFont font from: " + p_source_file);
+
+ Ref<FontData> font;
+ font.instantiate();
+ font->set_antialiased(false);
+ font->set_multichannel_signed_distance_field(false);
+ font->set_force_autohinter(false);
+ font->set_hinting(TextServer::HINTING_NONE);
+ font->set_oversampling(1.0f);
+
+ FileAccessRef f = FileAccess::open(p_source_file, FileAccess::READ);
+ if (f == nullptr) {
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Cannot open font from file ") + "\"" + p_source_file + "\".");
+ }
+
+ int base_size = 16;
+ int height = 0;
+ int ascent = 0;
+ int outline = 0;
+
+ bool packed = false;
+ uint8_t ch[4] = { 0, 0, 0, 0 }; // RGBA
+ int first_gl_ch = -1;
+ int first_ol_ch = -1;
+ int first_cm_ch = -1;
+
+ unsigned char magic[4];
+ f->get_buffer((unsigned char *)&magic, 4);
+ if (magic[0] == 'B' && magic[1] == 'M' && magic[2] == 'F') {
+ // Binary BMFont file.
+ ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(TTR("Version %d of BMFont is not supported."), (int)magic[3]));
+
+ uint8_t block_type = f->get_8();
+ uint32_t block_size = f->get_32();
+ while (!f->eof_reached()) {
+ uint64_t off = f->get_position();
+ switch (block_type) {
+ case 1: /* info */ {
+ ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, TTR("Invalid BMFont info block size."));
+ base_size = f->get_16();
+ uint8_t flags = f->get_8();
+ ERR_FAIL_COND_V_MSG(flags & 0x02, ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported."));
+ f->get_8(); // non-unicode charset, skip
+ f->get_16(); // stretch_h, skip
+ f->get_8(); // aa, skip
+ f->get_32(); // padding, skip
+ f->get_16(); // spacing, skip
+ outline = f->get_8();
+ // font name, skip
+ font->set_fixed_size(base_size);
+ } break;
+ case 2: /* common */ {
+ ERR_FAIL_COND_V_MSG(block_size != 15, ERR_CANT_CREATE, TTR("Invalid BMFont common block size."));
+ height = f->get_16();
+ ascent = f->get_16();
+ f->get_32(); // scale, skip
+ f->get_16(); // pages, skip
+ uint8_t flags = f->get_8();
+ packed = (flags & 0x01);
+ ch[3] = f->get_8();
+ ch[0] = f->get_8();
+ ch[1] = f->get_8();
+ ch[2] = f->get_8();
+ for (int i = 0; i < 4; i++) {
+ if (ch[i] == 0 && first_gl_ch == -1) {
+ first_gl_ch = i;
+ }
+ if (ch[i] == 1 && first_ol_ch == -1) {
+ first_ol_ch = i;
+ }
+ if (ch[i] == 2 && first_cm_ch == -1) {
+ first_cm_ch = i;
+ }
+ }
+ } break;
+ case 3: /* pages */ {
+ int page = 0;
+ CharString cs;
+ char32_t c = f->get_8();
+ while (!f->eof_reached() && f->get_position() <= off + block_size) {
+ if (c == '\0') {
+ String base_dir = p_source_file.get_base_dir();
+ String file = base_dir.plus_file(String::utf8(cs.ptr(), cs.length()));
+ if (RenderingServer::get_singleton() != nullptr) {
+ Ref<Image> img;
+ img.instantiate();
+ Error err = ImageLoader::load_image(file, img);
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\".");
+
+ if (packed) {
+ if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
+ outline = 0;
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_packed_8bit(font, img, page, base_size);
+ } else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_packed_4bit(font, img, page, base_size);
+ } else {
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+ }
+ } else {
+ if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
+ outline = 0;
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ font->set_texture_image(0, Vector2i(base_size, 0), page, img);
+ } else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_rgba_4bit(font, img, page, base_size);
+ } else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
+ _convert_mono_8bit(font, img, page, first_ol_ch, base_size, 1);
+ } else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_mono_4bit(font, img, page, first_cm_ch, base_size, 1);
+ } else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
+ outline = 0;
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
+ } else {
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+ }
+ }
+ }
+ page++;
+ cs = "";
+ } else {
+ cs += c;
+ }
+ c = f->get_8();
+ }
+ } break;
+ case 4: /* chars */ {
+ int char_count = block_size / 20;
+ for (int i = 0; i < char_count; i++) {
+ Vector2 advance;
+ Vector2 size;
+ Vector2 offset;
+ Rect2 uv_rect;
+
+ char32_t idx = f->get_32();
+ uv_rect.position.x = (int16_t)f->get_16();
+ uv_rect.position.y = (int16_t)f->get_16();
+ uv_rect.size.width = (int16_t)f->get_16();
+ size.width = uv_rect.size.width;
+ uv_rect.size.height = (int16_t)f->get_16();
+ size.height = uv_rect.size.height;
+ offset.x = (int16_t)f->get_16();
+ offset.y = (int16_t)f->get_16() - ascent;
+ advance.x = (int16_t)f->get_16();
+ if (advance.x < 0) {
+ advance.x = size.width + 1;
+ }
+
+ int texture_idx = f->get_8();
+ uint8_t channel = f->get_8();
+
+ ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel."));
+ int ch_off = 0;
+ switch (channel) {
+ case 1:
+ ch_off = 2;
+ break; // B
+ case 2:
+ ch_off = 1;
+ break; // G
+ case 4:
+ ch_off = 0;
+ break; // R
+ case 8:
+ ch_off = 3;
+ break; // A
+ default:
+ ch_off = 0;
+ break;
+ }
+ font->set_glyph_advance(0, base_size, idx, advance);
+ font->set_glyph_offset(0, Vector2i(base_size, 0), idx, offset);
+ font->set_glyph_size(0, Vector2i(base_size, 0), idx, size);
+ font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect);
+ font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+ if (outline > 0) {
+ font->set_glyph_offset(0, Vector2i(base_size, 1), idx, offset);
+ font->set_glyph_size(0, Vector2i(base_size, 1), idx, size);
+ font->set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect);
+ font->set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+ }
+ }
+ } break;
+ case 5: /* kerning */ {
+ int pair_count = block_size / 10;
+ for (int i = 0; i < pair_count; i++) {
+ Vector2i kpk;
+ kpk.x = f->get_32();
+ kpk.y = f->get_32();
+ font->set_kerning(0, base_size, kpk, Vector2((int16_t)f->get_16(), 0));
+ }
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Invalid BMFont block type."));
+ } break;
+ }
+ f->seek(off + block_size);
+ block_type = f->get_8();
+ block_size = f->get_32();
+ }
+
+ } else {
+ // Text BMFont file.
+ f->seek(0);
+ while (true) {
+ String line = f->get_line();
+
+ int delimiter = line.find(" ");
+ String type = line.substr(0, delimiter);
+ int pos = delimiter + 1;
+ Map<String, String> keys;
+
+ while (pos < line.size() && line[pos] == ' ') {
+ pos++;
+ }
+
+ while (pos < line.size()) {
+ int eq = line.find("=", pos);
+ if (eq == -1) {
+ break;
+ }
+ String key = line.substr(pos, eq - pos);
+ int end = -1;
+ String value;
+ if (line[eq + 1] == '"') {
+ end = line.find("\"", eq + 2);
+ if (end == -1) {
+ break;
+ }
+ value = line.substr(eq + 2, end - 1 - eq - 1);
+ pos = end + 1;
+ } else {
+ end = line.find(" ", eq + 1);
+ if (end == -1) {
+ end = line.size();
+ }
+ value = line.substr(eq + 1, end - eq);
+ pos = end;
+ }
+
+ while (pos < line.size() && line[pos] == ' ') {
+ pos++;
+ }
+
+ keys[key] = value;
+ }
+
+ if (type == "info") {
+ if (keys.has("size")) {
+ base_size = keys["size"].to_int();
+ font->set_fixed_size(base_size);
+ }
+ if (keys.has("outline")) {
+ outline = keys["outline"].to_int();
+ }
+ ERR_FAIL_COND_V_MSG((!keys.has("unicode") || keys["unicode"].to_int() != 1), ERR_CANT_CREATE, TTR("Non-unicode version of BMFont is not supported."));
+ } else if (type == "common") {
+ if (keys.has("lineHeight")) {
+ height = keys["lineHeight"].to_int();
+ }
+ if (keys.has("base")) {
+ ascent = keys["base"].to_int();
+ }
+ if (keys.has("packed")) {
+ packed = (keys["packed"].to_int() == 1);
+ }
+ if (keys.has("alphaChnl")) {
+ ch[3] = keys["alphaChnl"].to_int();
+ }
+ if (keys.has("redChnl")) {
+ ch[0] = keys["redChnl"].to_int();
+ }
+ if (keys.has("greenChnl")) {
+ ch[1] = keys["greenChnl"].to_int();
+ }
+ if (keys.has("blueChnl")) {
+ ch[2] = keys["blueChnl"].to_int();
+ }
+ for (int i = 0; i < 4; i++) {
+ if (ch[i] == 0 && first_gl_ch == -1) {
+ first_gl_ch = i;
+ }
+ if (ch[i] == 1 && first_ol_ch == -1) {
+ first_ol_ch = i;
+ }
+ if (ch[i] == 2 && first_cm_ch == -1) {
+ first_cm_ch = i;
+ }
+ }
+ } else if (type == "page") {
+ int page = 0;
+ if (keys.has("id")) {
+ page = keys["id"].to_int();
+ }
+ if (keys.has("file")) {
+ String base_dir = p_source_file.get_base_dir();
+ String file = base_dir.plus_file(keys["file"]);
+ if (RenderingServer::get_singleton() != nullptr) {
+ Ref<Image> img;
+ img.instantiate();
+ Error err = ImageLoader::load_image(file, img);
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + file + "\".");
+ if (packed) {
+ if (ch[3] == 0) { // 4 x 8 bit monochrome, no outline
+ outline = 0;
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_packed_8bit(font, img, page, base_size);
+ } else if ((ch[3] == 2) && (outline > 0)) { // 4 x 4 bit monochrome, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_packed_4bit(font, img, page, base_size);
+ } else {
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+ }
+ } else {
+ if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline
+ outline = 0;
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ font->set_texture_image(0, Vector2i(base_size, 0), page, img);
+ } else if ((ch[0] == 2) && (ch[1] == 2) && (ch[2] == 2) && (ch[3] == 2) && (outline > 0)) { // RGBA4 color, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_rgba_4bit(font, img, page, base_size);
+ } else if ((first_gl_ch >= 0) && (first_ol_ch >= 0) && (outline > 0)) { // 1 x 8 bit monochrome, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
+ _convert_mono_8bit(font, img, page, first_ol_ch, base_size, 1);
+ } else if ((first_cm_ch >= 0) && (outline > 0)) { // 1 x 4 bit monochrome, gl + outline
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_mono_4bit(font, img, page, first_cm_ch, base_size, 1);
+ } else if (first_gl_ch >= 0) { // 1 x 8 bit monochrome, no outline
+ outline = 0;
+ ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8 && img->get_format() != Image::FORMAT_L8, ERR_FILE_CANT_READ, TTR("Unsupported BMFont texture format."));
+ _convert_mono_8bit(font, img, page, first_gl_ch, base_size, 0);
+ } else {
+ ERR_FAIL_V_MSG(ERR_CANT_CREATE, TTR("Unsupported BMFont texture format."));
+ }
+ }
+ }
+ }
+ } else if (type == "char") {
+ char32_t idx = 0;
+ Vector2 advance;
+ Vector2 size;
+ Vector2 offset;
+ Rect2 uv_rect;
+ int texture_idx = -1;
+ uint8_t channel = 15;
+
+ if (keys.has("id")) {
+ idx = keys["id"].to_int();
+ }
+ if (keys.has("x")) {
+ uv_rect.position.x = keys["x"].to_int();
+ }
+ if (keys.has("y")) {
+ uv_rect.position.y = keys["y"].to_int();
+ }
+ if (keys.has("width")) {
+ uv_rect.size.width = keys["width"].to_int();
+ size.width = keys["width"].to_int();
+ }
+ if (keys.has("height")) {
+ uv_rect.size.height = keys["height"].to_int();
+ size.height = keys["height"].to_int();
+ }
+ if (keys.has("xoffset")) {
+ offset.x = keys["xoffset"].to_int();
+ }
+ if (keys.has("yoffset")) {
+ offset.y = keys["yoffset"].to_int() - ascent;
+ }
+ if (keys.has("page")) {
+ texture_idx = keys["page"].to_int();
+ }
+ if (keys.has("xadvance")) {
+ advance.x = keys["xadvance"].to_int();
+ }
+ if (advance.x < 0) {
+ advance.x = size.width + 1;
+ }
+ if (keys.has("chnl")) {
+ channel = keys["chnl"].to_int();
+ }
+
+ ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, TTR("Invalid glyph channel."));
+ int ch_off = 0;
+ switch (channel) {
+ case 1:
+ ch_off = 2;
+ break; // B
+ case 2:
+ ch_off = 1;
+ break; // G
+ case 4:
+ ch_off = 0;
+ break; // R
+ case 8:
+ ch_off = 3;
+ break; // A
+ default:
+ ch_off = 0;
+ break;
+ }
+ font->set_glyph_advance(0, base_size, idx, advance);
+ font->set_glyph_offset(0, Vector2i(base_size, 0), idx, offset);
+ font->set_glyph_size(0, Vector2i(base_size, 0), idx, size);
+ font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, uv_rect);
+ font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+ if (outline > 0) {
+ font->set_glyph_offset(0, Vector2i(base_size, 1), idx, offset);
+ font->set_glyph_size(0, Vector2i(base_size, 1), idx, size);
+ font->set_glyph_uv_rect(0, Vector2i(base_size, 1), idx, uv_rect);
+ font->set_glyph_texture_idx(0, Vector2i(base_size, 1), idx, texture_idx * (packed ? 4 : 1) + ch_off);
+ }
+ } else if (type == "kerning") {
+ Vector2i kpk;
+ if (keys.has("first")) {
+ kpk.x = keys["first"].to_int();
+ }
+ if (keys.has("second")) {
+ kpk.y = keys["second"].to_int();
+ }
+ if (keys.has("amount")) {
+ font->set_kerning(0, base_size, kpk, Vector2(keys["amount"].to_int(), 0));
+ }
+ }
+
+ if (f->eof_reached()) {
+ break;
+ }
+ }
+ }
+
+ font->set_ascent(0, base_size, ascent);
+ font->set_descent(0, base_size, height - ascent);
+
+ int flg = ResourceSaver::SaverFlags::FLAG_BUNDLE_RESOURCES | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
+ if ((bool)p_options["compress"]) {
+ flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
+ }
+
+ print_verbose("Saving to: " + p_save_path + ".fontdata");
+ Error err = ResourceSaver::save(p_save_path + ".fontdata", font, flg);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
+ print_verbose("Done saving to: " + p_save_path + ".fontdata");
+ return OK;
+}
+
+ResourceImporterBMFont::ResourceImporterBMFont() {
+}
diff --git a/editor/import/resource_importer_bmfont.h b/editor/import/resource_importer_bmfont.h
new file mode 100644
index 0000000000..065703132a
--- /dev/null
+++ b/editor/import/resource_importer_bmfont.h
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* resource_importer_bmfont.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 RESOURCE_IMPORTER_BMFONT_H
+#define RESOURCE_IMPORTER_BMFONT_H
+
+#include "core/io/resource_importer.h"
+#include "scene/resources/font.h"
+#include "servers/text_server.h"
+
+class ResourceImporterBMFont : public ResourceImporter {
+ GDCLASS(ResourceImporterBMFont, ResourceImporter);
+
+public:
+ virtual String get_importer_name() const override;
+ virtual String get_visible_name() const override;
+ virtual void get_recognized_extensions(List<String> *p_extensions) const override;
+ virtual String get_save_extension() const override;
+ virtual String get_resource_type() const override;
+
+ virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
+ virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
+
+ virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+
+ ResourceImporterBMFont();
+};
+
+#endif // RESOURCE_IMPORTER_BMFONT_H
diff --git a/editor/import/resource_importer_dynamicfont.cpp b/editor/import/resource_importer_dynamicfont.cpp
new file mode 100644
index 0000000000..8e01adbd56
--- /dev/null
+++ b/editor/import/resource_importer_dynamicfont.cpp
@@ -0,0 +1,304 @@
+/*************************************************************************/
+/* resource_importer_dynamicfont.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#include "resource_importer_dynamicfont.h"
+
+#include "dynamicfont_import_settings.h"
+
+#include "core/io/file_access.h"
+#include "core/io/resource_saver.h"
+#include "editor/editor_node.h"
+#include "modules/modules_enabled.gen.h"
+
+String ResourceImporterDynamicFont::get_importer_name() const {
+ return "font_data_dynamic";
+}
+
+String ResourceImporterDynamicFont::get_visible_name() const {
+ return "Font Data (Dynamic Font)";
+}
+
+void ResourceImporterDynamicFont::get_recognized_extensions(List<String> *p_extensions) const {
+ if (p_extensions) {
+#ifdef MODULE_FREETYPE_ENABLED
+ p_extensions->push_back("ttf");
+ p_extensions->push_back("otf");
+ p_extensions->push_back("woff");
+ //p_extensions->push_back("woff2");
+ p_extensions->push_back("pfb");
+ p_extensions->push_back("pfm");
+#endif
+ }
+}
+
+String ResourceImporterDynamicFont::get_save_extension() const {
+ return "fontdata";
+}
+
+String ResourceImporterDynamicFont::get_resource_type() const {
+ return "FontData";
+}
+
+bool ResourceImporterDynamicFont::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+ if (p_option == "msdf_pixel_range" && !bool(p_options["multichannel_signed_distance_field"])) {
+ return false;
+ }
+ if (p_option == "msdf_size" && !bool(p_options["multichannel_signed_distance_field"])) {
+ return false;
+ }
+ if (p_option == "oversampling" && bool(p_options["multichannel_signed_distance_field"])) {
+ return false;
+ }
+ return true;
+}
+
+int ResourceImporterDynamicFont::get_preset_count() const {
+ return PRESET_MAX;
+}
+
+String ResourceImporterDynamicFont::get_preset_name(int p_idx) const {
+ switch (p_idx) {
+ case PRESET_DYNAMIC:
+ return TTR("Dynamically rendered TrueType/OpenType font");
+ case PRESET_MSDF:
+ return TTR("Prerendered multichannel(+true) signed distance field");
+ default:
+ return String();
+ }
+}
+
+void ResourceImporterDynamicFont::get_import_options(List<ImportOption> *r_options, int p_preset) const {
+ bool msdf = p_preset == PRESET_MSDF;
+
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "antialiased"), true));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), (msdf) ? true : false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), 8));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48));
+
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0));
+
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
+
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "preload/char_ranges"), Vector<String>()));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "preload/glyph_ranges"), Vector<String>()));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "preload/configurations"), Vector<String>()));
+
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/language_enabled"), Vector<String>()));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/language_disabled"), Vector<String>()));
+
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/script_enabled"), Vector<String>()));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "support_overrides/script_disabled"), Vector<String>()));
+}
+
+bool ResourceImporterDynamicFont::_decode_variation(const String &p_token, Dictionary &r_variations, Vector2i &r_size, String &r_name, Vector2i &r_spacing) {
+ Vector<String> tokens = p_token.split("=");
+ if (tokens.size() == 2) {
+ if (tokens[0] == "name") {
+ r_name = tokens[1];
+ } else if (tokens[0] == "size") {
+ r_size.x = tokens[1].to_int();
+ } else if (tokens[0] == "outline_size") {
+ r_size.y = tokens[1].to_int();
+ } else if (tokens[0] == "spacing_space") {
+ r_spacing.x = tokens[1].to_int();
+ } else if (tokens[0] == "spacing_glyph") {
+ r_spacing.y = tokens[1].to_int();
+ } else {
+ r_variations[tokens[0]] = tokens[1].to_float();
+ }
+ return true;
+ } else {
+ WARN_PRINT("Invalid variation: '" + p_token + "'.");
+ return false;
+ }
+}
+
+bool ResourceImporterDynamicFont::_decode_range(const String &p_token, int32_t &r_pos) {
+ if (p_token.begins_with("U+") || p_token.begins_with("u+") || p_token.begins_with("0x")) {
+ // Unicode character hex index.
+ r_pos = p_token.substr(2).hex_to_int();
+ return true;
+ } else if (p_token.length() == 3 && p_token[0] == '\'' && p_token[2] == '\'') {
+ // Unicode character.
+ r_pos = p_token.unicode_at(1);
+ return true;
+ } else if (p_token.is_numeric()) {
+ // Unicode character decimal index.
+ r_pos = p_token.to_int();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool ResourceImporterDynamicFont::has_advanced_options() const {
+ return true;
+}
+void ResourceImporterDynamicFont::show_advanced_options(const String &p_path) {
+ DynamicFontImportSettings::get_singleton()->open_settings(p_path);
+}
+
+Error ResourceImporterDynamicFont::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
+ print_verbose("Importing dynamic font from: " + p_source_file);
+
+ bool antialiased = p_options["antialiased"];
+ bool msdf = p_options["multichannel_signed_distance_field"];
+ int px_range = p_options["msdf_pixel_range"];
+ int px_size = p_options["msdf_size"];
+
+ bool autohinter = p_options["force_autohinter"];
+ int hinting = p_options["hinting"];
+ real_t oversampling = p_options["oversampling"];
+
+ // Load base font data.
+ Vector<uint8_t> data = FileAccess::get_file_as_array(p_source_file);
+
+ // Create font.
+ Ref<FontData> font;
+ font.instantiate();
+ font->set_data(data);
+ font->set_antialiased(antialiased);
+ font->set_multichannel_signed_distance_field(msdf);
+ font->set_msdf_pixel_range(px_range);
+ font->set_msdf_size(px_size);
+ font->set_fixed_size(0);
+ font->set_force_autohinter(autohinter);
+ font->set_hinting((TextServer::Hinting)hinting);
+ font->set_oversampling(oversampling);
+
+ Vector<String> lang_en = p_options["support_overrides/language_enabled"];
+ for (int i = 0; i < lang_en.size(); i++) {
+ font->set_language_support_override(lang_en[i], true);
+ }
+
+ Vector<String> lang_dis = p_options["support_overrides/language_disabled"];
+ for (int i = 0; i < lang_dis.size(); i++) {
+ font->set_language_support_override(lang_dis[i], false);
+ }
+
+ Vector<String> scr_en = p_options["support_overrides/script_enabled"];
+ for (int i = 0; i < scr_en.size(); i++) {
+ font->set_script_support_override(scr_en[i], true);
+ }
+
+ Vector<String> scr_dis = p_options["support_overrides/script_disabled"];
+ for (int i = 0; i < scr_dis.size(); i++) {
+ font->set_script_support_override(scr_dis[i], false);
+ }
+
+ Vector<String> variations = p_options["preload/configurations"];
+ Vector<String> char_ranges = p_options["preload/char_ranges"];
+ Vector<String> gl_ranges = p_options["preload/glyph_ranges"];
+
+ for (int i = 0; i < variations.size(); i++) {
+ String name;
+ Dictionary var;
+ Vector2i size = Vector2(16, 0);
+ Vector2i spacing;
+
+ Vector<String> variation_tags = variations[i].split(",");
+ for (int j = 0; j < variation_tags.size(); j++) {
+ if (!_decode_variation(variation_tags[j], var, size, name, spacing)) {
+ WARN_PRINT(vformat(TTR("Invalid variation: \"%s\""), variations[i]));
+ continue;
+ }
+ }
+ RID conf = font->find_cache(var);
+
+ for (int j = 0; j < char_ranges.size(); j++) {
+ int32_t start, end;
+ Vector<String> tokens = char_ranges[j].split("-");
+ if (tokens.size() == 2) {
+ if (!_decode_range(tokens[0], start) || !_decode_range(tokens[1], end)) {
+ WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), char_ranges[j]));
+ continue;
+ }
+ } else if (tokens.size() == 1) {
+ if (!_decode_range(tokens[0], start)) {
+ WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), char_ranges[j]));
+ continue;
+ }
+ end = start;
+ } else {
+ WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), char_ranges[j]));
+ continue;
+ }
+
+ // Preload character ranges for each variations / sizes.
+ print_verbose(vformat(TTR("Pre-rendering range U+%s...%s from configuration \"%s\" (%d / %d)..."), String::num_int64(start, 16), String::num_int64(end, 16), name, i + 1, variations.size()));
+ TS->font_render_range(conf, size, start, end);
+ }
+
+ for (int j = 0; j < gl_ranges.size(); j++) {
+ int32_t start, end;
+ Vector<String> tokens = gl_ranges[j].split("-");
+ if (tokens.size() == 2) {
+ if (!_decode_range(tokens[0], start) || !_decode_range(tokens[1], end)) {
+ WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), gl_ranges[j]));
+ continue;
+ }
+ } else if (tokens.size() == 1) {
+ if (!_decode_range(tokens[0], start)) {
+ WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), gl_ranges[j]));
+ continue;
+ }
+ end = start;
+ } else {
+ WARN_PRINT(vformat(TTR("Invalid range: \"%s\""), gl_ranges[j]));
+ continue;
+ }
+
+ // Preload glyph range for each variations / sizes.
+ print_verbose(vformat(TTR("Pre-rendering glyph range 0x%s...%s from configuration \"%s\" (%d / %d)..."), String::num_int64(start, 16), String::num_int64(end, 16), name, i + 1, variations.size()));
+ for (int32_t k = start; k <= end; k++) {
+ TS->font_render_glyph(conf, size, k);
+ }
+ }
+
+ TS->font_set_spacing(conf, size.x, TextServer::SPACING_SPACE, spacing.x);
+ TS->font_set_spacing(conf, size.x, TextServer::SPACING_GLYPH, spacing.y);
+ }
+
+ int flg = ResourceSaver::SaverFlags::FLAG_BUNDLE_RESOURCES | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
+ if ((bool)p_options["compress"]) {
+ flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
+ }
+
+ print_verbose("Saving to: " + p_save_path + ".fontdata");
+ Error err = ResourceSaver::save(p_save_path + ".fontdata", font, flg);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
+ print_verbose("Done saving to: " + p_save_path + ".fontdata");
+ return OK;
+}
+
+ResourceImporterDynamicFont::ResourceImporterDynamicFont() {
+}
diff --git a/editor/import/resource_importer_dynamicfont.h b/editor/import/resource_importer_dynamicfont.h
new file mode 100644
index 0000000000..52f256ab96
--- /dev/null
+++ b/editor/import/resource_importer_dynamicfont.h
@@ -0,0 +1,71 @@
+/*************************************************************************/
+/* resource_importer_dynamicfont.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 RESOURCE_IMPORTER_FONT_DATA_H
+#define RESOURCE_IMPORTER_FONT_DATA_H
+
+#include "core/io/resource_importer.h"
+#include "scene/resources/font.h"
+#include "servers/text_server.h"
+
+class ResourceImporterDynamicFont : public ResourceImporter {
+ GDCLASS(ResourceImporterDynamicFont, ResourceImporter);
+
+ enum Presets {
+ PRESET_DYNAMIC,
+ PRESET_MSDF,
+ PRESET_MAX
+ };
+
+public:
+ static bool _decode_range(const String &p_token, int32_t &r_pos);
+ static bool _decode_variation(const String &p_token, Dictionary &r_variations, Vector2i &r_size, String &r_name, Vector2i &r_spacing);
+
+ virtual String get_importer_name() const override;
+ virtual String get_visible_name() const override;
+ virtual void get_recognized_extensions(List<String> *p_extensions) const override;
+ virtual String get_save_extension() const override;
+ virtual String get_resource_type() const override;
+
+ virtual int get_preset_count() const override;
+ virtual String get_preset_name(int p_idx) const override;
+
+ virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
+ virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
+
+ bool has_advanced_options() const override;
+ void show_advanced_options(const String &p_path) override;
+
+ virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+
+ ResourceImporterDynamicFont();
+};
+
+#endif // RESOURCE_IMPORTER_FONTDATA_H
diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp
new file mode 100644
index 0000000000..997280d1dd
--- /dev/null
+++ b/editor/import/resource_importer_imagefont.cpp
@@ -0,0 +1,162 @@
+/*************************************************************************/
+/* resource_importer_imagefont.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#include "resource_importer_imagefont.h"
+
+#include "core/io/image_loader.h"
+#include "core/io/resource_saver.h"
+
+String ResourceImporterImageFont::get_importer_name() const {
+ return "font_data_image";
+}
+
+String ResourceImporterImageFont::get_visible_name() const {
+ return "Font Data (Monospace Image Font)";
+}
+
+void ResourceImporterImageFont::get_recognized_extensions(List<String> *p_extensions) const {
+ if (p_extensions) {
+ ImageLoader::get_recognized_extensions(p_extensions);
+ }
+}
+
+String ResourceImporterImageFont::get_save_extension() const {
+ return "fontdata";
+}
+
+String ResourceImporterImageFont::get_resource_type() const {
+ return "FontData";
+}
+
+bool ResourceImporterImageFont::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const {
+ return true;
+}
+
+void ResourceImporterImageFont::get_import_options(List<ImportOption> *r_options, int p_preset) const {
+ r_options->push_back(ImportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "character_ranges"), Vector<String>()));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "columns"), 1));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rows"), 1));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "font_size"), 14));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress"), true));
+}
+
+bool ResourceImporterImageFont::_decode_range(const String &p_token, int32_t &r_pos) {
+ if (p_token.begins_with("U+") || p_token.begins_with("u+") || p_token.begins_with("0x")) {
+ // Unicode character hex index.
+ r_pos = p_token.substr(2).hex_to_int();
+ return true;
+ } else if (p_token.length() == 3 && p_token[0] == '\'' && p_token[2] == '\'') {
+ // Unicode character.
+ r_pos = p_token.unicode_at(1);
+ return true;
+ } else if (p_token.is_numeric()) {
+ // Unicode character decimal index.
+ r_pos = p_token.to_int();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+Error ResourceImporterImageFont::import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
+ print_verbose("Importing image font from: " + p_source_file);
+
+ int columns = p_options["columns"];
+ int rows = p_options["rows"];
+ int base_size = p_options["font_size"];
+ Vector<String> ranges = p_options["character_ranges"];
+
+ Ref<FontData> font;
+ font.instantiate();
+ font->set_antialiased(false);
+ font->set_multichannel_signed_distance_field(false);
+ font->set_fixed_size(base_size);
+ font->set_force_autohinter(false);
+ font->set_hinting(TextServer::HINTING_NONE);
+ font->set_oversampling(1.0f);
+
+ Ref<Image> img;
+ img.instantiate();
+ Error err = ImageLoader::load_image(p_source_file, img);
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_READ, TTR("Can't load font texture: ") + "\"" + p_source_file + "\".");
+ font->set_texture_image(0, Vector2i(base_size, 0), 0, img);
+
+ int count = columns * rows;
+ int chr_width = img->get_width() / columns;
+ int chr_height = img->get_height() / rows;
+ int pos = 0;
+
+ for (int i = 0; i < ranges.size(); i++) {
+ int32_t start, end;
+ Vector<String> tokens = ranges[i].split("-");
+ if (tokens.size() == 2) {
+ if (!_decode_range(tokens[0], start) || !_decode_range(tokens[1], end)) {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ } else if (tokens.size() == 1) {
+ if (!_decode_range(tokens[0], start)) {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ end = start;
+ } else {
+ WARN_PRINT("Invalid range: \"" + ranges[i] + "\"");
+ continue;
+ }
+ for (int32_t idx = start; idx <= end; idx++) {
+ int x = pos % columns;
+ int y = pos / columns;
+ font->set_glyph_advance(0, base_size, idx, Vector2(chr_width, 0));
+ font->set_glyph_offset(0, Vector2i(base_size, 0), idx, Vector2(0, -0.5 * chr_height));
+ font->set_glyph_size(0, Vector2i(base_size, 0), idx, Vector2(chr_width, chr_height));
+ font->set_glyph_uv_rect(0, Vector2i(base_size, 0), idx, Rect2(chr_width * x, chr_height * y, chr_width, chr_height));
+ font->set_glyph_texture_idx(0, Vector2i(base_size, 0), idx, 0);
+ pos++;
+ ERR_FAIL_COND_V_MSG(pos >= count, ERR_CANT_CREATE, "Too many characters in range.");
+ }
+ }
+ font->set_ascent(0, base_size, 0.5 * chr_height);
+ font->set_descent(0, base_size, 0.5 * chr_height);
+
+ int flg = ResourceSaver::SaverFlags::FLAG_BUNDLE_RESOURCES | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS;
+ if ((bool)p_options["compress"]) {
+ flg |= ResourceSaver::SaverFlags::FLAG_COMPRESS;
+ }
+
+ print_verbose("Saving to: " + p_save_path + ".fontdata");
+ err = ResourceSaver::save(p_save_path + ".fontdata", font, flg);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save font to file \"" + p_save_path + ".res\".");
+ print_verbose("Done saving to: " + p_save_path + ".fontdata");
+ return OK;
+}
+
+ResourceImporterImageFont::ResourceImporterImageFont() {
+}
diff --git a/editor/import/resource_importer_imagefont.h b/editor/import/resource_importer_imagefont.h
new file mode 100644
index 0000000000..9b2b38596f
--- /dev/null
+++ b/editor/import/resource_importer_imagefont.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* resource_importer_imagefont.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 RESOURCE_IMPORTER_IMAGE_FONT_H
+#define RESOURCE_IMPORTER_IMAGE_FONT_H
+
+#include "core/io/resource_importer.h"
+#include "scene/resources/font.h"
+#include "servers/text_server.h"
+
+class ResourceImporterImageFont : public ResourceImporter {
+ GDCLASS(ResourceImporterImageFont, ResourceImporter);
+
+public:
+ static bool _decode_range(const String &p_token, int32_t &r_pos);
+
+ virtual String get_importer_name() const override;
+ virtual String get_visible_name() const override;
+ virtual void get_recognized_extensions(List<String> *p_extensions) const override;
+ virtual String get_save_extension() const override;
+ virtual String get_resource_type() const override;
+
+ virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
+ virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
+
+ virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+
+ ResourceImporterImageFont();
+};
+
+#endif // RESOURCE_IMPORTER_IMAGE_FONT_H
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index 2b03ad928c..9f617be4ee 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -46,6 +46,7 @@
#include "scene/resources/box_shape_3d.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/resource_format_text.h"
+#include "scene/resources/separation_ray_shape_3d.h"
#include "scene/resources/sphere_shape_3d.h"
#include "scene/resources/surface_tool.h"
#include "scene/resources/world_margin_shape_3d.h"
@@ -232,13 +233,14 @@ static String _fixstr(const String &p_what, const String &p_str) {
return what;
}
-static void _pre_gen_shape_list(const Ref<EditorSceneImporterMesh> &mesh, List<Ref<Shape3D>> &r_shape_list, bool p_convex) {
+static void _pre_gen_shape_list(Ref<EditorSceneImporterMesh> &mesh, Vector<Ref<Shape3D>> &r_shape_list, bool p_convex) {
ERR_FAIL_NULL_MSG(mesh, "Cannot generate shape list with null mesh value");
if (!p_convex) {
Ref<Shape3D> shape = mesh->create_trimesh_shape();
r_shape_list.push_back(shape);
} else {
- Vector<Ref<Shape3D>> cd = mesh->convex_decompose();
+ Vector<Ref<Shape3D>> cd;
+ cd.push_back(mesh->get_mesh()->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false));
if (cd.size()) {
for (int i = 0; i < cd.size(); i++) {
r_shape_list.push_back(cd[i]);
@@ -247,7 +249,7 @@ static void _pre_gen_shape_list(const Ref<EditorSceneImporterMesh> &mesh, List<R
}
}
-Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map) {
+Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map) {
// children first
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *r = _pre_fix_node(p_node->get_child(i), p_root, collision_map);
@@ -334,7 +336,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E
Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
if (mesh.is_valid()) {
- List<Ref<Shape3D>> shapes;
+ Vector<Ref<Shape3D>> shapes;
String fixed_name;
if (collision_map.has(mesh)) {
shapes = collision_map[mesh];
@@ -379,6 +381,11 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E
BoxShape3D *boxShape = memnew(BoxShape3D);
boxShape->set_size(Vector3(2, 2, 2));
colshape->set_shape(boxShape);
+ } else if (empty_draw_type == "SINGLE_ARROW") {
+ SeparationRayShape3D *rayShape = memnew(SeparationRayShape3D);
+ rayShape->set_length(1);
+ colshape->set_shape(rayShape);
+ Object::cast_to<Node3D>(sb)->rotate_x(Math_PI / 2);
} else if (empty_draw_type == "IMAGE") {
WorldMarginShape3D *world_margin_shape = memnew(WorldMarginShape3D);
colshape->set_shape(world_margin_shape);
@@ -400,7 +407,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E
Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
if (mesh.is_valid()) {
- List<Ref<Shape3D>> shapes;
+ Vector<Ref<Shape3D>> shapes;
if (collision_map.has(mesh)) {
shapes = collision_map[mesh];
} else {
@@ -425,7 +432,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E
Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
if (mesh.is_valid()) {
- List<Ref<Shape3D>> shapes;
+ Vector<Ref<Shape3D>> shapes;
String fixed_name;
if (collision_map.has(mesh)) {
shapes = collision_map[mesh];
@@ -484,7 +491,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E
Ref<EditorSceneImporterMesh> mesh = mi->get_mesh();
if (!mesh.is_null()) {
- List<Ref<Shape3D>> shapes;
+ Vector<Ref<Shape3D>> shapes;
if (collision_map.has(mesh)) {
shapes = collision_map[mesh];
} else if (_teststr(mesh->get_name(), "col")) {
@@ -510,7 +517,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<E
return p_node;
}
-Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) {
+Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps) {
// children first
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *r = _post_fix_node(p_node->get_child(i), p_root, collision_map, r_scanned_meshes, p_node_data, p_material_data, p_animation_data, p_animation_fps);
@@ -573,28 +580,35 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
}
if (node_settings.has("generate/physics")) {
- int mesh_physics_mode = node_settings["generate/physics"];
-
- if (mesh_physics_mode != MESH_PHYSICS_DISABLED) {
- List<Ref<Shape3D>> shapes;
+ int mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_DISABLED;
+
+ const bool generate_collider = node_settings["generate/physics"];
+ if (generate_collider) {
+ mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_MESH_AND_STATIC_COLLIDER;
+ if (node_settings.has("physics/body_type")) {
+ const BodyType body_type = (BodyType)node_settings["physics/body_type"].operator int();
+ switch (body_type) {
+ case BODY_TYPE_STATIC:
+ mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_MESH_AND_STATIC_COLLIDER;
+ break;
+ case BODY_TYPE_DYNAMIC:
+ mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_RIGID_BODY_AND_MESH;
+ break;
+ case BODY_TYPE_AREA:
+ mesh_physics_mode = MeshPhysicsMode::MESH_PHYSICS_AREA_ONLY;
+ break;
+ }
+ }
+ }
+ if (mesh_physics_mode != MeshPhysicsMode::MESH_PHYSICS_DISABLED) {
+ Vector<Ref<Shape3D>> shapes;
if (collision_map.has(m)) {
shapes = collision_map[m];
} else {
- switch (mesh_physics_mode) {
- case MESH_PHYSICS_MESH_AND_STATIC_COLLIDER: {
- _pre_gen_shape_list(m, shapes, false);
- } break;
- case MESH_PHYSICS_RIGID_BODY_AND_MESH: {
- _pre_gen_shape_list(m, shapes, true);
- } break;
- case MESH_PHYSICS_STATIC_COLLIDER_ONLY: {
- _pre_gen_shape_list(m, shapes, false);
- } break;
- case MESH_PHYSICS_AREA_ONLY: {
- _pre_gen_shape_list(m, shapes, true);
- } break;
- }
+ shapes = get_collision_shapes(
+ m->get_mesh(),
+ node_settings);
}
if (shapes.size()) {
@@ -603,13 +617,15 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
case MESH_PHYSICS_MESH_AND_STATIC_COLLIDER: {
StaticBody3D *col = memnew(StaticBody3D);
p_node->add_child(col);
+ col->set_owner(p_node->get_owner());
+ col->set_transform(get_collision_shapes_transform(node_settings));
base = col;
} break;
case MESH_PHYSICS_RIGID_BODY_AND_MESH: {
RigidBody3D *rigid_body = memnew(RigidBody3D);
rigid_body->set_name(p_node->get_name());
p_node->replace_by(rigid_body);
- rigid_body->set_transform(mi->get_transform());
+ rigid_body->set_transform(mi->get_transform() * get_collision_shapes_transform(node_settings));
p_node = rigid_body;
mi->set_transform(Transform3D());
rigid_body->add_child(mi);
@@ -618,7 +634,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
} break;
case MESH_PHYSICS_STATIC_COLLIDER_ONLY: {
StaticBody3D *col = memnew(StaticBody3D);
- col->set_transform(mi->get_transform());
+ col->set_transform(mi->get_transform() * get_collision_shapes_transform(node_settings));
col->set_name(p_node->get_name());
p_node->replace_by(col);
memdelete(p_node);
@@ -627,7 +643,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
} break;
case MESH_PHYSICS_AREA_ONLY: {
Area3D *area = memnew(Area3D);
- area->set_transform(mi->get_transform());
+ area->set_transform(mi->get_transform() * get_collision_shapes_transform(node_settings));
area->set_name(p_node->get_name());
p_node->replace_by(area);
memdelete(p_node);
@@ -927,8 +943,35 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
} break;
case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
- r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/physics", PROPERTY_HINT_ENUM, "Disabled,Mesh + Static Collider,Rigid Body + Mesh,Static Collider Only,Area Only"), 0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate/physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/navmesh", PROPERTY_HINT_ENUM, "Disabled,Mesh + NavMesh,NavMesh Only"), 0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/body_type", PROPERTY_HINT_ENUM, "Static,Dynamic,Area"), 0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/shape_type", PROPERTY_HINT_ENUM, "Decompose Convex,Simple Convex,Trimesh,Box,Sphere,Cylinder,Capsule", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
+
+ // Decomposition
+ Mesh::ConvexDecompositionSettings decomposition_default;
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/advanced", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/precision", PROPERTY_HINT_RANGE, "1,10,1"), 5));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/max_concavity", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.max_concavity));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/symmetry_planes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.symmetry_planes_clipping_bias));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/revolution_axes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.revolution_axes_clipping_bias));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/min_volume_per_convex_hull", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.min_volume_per_convex_hull));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/resolution", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.resolution));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_num_vertices_per_convex_hull", PROPERTY_HINT_RANGE, "5,512,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.max_num_vertices_per_convex_hull));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/plane_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.plane_downsampling));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/convexhull_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.convexhull_downsampling));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/normalize_mesh", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.normalize_mesh));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/mode", PROPERTY_HINT_ENUM, "Voxel,Tetrahedron", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), static_cast<int>(decomposition_default.mode)));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/convexhull_approximation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.convexhull_approximation));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_convex_hulls", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.max_convex_hulls));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/project_hull_vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default.project_hull_vertices));
+
+ // Primitives: Box, Sphere, Cylinder, Capsule.
+ r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3(2.0, 2.0, 2.0)));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/height", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1.0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/radius", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1.0));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3()));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3()));
} break;
case INTERNAL_IMPORT_CATEGORY_MESH: {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
@@ -979,6 +1022,65 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
case INTERNAL_IMPORT_CATEGORY_NODE: {
} break;
case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: {
+ const bool generate_physics =
+ p_options.has("generate/physics") &&
+ p_options["generate/physics"].operator bool();
+
+ if (
+ p_option == "physics/body_type" ||
+ p_option == "physics/shape_type") {
+ // Show if need to generate collisions.
+ return generate_physics;
+ }
+
+ if (p_option.find("decomposition/") >= 0) {
+ // Show if need to generate collisions.
+ if (generate_physics &&
+ // Show if convex is enabled.
+ p_options["physics/shape_type"] == Variant(SHAPE_TYPE_DECOMPOSE_CONVEX)) {
+ if (p_option == "decomposition/advanced") {
+ return true;
+ }
+
+ const bool decomposition_advanced =
+ p_options.has("decomposition/advanced") &&
+ p_options["decomposition/advanced"].operator bool();
+
+ if (p_option == "decomposition/precision") {
+ return !decomposition_advanced;
+ } else {
+ return decomposition_advanced;
+ }
+ }
+
+ return false;
+ }
+
+ if (p_option == "primitive/position" || p_option == "primitive/rotation") {
+ const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int();
+ return generate_physics &&
+ physics_shape >= SHAPE_TYPE_BOX;
+ }
+
+ if (p_option == "primitive/size") {
+ const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int();
+ return generate_physics &&
+ physics_shape == SHAPE_TYPE_BOX;
+ }
+
+ if (p_option == "primitive/radius") {
+ const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int();
+ return generate_physics && (physics_shape == SHAPE_TYPE_SPHERE ||
+ physics_shape == SHAPE_TYPE_CYLINDER ||
+ physics_shape == SHAPE_TYPE_CAPSULE);
+ }
+
+ if (p_option == "primitive/height") {
+ const ShapeType physics_shape = (ShapeType)p_options["physics/shape_type"].operator int();
+ return generate_physics &&
+ (physics_shape == SHAPE_TYPE_CYLINDER ||
+ physics_shape == SHAPE_TYPE_CAPSULE);
+ }
} break;
case INTERNAL_IMPORT_CATEGORY_MESH: {
if (p_option == "save_to_file/path" || p_option == "save_to_file/make_streamable") {
@@ -1015,6 +1117,33 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
return true;
}
+bool ResourceImporterScene::get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const {
+ switch (p_category) {
+ case INTERNAL_IMPORT_CATEGORY_NODE: {
+ } break;
+ case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: {
+ if (
+ p_option == "generate/physics" ||
+ p_option == "physics/shape_type" ||
+ p_option.find("decomposition/") >= 0 ||
+ p_option.find("primitive/") >= 0) {
+ return true;
+ }
+ } break;
+ case INTERNAL_IMPORT_CATEGORY_MESH: {
+ } break;
+ case INTERNAL_IMPORT_CATEGORY_MATERIAL: {
+ } break;
+ case INTERNAL_IMPORT_CATEGORY_ANIMATION: {
+ } break;
+ case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: {
+ } break;
+ default: {
+ }
+ }
+ return false;
+}
+
void ResourceImporterScene::get_import_options(List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_type", PROPERTY_HINT_TYPE_STRING, "Node"), "Node3D"));
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_name"), "Scene Root"));
@@ -1269,7 +1398,7 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m
}
}
-void ResourceImporterScene::_add_shapes(Node *p_node, const List<Ref<Shape3D>> &p_shapes) {
+void ResourceImporterScene::_add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes) {
for (const Ref<Shape3D> &E : p_shapes) {
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(E);
@@ -1310,7 +1439,7 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file) {
return nullptr;
}
- Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> collision_map;
+ Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> collision_map;
_pre_fix_node(scene, scene, collision_map);
@@ -1386,7 +1515,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
}
Set<Ref<EditorSceneImporterMesh>> scanned_meshes;
- Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> collision_map;
+ Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> collision_map;
_pre_fix_node(scene, scene, collision_map);
_post_fix_node(scene, scene, collision_map, scanned_meshes, node_data, material_data, animation_data, fps);
diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h
index 542959be02..e232b715be 100644
--- a/editor/import/resource_importer_scene.h
+++ b/editor/import/resource_importer_scene.h
@@ -63,7 +63,6 @@ public:
IMPORT_FAIL_ON_MISSING_DEPENDENCIES = 4,
IMPORT_GENERATE_TANGENT_ARRAYS = 8,
IMPORT_USE_NAMED_SKIN_BINDS = 16,
-
};
virtual uint32_t get_import_flags() const;
@@ -125,9 +124,25 @@ class ResourceImporterScene : public ResourceImporter {
MESH_OVERRIDE_DISABLE,
};
+ enum BodyType {
+ BODY_TYPE_STATIC,
+ BODY_TYPE_DYNAMIC,
+ BODY_TYPE_AREA
+ };
+
+ enum ShapeType {
+ SHAPE_TYPE_DECOMPOSE_CONVEX,
+ SHAPE_TYPE_SIMPLE_CONVEX,
+ SHAPE_TYPE_TRIMESH,
+ SHAPE_TYPE_BOX,
+ SHAPE_TYPE_SPHERE,
+ SHAPE_TYPE_CYLINDER,
+ SHAPE_TYPE_CAPSULE,
+ };
+
void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner);
void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
- void _add_shapes(Node *p_node, const List<Ref<Shape3D>> &p_shapes);
+ void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes);
public:
static ResourceImporterScene *get_singleton() { return singleton; }
@@ -159,14 +174,15 @@ public:
void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const;
bool get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const;
+ bool get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const;
virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
// Import scenes *after* everything else (such as textures).
virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; }
- Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map);
- Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps);
+ Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map);
+ Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps);
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks);
void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all);
@@ -184,6 +200,12 @@ public:
virtual bool can_import_threaded() const override { return false; }
ResourceImporterScene();
+
+ template <class M>
+ static Vector<Ref<Shape3D>> get_collision_shapes(const Ref<Mesh> &p_mesh, const M &p_options);
+
+ template <class M>
+ static Transform3D get_collision_shapes_transform(const M &p_options);
};
class EditorSceneImporterESCN : public EditorSceneImporter {
@@ -196,4 +218,176 @@ public:
virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) override;
};
+#include "scene/resources/box_shape_3d.h"
+#include "scene/resources/capsule_shape_3d.h"
+#include "scene/resources/cylinder_shape_3d.h"
+#include "scene/resources/sphere_shape_3d.h"
+
+template <class M>
+Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<Mesh> &p_mesh, const M &p_options) {
+ ShapeType generate_shape_type = SHAPE_TYPE_DECOMPOSE_CONVEX;
+ if (p_options.has(SNAME("physics/shape_type"))) {
+ generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
+ }
+
+ if (generate_shape_type == SHAPE_TYPE_DECOMPOSE_CONVEX) {
+ Mesh::ConvexDecompositionSettings decomposition_settings;
+ bool advanced = false;
+ if (p_options.has(SNAME("decomposition/advanced"))) {
+ advanced = p_options[SNAME("decomposition/advanced")];
+ }
+
+ if (advanced) {
+ if (p_options.has(SNAME("decomposition/max_concavity"))) {
+ decomposition_settings.max_concavity = p_options[SNAME("decomposition/max_concavity")];
+ }
+
+ if (p_options.has(SNAME("decomposition/symmetry_planes_clipping_bias"))) {
+ decomposition_settings.symmetry_planes_clipping_bias = p_options[SNAME("decomposition/symmetry_planes_clipping_bias")];
+ }
+
+ if (p_options.has(SNAME("decomposition/revolution_axes_clipping_bias"))) {
+ decomposition_settings.revolution_axes_clipping_bias = p_options[SNAME("decomposition/revolution_axes_clipping_bias")];
+ }
+
+ if (p_options.has(SNAME("decomposition/min_volume_per_convex_hull"))) {
+ decomposition_settings.min_volume_per_convex_hull = p_options[SNAME("decomposition/min_volume_per_convex_hull")];
+ }
+
+ if (p_options.has(SNAME("decomposition/resolution"))) {
+ decomposition_settings.resolution = p_options[SNAME("decomposition/resolution")];
+ }
+
+ if (p_options.has(SNAME("decomposition/max_num_vertices_per_convex_hull"))) {
+ decomposition_settings.max_num_vertices_per_convex_hull = p_options[SNAME("decomposition/max_num_vertices_per_convex_hull")];
+ }
+
+ if (p_options.has(SNAME("decomposition/plane_downsampling"))) {
+ decomposition_settings.plane_downsampling = p_options[SNAME("decomposition/plane_downsampling")];
+ }
+
+ if (p_options.has(SNAME("decomposition/convexhull_downsampling"))) {
+ decomposition_settings.convexhull_downsampling = p_options[SNAME("decomposition/convexhull_downsampling")];
+ }
+
+ if (p_options.has(SNAME("decomposition/normalize_mesh"))) {
+ decomposition_settings.normalize_mesh = p_options[SNAME("decomposition/normalize_mesh")];
+ }
+
+ if (p_options.has(SNAME("decomposition/mode"))) {
+ decomposition_settings.mode = (Mesh::ConvexDecompositionSettings::Mode)p_options[SNAME("decomposition/mode")].operator int();
+ }
+
+ if (p_options.has(SNAME("decomposition/convexhull_approximation"))) {
+ decomposition_settings.convexhull_approximation = p_options[SNAME("decomposition/convexhull_approximation")];
+ }
+
+ if (p_options.has(SNAME("decomposition/max_convex_hulls"))) {
+ decomposition_settings.max_convex_hulls = p_options[SNAME("decomposition/max_convex_hulls")];
+ }
+
+ if (p_options.has(SNAME("decomposition/project_hull_vertices"))) {
+ decomposition_settings.project_hull_vertices = p_options[SNAME("decomposition/project_hull_vertices")];
+ }
+ } else {
+ int precision_level = 5;
+ if (p_options.has(SNAME("decomposition/precision"))) {
+ precision_level = p_options[SNAME("decomposition/precision")];
+ }
+
+ const real_t precision = real_t(precision_level - 1) / 9.0;
+
+ decomposition_settings.max_concavity = Math::lerp(real_t(1.0), real_t(0.001), precision);
+ decomposition_settings.min_volume_per_convex_hull = Math::lerp(real_t(0.01), real_t(0.0001), precision);
+ decomposition_settings.resolution = Math::lerp(10'000, 100'000, precision);
+ decomposition_settings.max_num_vertices_per_convex_hull = Math::lerp(32, 64, precision);
+ decomposition_settings.plane_downsampling = Math::lerp(3, 16, precision);
+ decomposition_settings.convexhull_downsampling = Math::lerp(3, 16, precision);
+ decomposition_settings.max_convex_hulls = Math::lerp(1, 32, precision);
+ }
+
+ return p_mesh->convex_decompose(decomposition_settings);
+ } else if (generate_shape_type == SHAPE_TYPE_SIMPLE_CONVEX) {
+ Vector<Ref<Shape3D>> shapes;
+ shapes.push_back(p_mesh->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false));
+ return shapes;
+ } else if (generate_shape_type == SHAPE_TYPE_TRIMESH) {
+ Vector<Ref<Shape3D>> shapes;
+ shapes.push_back(p_mesh->create_trimesh_shape());
+ return shapes;
+ } else if (generate_shape_type == SHAPE_TYPE_BOX) {
+ Ref<BoxShape3D> box;
+ box.instantiate();
+ if (p_options.has(SNAME("primitive/size"))) {
+ box->set_size(p_options[SNAME("primitive/size")]);
+ }
+
+ Vector<Ref<Shape3D>> shapes;
+ shapes.push_back(box);
+ return shapes;
+
+ } else if (generate_shape_type == SHAPE_TYPE_SPHERE) {
+ Ref<SphereShape3D> sphere;
+ sphere.instantiate();
+ if (p_options.has(SNAME("primitive/radius"))) {
+ sphere->set_radius(p_options[SNAME("primitive/radius")]);
+ }
+
+ Vector<Ref<Shape3D>> shapes;
+ shapes.push_back(sphere);
+ return shapes;
+ } else if (generate_shape_type == SHAPE_TYPE_CYLINDER) {
+ Ref<CylinderShape3D> cylinder;
+ cylinder.instantiate();
+ if (p_options.has(SNAME("primitive/height"))) {
+ cylinder->set_height(p_options[SNAME("primitive/height")]);
+ }
+ if (p_options.has(SNAME("primitive/radius"))) {
+ cylinder->set_radius(p_options[SNAME("primitive/radius")]);
+ }
+
+ Vector<Ref<Shape3D>> shapes;
+ shapes.push_back(cylinder);
+ return shapes;
+ } else if (generate_shape_type == SHAPE_TYPE_CAPSULE) {
+ Ref<CapsuleShape3D> capsule;
+ capsule.instantiate();
+ if (p_options.has(SNAME("primitive/height"))) {
+ capsule->set_height(p_options[SNAME("primitive/height")]);
+ }
+ if (p_options.has(SNAME("primitive/radius"))) {
+ capsule->set_radius(p_options[SNAME("primitive/radius")]);
+ }
+
+ Vector<Ref<Shape3D>> shapes;
+ shapes.push_back(capsule);
+ return shapes;
+ }
+ return Vector<Ref<Shape3D>>();
+}
+
+template <class M>
+Transform3D ResourceImporterScene::get_collision_shapes_transform(const M &p_options) {
+ Transform3D transform;
+
+ ShapeType generate_shape_type = SHAPE_TYPE_DECOMPOSE_CONVEX;
+ if (p_options.has(SNAME("physics/shape_type"))) {
+ generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
+ }
+
+ if (generate_shape_type == SHAPE_TYPE_BOX ||
+ generate_shape_type == SHAPE_TYPE_SPHERE ||
+ generate_shape_type == SHAPE_TYPE_CYLINDER ||
+ generate_shape_type == SHAPE_TYPE_CAPSULE) {
+ if (p_options.has(SNAME("primitive/position"))) {
+ transform.origin = p_options[SNAME("primitive/position")];
+ }
+
+ if (p_options.has(SNAME("primitive/rotation"))) {
+ transform.basis.set_euler((p_options[SNAME("primitive/rotation")].operator Vector3() / 180.0) * Math_PI);
+ }
+ }
+ return transform;
+}
+
#endif // RESOURCEIMPORTERSCENE_H
diff --git a/editor/import/resource_importer_shader_file.cpp b/editor/import/resource_importer_shader_file.cpp
index 4d92490675..c01d8068da 100644
--- a/editor/import/resource_importer_shader_file.cpp
+++ b/editor/import/resource_importer_shader_file.cpp
@@ -78,7 +78,7 @@ static String _include_function(const String &p_path, void *userpointer) {
String *base_path = (String *)userpointer;
String include = p_path;
- if (include.is_rel_path()) {
+ if (include.is_relative_path()) {
include = base_path->plus_file(include);
}
diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp
index daf7b15794..61745cb6ee 100644
--- a/editor/import/resource_importer_texture.cpp
+++ b/editor/import/resource_importer_texture.cpp
@@ -393,7 +393,7 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String
float lossy = p_options["compress/lossy_quality"];
int pack_channels = p_options["compress/channel_pack"];
bool mipmaps = p_options["mipmaps/generate"];
- uint32_t mipmap_limit = int(mipmaps ? int(p_options["mipmaps/limit"]) : int(-1));
+ uint32_t mipmap_limit = mipmaps ? uint32_t(p_options["mipmaps/limit"]) : uint32_t(-1);
bool fix_alpha_border = p_options["process/fix_alpha_border"];
bool premult_alpha = p_options["process/premult_alpha"];
bool normal_map_invert_y = p_options["process/normal_map_invert_y"];
diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp
index 19a8f209bb..4bcb6863fb 100644
--- a/editor/import/scene_import_settings.cpp
+++ b/editor/import/scene_import_settings.cpp
@@ -53,6 +53,11 @@ class SceneImportSettingsData : public Object {
}
current[p_name] = p_value;
+
+ if (ResourceImporterScene::get_singleton()->get_internal_option_update_view_required(category, p_name, current)) {
+ SceneImportSettings::get_singleton()->update_view();
+ }
+
return true;
}
return false;
@@ -317,6 +322,13 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) {
if (mesh_node && mesh_node->get_mesh().is_valid()) {
_fill_mesh(scene_tree, mesh_node->get_mesh(), item);
+ // Add the collider view.
+ MeshInstance3D *collider_view = memnew(MeshInstance3D);
+ collider_view->set_name("collider_view");
+ collider_view->set_visible(false);
+ mesh_node->add_child(collider_view);
+ collider_view->set_owner(mesh_node);
+
Transform3D accum_xform;
Node3D *base = mesh_node;
while (base) {
@@ -346,6 +358,54 @@ void SceneImportSettings::_update_scene() {
_fill_scene(scene, nullptr);
}
+void SceneImportSettings::_update_view_gizmos() {
+ for (const KeyValue<String, NodeData> &e : node_map) {
+ bool generate_collider = false;
+ if (e.value.settings.has(SNAME("generate/physics"))) {
+ generate_collider = e.value.settings[SNAME("generate/physics")];
+ }
+
+ MeshInstance3D *mesh_node = Object::cast_to<MeshInstance3D>(e.value.node);
+ if (mesh_node == nullptr || mesh_node->get_mesh().is_null()) {
+ // Nothing to do
+ continue;
+ }
+
+ MeshInstance3D *collider_view = static_cast<MeshInstance3D *>(mesh_node->find_node("collider_view"));
+ CRASH_COND_MSG(collider_view == nullptr, "This is unreachable, since the collider view is always created even when the collision is not used! If this is triggered there is a bug on the function `_fill_scene`.");
+
+ collider_view->set_visible(generate_collider);
+ if (generate_collider) {
+ // This collider_view doesn't have a mesh so we need to generate a new one.
+
+ // Generate the mesh collider.
+ Vector<Ref<Shape3D>> shapes = ResourceImporterScene::get_collision_shapes(mesh_node->get_mesh(), e.value.settings);
+ const Transform3D transform = ResourceImporterScene::get_collision_shapes_transform(e.value.settings);
+
+ Ref<ArrayMesh> collider_view_mesh;
+ collider_view_mesh.instantiate();
+ for (Ref<Shape3D> shape : shapes) {
+ Ref<ArrayMesh> debug_shape_mesh;
+ if (shape.is_valid()) {
+ debug_shape_mesh = shape->get_debug_mesh();
+ }
+ if (debug_shape_mesh.is_valid()) {
+ collider_view_mesh->add_surface_from_arrays(
+ debug_shape_mesh->surface_get_primitive_type(0),
+ debug_shape_mesh->surface_get_arrays(0));
+
+ collider_view_mesh->surface_set_material(
+ collider_view_mesh->get_surface_count() - 1,
+ collider_mat);
+ }
+ }
+
+ collider_view->set_mesh(collider_view_mesh);
+ collider_view->set_transform(transform);
+ }
+ }
+}
+
void SceneImportSettings::_update_camera() {
AABB camera_aabb;
@@ -404,11 +464,16 @@ void SceneImportSettings::_load_default_subresource_settings(Map<StringName, Var
}
}
+void SceneImportSettings::update_view() {
+ _update_view_gizmos();
+}
+
void SceneImportSettings::open_settings(const String &p_path) {
if (scene) {
memdelete(scene);
scene = nullptr;
}
+ scene_import_settings_data->settings = nullptr;
scene = ResourceImporterScene::get_singleton()->pre_import(p_path);
if (scene == nullptr) {
EditorNode::get_singleton()->show_warning(TTR("Error opening scene"));
@@ -463,6 +528,7 @@ void SceneImportSettings::open_settings(const String &p_path) {
}
popup_centered_ratio();
+ _update_view_gizmos();
_update_camera();
set_title(vformat(TTR("Advanced Import Settings for '%s'"), base_path.get_file()));
@@ -629,6 +695,7 @@ void SceneImportSettings::_material_tree_selected() {
_select(material_tree, type, import_id);
}
+
void SceneImportSettings::_mesh_tree_selected() {
if (selecting) {
return;
@@ -640,6 +707,7 @@ void SceneImportSettings::_mesh_tree_selected() {
_select(mesh_tree, type, import_id);
}
+
void SceneImportSettings::_scene_tree_selected() {
if (selecting) {
return;
@@ -1144,6 +1212,12 @@ SceneImportSettings::SceneImportSettings() {
material_preview.instantiate();
}
+ {
+ collider_mat.instantiate();
+ collider_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ collider_mat->set_albedo(Color(0.5, 0.5, 1.0));
+ }
+
inspector = memnew(EditorInspector);
inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0));
diff --git a/editor/import/scene_import_settings.h b/editor/import/scene_import_settings.h
index ddcf4a6d5d..c7c94af493 100644
--- a/editor/import/scene_import_settings.h
+++ b/editor/import/scene_import_settings.h
@@ -84,6 +84,8 @@ class SceneImportSettings : public ConfirmationDialog {
MeshInstance3D *mesh_preview;
Ref<SphereMesh> material_preview;
+ Ref<StandardMaterial3D> collider_mat;
+
float cam_rot_x;
float cam_rot_y;
float cam_zoom;
@@ -145,6 +147,7 @@ class SceneImportSettings : public ConfirmationDialog {
bool selecting = false;
+ void _update_view_gizmos();
void _update_camera();
void _select(Tree *p_from, String p_type, String p_id);
void _material_tree_selected();
@@ -190,6 +193,7 @@ protected:
void _notification(int p_what);
public:
+ void update_view();
void open_settings(const String &p_path);
static SceneImportSettings *get_singleton();
SceneImportSettings();
diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp
index 06f373c54f..5e6dd08e79 100644
--- a/editor/import/scene_importer_mesh.cpp
+++ b/editor/import/scene_importer_mesh.cpp
@@ -33,6 +33,8 @@
#include "core/math/math_defs.h"
#include "scene/resources/surface_tool.h"
+#include <cstdint>
+
void EditorSceneImporterMesh::add_blend_shape(const String &p_name) {
ERR_FAIL_COND(surfaces.size() > 0);
blend_shapes.push_back(p_name);
@@ -55,13 +57,14 @@ Mesh::BlendShapeMode EditorSceneImporterMesh::get_blend_shape_mode() const {
return blend_shape_mode;
}
-void EditorSceneImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name) {
+void EditorSceneImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes, const Dictionary &p_lods, const Ref<Material> &p_material, const String &p_name, const uint32_t p_flags) {
ERR_FAIL_COND(p_blend_shapes.size() != blend_shapes.size());
ERR_FAIL_COND(p_arrays.size() != Mesh::ARRAY_MAX);
Surface s;
s.primitive = p_primitive;
s.arrays = p_arrays;
s.name = p_name;
+ s.flags = p_flags;
Vector<Vector3> vertex_array = p_arrays[Mesh::ARRAY_VERTEX];
int vertex_count = vertex_array.size();
@@ -138,6 +141,11 @@ float EditorSceneImporterMesh::get_surface_lod_size(int p_surface, int p_lod) co
return surfaces[p_surface].lods[p_lod].distance;
}
+uint32_t EditorSceneImporterMesh::get_surface_format(int p_surface) const {
+ ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0);
+ return surfaces[p_surface].flags;
+}
+
Ref<Material> EditorSceneImporterMesh::get_surface_material(int p_surface) const {
ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Ref<Material>());
return surfaces[p_surface].material;
@@ -283,7 +291,7 @@ Ref<ArrayMesh> EditorSceneImporterMesh::get_mesh(const Ref<ArrayMesh> &p_base) {
}
}
- mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods);
+ mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods, surfaces[i].flags);
if (surfaces[i].material.is_valid()) {
mesh->surface_set_material(mesh->get_surface_count() - 1, surfaces[i].material);
}
@@ -398,7 +406,7 @@ void EditorSceneImporterMesh::create_shadow_mesh() {
}
}
- shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name);
+ shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref<Material>(), surfaces[i].name, surfaces[i].flags);
}
}
@@ -436,7 +444,11 @@ void EditorSceneImporterMesh::_set_data(const Dictionary &p_data) {
if (s.has("material")) {
material = s["material"];
}
- add_surface(prim, arr, blend_shapes, lods, material, name);
+ uint32_t flags = 0;
+ if (s.has("flags")) {
+ flags = s["flags"];
+ }
+ add_surface(prim, arr, blend_shapes, lods, material, name, flags);
}
}
}
@@ -473,6 +485,10 @@ Dictionary EditorSceneImporterMesh::_get_data() const {
d["name"] = surfaces[i].name;
}
+ if (surfaces[i].flags != 0) {
+ d["flags"] = surfaces[i].flags;
+ }
+
surface_arr.push_back(d);
}
data["surfaces"] = surface_arr;
@@ -508,36 +524,47 @@ Vector<Face3> EditorSceneImporterMesh::get_faces() const {
return faces;
}
-Vector<Ref<Shape3D>> EditorSceneImporterMesh::convex_decompose() const {
- ERR_FAIL_COND_V(!Mesh::convex_composition_function, Vector<Ref<Shape3D>>());
+Vector<Ref<Shape3D>> EditorSceneImporterMesh::convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const {
+ ERR_FAIL_COND_V(!Mesh::convex_decomposition_function, Vector<Ref<Shape3D>>());
const Vector<Face3> faces = get_faces();
+ int face_count = faces.size();
+
+ Vector<Vector3> vertices;
+ uint32_t vertex_count = 0;
+ vertices.resize(face_count * 3);
+ Vector<uint32_t> indices;
+ indices.resize(face_count * 3);
+ {
+ Map<Vector3, uint32_t> vertex_map;
+ Vector3 *vertex_w = vertices.ptrw();
+ uint32_t *index_w = indices.ptrw();
+ for (int i = 0; i < face_count; i++) {
+ for (int j = 0; j < 3; j++) {
+ const Vector3 &vertex = faces[i].vertex[j];
+ Map<Vector3, uint32_t>::Element *found_vertex = vertex_map.find(vertex);
+ uint32_t index;
+ if (found_vertex) {
+ index = found_vertex->get();
+ } else {
+ index = ++vertex_count;
+ vertex_map[vertex] = index;
+ vertex_w[index] = vertex;
+ }
+ index_w[i * 3 + j] = index;
+ }
+ }
+ }
+ vertices.resize(vertex_count);
- Vector<Vector<Face3>> decomposed = Mesh::convex_composition_function(faces, -1);
+ Vector<Vector<Vector3>> decomposed = Mesh::convex_decomposition_function((real_t *)vertices.ptr(), vertex_count, indices.ptr(), face_count, p_settings, nullptr);
Vector<Ref<Shape3D>> ret;
for (int i = 0; i < decomposed.size(); i++) {
- Set<Vector3> points;
- for (int j = 0; j < decomposed[i].size(); j++) {
- points.insert(decomposed[i][j].vertex[0]);
- points.insert(decomposed[i][j].vertex[1]);
- points.insert(decomposed[i][j].vertex[2]);
- }
-
- Vector<Vector3> convex_points;
- convex_points.resize(points.size());
- {
- Vector3 *w = convex_points.ptrw();
- int idx = 0;
- for (Set<Vector3>::Element *E = points.front(); E; E = E->next()) {
- w[idx++] = E->get();
- }
- }
-
Ref<ConvexPolygonShape3D> shape;
shape.instantiate();
- shape->set_points(convex_points);
+ shape->set_points(decomposed[i]);
ret.push_back(shape);
}
@@ -833,7 +860,7 @@ void EditorSceneImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &EditorSceneImporterMesh::set_blend_shape_mode);
ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &EditorSceneImporterMesh::get_blend_shape_mode);
- ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name"), &EditorSceneImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String()));
+ ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name", "flags"), &EditorSceneImporterMesh::add_surface, DEFVAL(Array()), DEFVAL(Dictionary()), DEFVAL(Ref<Material>()), DEFVAL(String()), DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_surface_count"), &EditorSceneImporterMesh::get_surface_count);
ClassDB::bind_method(D_METHOD("get_surface_primitive_type", "surface_idx"), &EditorSceneImporterMesh::get_surface_primitive_type);
@@ -844,6 +871,7 @@ void EditorSceneImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_surface_lod_size", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_size);
ClassDB::bind_method(D_METHOD("get_surface_lod_indices", "surface_idx", "lod_idx"), &EditorSceneImporterMesh::get_surface_lod_indices);
ClassDB::bind_method(D_METHOD("get_surface_material", "surface_idx"), &EditorSceneImporterMesh::get_surface_material);
+ ClassDB::bind_method(D_METHOD("get_surface_format", "surface_idx"), &EditorSceneImporterMesh::get_surface_format);
ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &EditorSceneImporterMesh::set_surface_name);
ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &EditorSceneImporterMesh::set_surface_material);
diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h
index e57e479d8e..d32b1fdf74 100644
--- a/editor/import/scene_importer_mesh.h
+++ b/editor/import/scene_importer_mesh.h
@@ -36,6 +36,9 @@
#include "scene/resources/convex_polygon_shape_3d.h"
#include "scene/resources/mesh.h"
#include "scene/resources/navigation_mesh.h"
+
+#include <cstdint>
+
// The following classes are used by importers instead of ArrayMesh and MeshInstance3D
// so the data is not registered (hence, quality loss), importing happens faster and
// its easier to modify before saving
@@ -57,6 +60,7 @@ class EditorSceneImporterMesh : public Resource {
Vector<LOD> lods;
Ref<Material> material;
String name;
+ uint32_t flags = 0;
};
Vector<Surface> surfaces;
Vector<String> blend_shapes;
@@ -80,7 +84,7 @@ public:
int get_blend_shape_count() const;
String get_blend_shape_name(int p_blend_shape) const;
- void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String());
+ void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint32_t p_flags = 0);
int get_surface_count() const;
void set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode);
@@ -95,6 +99,7 @@ public:
Vector<int> get_surface_lod_indices(int p_surface, int p_lod) const;
float get_surface_lod_size(int p_surface, int p_lod) const;
Ref<Material> get_surface_material(int p_surface) const;
+ uint32_t get_surface_format(int p_surface) const;
void set_surface_material(int p_surface, const Ref<Material> &p_material);
@@ -104,7 +109,7 @@ public:
Ref<EditorSceneImporterMesh> get_shadow_mesh() const;
Vector<Face3> get_faces() const;
- Vector<Ref<Shape3D>> convex_decompose() const;
+ Vector<Ref<Shape3D>> convex_decompose(const Mesh::ConvexDecompositionSettings &p_settings) const;
Ref<Shape3D> create_trimesh_shape() const;
Ref<NavigationMesh> create_navigation_mesh();
Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache);
diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp
index fd4a4334fc..1e707c1a60 100644
--- a/editor/multi_node_edit.cpp
+++ b/editor/multi_node_edit.cpp
@@ -49,6 +49,11 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value,
name = "script";
}
+ Node *node_path_target = nullptr;
+ if (p_value.get_type() == Variant::NODE_PATH && p_value != NodePath()) {
+ node_path_target = es->get_node(p_value);
+ }
+
UndoRedo *ur = EditorNode::get_undo_redo();
ur->create_action(TTR("MultiNode Set") + " " + String(name), UndoRedo::MERGE_ENDS);
@@ -63,9 +68,11 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value,
}
if (p_value.get_type() == Variant::NODE_PATH) {
- Node *tonode = n->get_node(p_value);
- NodePath p_path = n->get_path_to(tonode);
- ur->add_do_property(n, name, p_path);
+ NodePath path;
+ if (node_path_target) {
+ path = n->get_path_to(node_path_target);
+ }
+ ur->add_do_property(n, name, path);
} else {
Variant new_value;
if (p_field == "") {
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 830b010d01..18b4966f80 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -904,7 +904,7 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
}
}
-void AnimationPlayerEditor::forward_canvas_force_draw_over_viewport(Control *p_overlay) {
+void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) {
if (!onion.can_overlay) {
return;
}
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index be80b7f4e3..0a514d3ff1 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -238,7 +238,7 @@ public:
void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; }
void edit(AnimationPlayer *p_player);
- void forward_canvas_force_draw_over_viewport(Control *p_overlay);
+ void forward_force_draw_over_viewport(Control *p_overlay);
AnimationPlayerEditor(EditorNode *p_editor, AnimationPlayerEditorPlugin *p_plugin);
};
@@ -262,7 +262,8 @@ public:
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
- virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_canvas_force_draw_over_viewport(p_overlay); }
+ virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); }
+ virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); }
AnimationPlayerEditorPlugin(EditorNode *p_node);
~AnimationPlayerEditorPlugin();
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index ec3f899e78..f11e51960c 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -4195,6 +4195,7 @@ void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {
p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
if (p_zoom == zoom) {
+ zoom_widget->set_zoom(p_zoom);
return;
}
@@ -5203,7 +5204,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
snap_rotation = false;
snap_scale = false;
snap_relative = false;
- snap_pixel = false;
+ // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings.
+ // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates.
+ snap_pixel = true;
snap_target[0] = SNAP_TARGET_NONE;
snap_target[1] = SNAP_TARGET_NONE;
diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp
index c2684305ef..bfcc293625 100644
--- a/editor/plugins/collision_shape_2d_editor_plugin.cpp
+++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp
@@ -38,6 +38,7 @@
#include "scene/resources/convex_polygon_shape_2d.h"
#include "scene/resources/rectangle_shape_2d.h"
#include "scene/resources/segment_shape_2d.h"
+#include "scene/resources/separation_ray_shape_2d.h"
#include "scene/resources/world_margin_shape_2d.h"
void CollisionShape2DEditor::_node_removed(Node *p_node) {
@@ -80,6 +81,15 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const {
} break;
+ case SEPARATION_RAY_SHAPE: {
+ Ref<SeparationRayShape2D> ray = node->get_shape();
+
+ if (idx == 0) {
+ return ray->get_length();
+ }
+
+ } break;
+
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> rect = node->get_shape();
@@ -152,6 +162,15 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
} break;
+ case SEPARATION_RAY_SHAPE: {
+ Ref<SeparationRayShape2D> ray = node->get_shape();
+
+ ray->set_length(Math::abs(p_point.y));
+
+ canvas_item_editor->update_viewport();
+
+ } break;
+
case RECTANGLE_SHAPE: {
if (idx < 8) {
Ref<RectangleShape2D> rect = node->get_shape();
@@ -253,6 +272,16 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
} break;
+ case SEPARATION_RAY_SHAPE: {
+ Ref<SeparationRayShape2D> ray = node->get_shape();
+
+ undo_redo->add_do_method(ray.ptr(), "set_length", ray->get_length());
+ undo_redo->add_do_method(canvas_item_editor, "update_viewport");
+ undo_redo->add_undo_method(ray.ptr(), "set_length", p_org);
+ undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
+
+ } break;
+
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> rect = node->get_shape();
@@ -394,6 +423,8 @@ void CollisionShape2DEditor::_get_current_shape_type() {
shape_type = CONVEX_POLYGON_SHAPE;
} else if (Object::cast_to<WorldMarginShape2D>(*s)) {
shape_type = WORLD_MARGIN_SHAPE;
+ } else if (Object::cast_to<SeparationRayShape2D>(*s)) {
+ shape_type = SEPARATION_RAY_SHAPE;
} else if (Object::cast_to<RectangleShape2D>(*s)) {
shape_type = RECTANGLE_SHAPE;
} else if (Object::cast_to<SegmentShape2D>(*s)) {
@@ -471,6 +502,16 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla
} break;
+ case SEPARATION_RAY_SHAPE: {
+ Ref<SeparationRayShape2D> shape = node->get_shape();
+
+ handles.resize(1);
+ handles.write[0] = Point2(0, shape->get_length());
+
+ p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
+
+ } break;
+
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> shape = node->get_shape();
diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h
index 056e1b5b7d..421e674df8 100644
--- a/editor/plugins/collision_shape_2d_editor_plugin.h
+++ b/editor/plugins/collision_shape_2d_editor_plugin.h
@@ -47,6 +47,7 @@ class CollisionShape2DEditor : public Control {
CONCAVE_POLYGON_SHAPE,
CONVEX_POLYGON_SHAPE,
WORLD_MARGIN_SHAPE,
+ SEPARATION_RAY_SHAPE,
RECTANGLE_SHAPE,
SEGMENT_SHAPE
};
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 95f68d5f7f..415832ab3b 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -826,55 +826,6 @@ bool EditorFontPreviewPlugin::handles(const String &p_type) const {
return ClassDB::is_parent_class(p_type, "FontData") || ClassDB::is_parent_class(p_type, "Font");
}
-struct FSample {
- String script;
- String sample;
-};
-
-static FSample _samples[] = {
- { "hani", U"漢字" },
- { "armn", U"Աբ" },
- { "copt", U"Αα" },
- { "cyrl", U"Аб" },
- { "grek", U"Αα" },
- { "hebr", U"אב" },
- { "arab", U"اب" },
- { "syrc", U"ܐܒ" },
- { "thaa", U"ހށ" },
- { "deva", U"आ" },
- { "beng", U"আ" },
- { "guru", U"ਆ" },
- { "gujr", U"આ" },
- { "orya", U"ଆ" },
- { "taml", U"ஆ" },
- { "telu", U"ఆ" },
- { "knda", U"ಆ" },
- { "mylm", U"ആ" },
- { "sinh", U"ආ" },
- { "thai", U"กิ" },
- { "laoo", U"ກິ" },
- { "tibt", U"ༀ" },
- { "mymr", U"က" },
- { "geor", U"Ⴀა" },
- { "hang", U"한글" },
- { "ethi", U"ሀ" },
- { "cher", U"Ꭳ" },
- { "cans", U"ᐁ" },
- { "ogam", U"ᚁ" },
- { "runr", U"ᚠ" },
- { "tglg", U"ᜀ" },
- { "hano", U"ᜠ" },
- { "buhd", U"ᝀ" },
- { "tagb", U"ᝠ" },
- { "khmr", U"ក" },
- { "mong", U"ᠠ" },
- { "limb", U"ᤁ" },
- { "tale", U"ᥐ" },
- { "latn", U"Ab" },
- { "zyyy", U"😀" },
- { "", U"" }
-};
-
Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const {
RES res = ResourceLoader::load(p_path);
Ref<Font> sampled_font;
@@ -886,15 +837,15 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path,
}
String sample;
- for (int j = 0; j < sampled_font->get_data_count(); j++) {
- for (int i = 0; _samples[i].script != String(); i++) {
- if (sampled_font->get_data(j)->is_script_supported(_samples[i].script)) {
- if (sampled_font->get_data(j)->has_char(_samples[i].sample[0])) {
- sample += _samples[i].sample;
- }
- }
+ static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";
+ for (int i = 0; i < sample_base.length(); i++) {
+ if (sampled_font->has_char(sample_base[i])) {
+ sample += sample_base[i];
}
}
+ if (sample.is_empty()) {
+ sample = sampled_font->get_supported_chars().substr(0, 6);
+ }
Vector2 size = sampled_font->get_string_size(sample, 50);
Vector2 pos;
diff --git a/editor/plugins/font_editor_plugin.cpp b/editor/plugins/font_editor_plugin.cpp
index 22c9cc9ab1..52fb5b69ea 100644
--- a/editor/plugins/font_editor_plugin.cpp
+++ b/editor/plugins/font_editor_plugin.cpp
@@ -50,70 +50,24 @@ Size2 FontDataPreview::get_minimum_size() const {
return Vector2(64, 64) * EDSCALE;
}
-struct FSample {
- String script;
- String sample;
-};
-
-static FSample _samples[] = {
- { "hani", U"漢字" },
- { "armn", U"Աբ" },
- { "copt", U"Αα" },
- { "cyrl", U"Аб" },
- { "grek", U"Αα" },
- { "hebr", U"אב" },
- { "arab", U"اب" },
- { "syrc", U"ܐܒ" },
- { "thaa", U"ހށ" },
- { "deva", U"आ" },
- { "beng", U"আ" },
- { "guru", U"ਆ" },
- { "gujr", U"આ" },
- { "orya", U"ଆ" },
- { "taml", U"ஆ" },
- { "telu", U"ఆ" },
- { "knda", U"ಆ" },
- { "mylm", U"ആ" },
- { "sinh", U"ආ" },
- { "thai", U"กิ" },
- { "laoo", U"ກິ" },
- { "tibt", U"ༀ" },
- { "mymr", U"က" },
- { "geor", U"Ⴀა" },
- { "hang", U"한글" },
- { "ethi", U"ሀ" },
- { "cher", U"Ꭳ" },
- { "cans", U"ᐁ" },
- { "ogam", U"ᚁ" },
- { "runr", U"ᚠ" },
- { "tglg", U"ᜀ" },
- { "hano", U"ᜠ" },
- { "buhd", U"ᝀ" },
- { "tagb", U"ᝠ" },
- { "khmr", U"ក" },
- { "mong", U"ᠠ" },
- { "limb", U"ᤁ" },
- { "tale", U"ᥐ" },
- { "latn", U"Ab" },
- { "zyyy", U"😀" },
- { "", U"" }
-};
-
void FontDataPreview::set_data(const Ref<FontData> &p_data) {
Ref<Font> f = memnew(Font);
f->add_data(p_data);
line->clear();
-
- String sample;
- for (int i = 0; _samples[i].script != String(); i++) {
- if (p_data->is_script_supported(_samples[i].script)) {
- if (p_data->has_char(_samples[i].sample[0])) {
- sample += _samples[i].sample;
+ if (p_data.is_valid()) {
+ String sample;
+ static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";
+ for (int i = 0; i < sample_base.length(); i++) {
+ if (p_data->has_char(sample_base[i])) {
+ sample += sample_base[i];
}
}
+ if (sample.is_empty()) {
+ sample = p_data->get_supported_chars().substr(0, 6);
+ }
+ line->add_string(sample, f, 72);
}
- line->add_string(sample, f, 72);
update();
}
@@ -124,159 +78,6 @@ FontDataPreview::FontDataPreview() {
/*************************************************************************/
-void FontDataEditor::_notification(int p_what) {
- if (p_what == NOTIFICATION_SORT_CHILDREN) {
- int split_width = get_name_split_ratio() * get_size().width;
- button->set_size(Size2(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))->get_width(), get_size().height));
- if (is_layout_rtl()) {
- if (le != nullptr) {
- fit_child_in_rect(le, Rect2(Vector2(split_width, 0), Size2(split_width, get_size().height)));
- }
- fit_child_in_rect(chk, Rect2(Vector2(split_width - chk->get_size().x, 0), Size2(chk->get_size().x, get_size().height)));
- fit_child_in_rect(button, Rect2(Vector2(0, 0), Size2(button->get_size().width, get_size().height)));
- } else {
- if (le != nullptr) {
- fit_child_in_rect(le, Rect2(Vector2(0, 0), Size2(split_width, get_size().height)));
- }
- fit_child_in_rect(chk, Rect2(Vector2(split_width, 0), Size2(chk->get_size().x, get_size().height)));
- fit_child_in_rect(button, Rect2(Vector2(get_size().width - button->get_size().width, 0), Size2(button->get_size().width, get_size().height)));
- }
- update();
- }
- if (p_what == NOTIFICATION_DRAW) {
- int split_width = get_name_split_ratio() * get_size().width;
- Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor"));
- if (is_layout_rtl()) {
- draw_rect(Rect2(Vector2(0, 0), Size2(split_width, get_size().height)), dark_color);
- } else {
- draw_rect(Rect2(Vector2(split_width, 0), Size2(split_width, get_size().height)), dark_color);
- }
- }
- if (p_what == NOTIFICATION_THEME_CHANGED) {
- if (le != nullptr) {
- button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
- } else {
- button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
- }
- queue_sort();
- }
- if (p_what == NOTIFICATION_RESIZED) {
- queue_sort();
- }
-}
-
-void FontDataEditor::update_property() {
- if (le == nullptr) {
- bool c = get_edited_object()->get(get_edited_property());
- chk->set_pressed(c);
- chk->set_disabled(is_read_only());
- }
-}
-
-Size2 FontDataEditor::get_minimum_size() const {
- return Size2(0, 60);
-}
-
-void FontDataEditor::_bind_methods() {
-}
-
-void FontDataEditor::init_lang_add() {
- le = memnew(LineEdit);
- le->set_placeholder("Language code");
- le->set_custom_minimum_size(Size2(get_size().width / 2, 0));
- le->set_editable(true);
- add_child(le);
-
- button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
- button->connect("pressed", callable_mp(this, &FontDataEditor::add_lang));
-}
-
-void FontDataEditor::init_lang_edit() {
- button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
- button->connect("pressed", callable_mp(this, &FontDataEditor::remove_lang));
- chk->connect("toggled", callable_mp(this, &FontDataEditor::toggle_lang));
-}
-
-void FontDataEditor::init_script_add() {
- le = memnew(LineEdit);
- le->set_placeholder("Script code");
- le->set_custom_minimum_size(Size2(get_size().width / 2, 0));
- le->set_editable(true);
- add_child(le);
-
- button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
- button->connect("pressed", callable_mp(this, &FontDataEditor::add_script));
-}
-
-void FontDataEditor::init_script_edit() {
- button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
- button->connect("pressed", callable_mp(this, &FontDataEditor::remove_script));
- chk->connect("toggled", callable_mp(this, &FontDataEditor::toggle_script));
-}
-
-void FontDataEditor::add_lang() {
- FontData *fd = Object::cast_to<FontData>(get_edited_object());
- if (fd != nullptr && !le->get_text().is_empty()) {
- fd->set_language_support_override(le->get_text(), chk->is_pressed());
- le->set_text("");
- chk->set_pressed(false);
- }
-}
-
-void FontDataEditor::add_script() {
- FontData *fd = Object::cast_to<FontData>(get_edited_object());
- if (fd != nullptr && le->get_text().length() == 4) {
- fd->set_script_support_override(le->get_text(), chk->is_pressed());
- le->set_text("");
- chk->set_pressed(false);
- }
-}
-
-void FontDataEditor::toggle_lang(bool p_pressed) {
- FontData *fd = Object::cast_to<FontData>(get_edited_object());
- if (fd != nullptr) {
- String lang = String(get_edited_property()).replace("language_support_override/", "");
- fd->set_language_support_override(lang, p_pressed);
- }
-}
-
-void FontDataEditor::toggle_script(bool p_pressed) {
- FontData *fd = Object::cast_to<FontData>(get_edited_object());
- if (fd != nullptr) {
- String script = String(get_edited_property()).replace("script_support_override/", "");
- fd->set_script_support_override(script, p_pressed);
- }
-}
-
-void FontDataEditor::remove_lang() {
- FontData *fd = Object::cast_to<FontData>(get_edited_object());
- if (fd != nullptr) {
- String lang = String(get_edited_property()).replace("language_support_override/", "");
- fd->remove_language_support_override(lang);
- }
-}
-
-void FontDataEditor::remove_script() {
- FontData *fd = Object::cast_to<FontData>(get_edited_object());
- if (fd != nullptr) {
- String script = String(get_edited_property()).replace("script_support_override/", "");
- fd->remove_script_support_override(script);
- }
-}
-
-FontDataEditor::FontDataEditor() {
- chk = memnew(CheckBox);
- chk->set_text(TTR("On"));
- chk->set_flat(true);
- add_child(chk);
-
- button = memnew(Button);
- button->set_flat(true);
- add_child(button);
-}
-
-/*************************************************************************/
-
bool EditorInspectorPluginFont::can_handle(Object *p_object) {
return Object::cast_to<FontData>(p_object) != nullptr;
}
@@ -291,34 +92,6 @@ void EditorInspectorPluginFont::parse_begin(Object *p_object) {
}
bool EditorInspectorPluginFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) {
- if (p_path.begins_with("language_support_override/") && p_object->is_class("FontData")) {
- String lang = p_path.replace("language_support_override/", "");
-
- FontDataEditor *editor = memnew(FontDataEditor);
- if (lang != "_new") {
- editor->init_lang_edit();
- } else {
- editor->init_lang_add();
- }
- add_property_editor(p_path, editor);
-
- return true;
- }
-
- if (p_path.begins_with("script_support_override/") && p_object->is_class("FontData")) {
- String script = p_path.replace("script_support_override/", "");
-
- FontDataEditor *editor = memnew(FontDataEditor);
- if (script != "_new") {
- editor->init_script_edit();
- } else {
- editor->init_script_add();
- }
- add_property_editor(p_path, editor);
-
- return true;
- }
-
return false;
}
diff --git a/editor/plugins/font_editor_plugin.h b/editor/plugins/font_editor_plugin.h
index 71464003a0..3530815872 100644
--- a/editor/plugins/font_editor_plugin.h
+++ b/editor/plugins/font_editor_plugin.h
@@ -55,39 +55,6 @@ public:
/*************************************************************************/
-class FontDataEditor : public EditorProperty {
- GDCLASS(FontDataEditor, EditorProperty);
-
- LineEdit *le = nullptr;
- CheckBox *chk = nullptr;
- Button *button = nullptr;
-
- void toggle_lang(bool p_pressed);
- void toggle_script(bool p_pressed);
- void add_lang();
- void add_script();
- void remove_lang();
- void remove_script();
-
-protected:
- void _notification(int p_what);
-
- static void _bind_methods();
-
-public:
- virtual Size2 get_minimum_size() const override;
- virtual void update_property() override;
-
- void init_lang_add();
- void init_lang_edit();
- void init_script_add();
- void init_script_edit();
-
- FontDataEditor();
-};
-
-/*************************************************************************/
-
class EditorInspectorPluginFont : public EditorInspectorPlugin {
GDCLASS(EditorInspectorPluginFont, EditorInspectorPlugin);
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index 9a2b222f21..574d3ef27e 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -202,7 +202,8 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
return;
}
- Vector<Ref<Shape3D>> shapes = mesh->convex_decompose();
+ Mesh::ConvexDecompositionSettings settings;
+ Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(settings);
if (!shapes.size()) {
err_dialog->set_text(TTR("Couldn't create any collision shapes."));
diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp
index b3f92c9d95..18e7480287 100644
--- a/editor/plugins/mesh_library_editor_plugin.cpp
+++ b/editor/plugins/mesh_library_editor_plugin.cpp
@@ -47,23 +47,25 @@ void MeshLibraryEditor::edit(const Ref<MeshLibrary> &p_mesh_library) {
}
}
-void MeshLibraryEditor::_menu_confirm() {
+void MeshLibraryEditor::_menu_remove_confirm() {
switch (option) {
case MENU_OPTION_REMOVE_ITEM: {
mesh_library->remove_item(to_erase);
} break;
- case MENU_OPTION_UPDATE_FROM_SCENE: {
- String existing = mesh_library->get_meta("_editor_source_scene");
- ERR_FAIL_COND(existing == "");
- _import_scene_cbk(existing);
-
- } break;
default: {
};
}
}
-void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge) {
+void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) {
+ cd_update->hide();
+ apply_xforms = p_apply_xforms;
+ String existing = mesh_library->get_meta("_editor_source_scene");
+ ERR_FAIL_COND(existing == "");
+ _import_scene_cbk(existing);
+}
+
+void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms) {
if (!p_merge) {
p_library->clear();
}
@@ -108,6 +110,13 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library,
}
p_library->set_item_mesh(id, mesh);
+
+ if (p_apply_xforms) {
+ p_library->set_item_mesh_transform(id, mi->get_transform());
+ } else {
+ p_library->set_item_mesh_transform(id, Transform3D());
+ }
+
mesh_instances[id] = mi;
Vector<MeshLibrary::ShapeData> collisions;
@@ -197,15 +206,16 @@ void MeshLibraryEditor::_import_scene_cbk(const String &p_str) {
ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'.");
- _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE);
+ _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE, apply_xforms);
memdelete(scene);
mesh_library->set_meta("_editor_source_scene", p_str);
+
menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false);
}
-Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge) {
- _import_scene(p_base_scene, ml, p_merge);
+Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge, bool p_apply_xforms) {
+ _import_scene(p_base_scene, ml, p_merge, p_apply_xforms);
return OK;
}
@@ -219,16 +229,21 @@ void MeshLibraryEditor::_menu_cbk(int p_option) {
String p = editor->get_inspector()->get_selected_path();
if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) {
to_erase = p.get_slice("/", 3).to_int();
- cd->set_text(vformat(TTR("Remove item %d?"), to_erase));
- cd->popup_centered(Size2(300, 60));
+ cd_remove->set_text(vformat(TTR("Remove item %d?"), to_erase));
+ cd_remove->popup_centered(Size2(300, 60));
}
} break;
case MENU_OPTION_IMPORT_FROM_SCENE: {
+ apply_xforms = false;
+ file->popup_file_dialog();
+ } break;
+ case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: {
+ apply_xforms = true;
file->popup_file_dialog();
} break;
case MENU_OPTION_UPDATE_FROM_SCENE: {
- cd->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene"))));
- cd->popup_centered(Size2(500, 60));
+ cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene"))));
+ cd_update->popup_centered(Size2(500, 60));
} break;
}
}
@@ -258,16 +273,22 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) {
menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM);
menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM);
menu->get_popup()->add_separator();
- menu->get_popup()->add_item(TTR("Import from Scene"), MENU_OPTION_IMPORT_FROM_SCENE);
+ menu->get_popup()->add_item(TTR("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE);
+ menu->get_popup()->add_item(TTR("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS);
menu->get_popup()->add_item(TTR("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE);
menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), true);
menu->get_popup()->connect("id_pressed", callable_mp(this, &MeshLibraryEditor::_menu_cbk));
menu->hide();
editor = p_editor;
- cd = memnew(ConfirmationDialog);
- add_child(cd);
- cd->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_confirm));
+ cd_remove = memnew(ConfirmationDialog);
+ add_child(cd_remove);
+ cd_remove->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_remove_confirm));
+ cd_update = memnew(ConfirmationDialog);
+ add_child(cd_update);
+ cd_update->get_ok_button()->set_text("Apply without Transforms");
+ cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(false));
+ cd_update->add_button("Apply with Transforms")->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(true));
}
void MeshLibraryEditorPlugin::edit(Object *p_node) {
diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h
index 6c33c8bb9e..9e225ffb9b 100644
--- a/editor/plugins/mesh_library_editor_plugin.h
+++ b/editor/plugins/mesh_library_editor_plugin.h
@@ -41,23 +41,27 @@ class MeshLibraryEditor : public Control {
EditorNode *editor;
MenuButton *menu;
- ConfirmationDialog *cd;
+ ConfirmationDialog *cd_remove;
+ ConfirmationDialog *cd_update;
EditorFileDialog *file;
+ bool apply_xforms;
int to_erase;
enum {
MENU_OPTION_ADD_ITEM,
MENU_OPTION_REMOVE_ITEM,
MENU_OPTION_UPDATE_FROM_SCENE,
- MENU_OPTION_IMPORT_FROM_SCENE
+ MENU_OPTION_IMPORT_FROM_SCENE,
+ MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS
};
int option;
void _import_scene_cbk(const String &p_str);
void _menu_cbk(int p_option);
- void _menu_confirm();
+ void _menu_remove_confirm();
+ void _menu_update_confirm(bool p_apply_xforms);
- static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge);
+ static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms);
protected:
static void _bind_methods();
@@ -66,7 +70,7 @@ public:
MenuButton *get_menu_button() const { return menu; }
void edit(const Ref<MeshLibrary> &p_mesh_library);
- static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true);
+ static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true, bool p_apply_xforms = false);
MeshLibraryEditor(EditorNode *p_editor);
};
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index 5d1b4d8ead..d20f3d105b 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -66,6 +66,7 @@
#include "scene/resources/cylinder_shape_3d.h"
#include "scene/resources/height_map_shape_3d.h"
#include "scene/resources/primitive_meshes.h"
+#include "scene/resources/separation_ray_shape_3d.h"
#include "scene/resources/sphere_shape_3d.h"
#include "scene/resources/surface_tool.h"
#include "scene/resources/world_margin_shape_3d.h"
@@ -2096,7 +2097,6 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Color bonecolor = Color(1.0, 0.4, 0.4, 0.3);
Color rootcolor = Color(0.4, 1.0, 0.4, 0.1);
- //LocalVector<int> bones_to_process = skel->get_parentless_bones();
LocalVector<int> bones_to_process;
bones_to_process = skel->get_parentless_bones();
@@ -2108,19 +2108,18 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
child_bones_vector = skel->get_bone_children(current_bone_idx);
int child_bones_size = child_bones_vector.size();
- // You have children but no parent, then you must be a root/parentless bone.
- if (child_bones_size >= 0 && skel->get_bone_parent(current_bone_idx) <= 0) {
- grests[current_bone_idx] = skel->global_pose_to_local_pose(current_bone_idx, skel->get_bone_global_pose(current_bone_idx));
+ if (skel->get_bone_parent(current_bone_idx) < 0) {
+ grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx);
}
for (int i = 0; i < child_bones_size; i++) {
int child_bone_idx = child_bones_vector[i];
- grests[child_bone_idx] = skel->global_pose_to_local_pose(child_bone_idx, skel->get_bone_global_pose(child_bone_idx));
+ grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx);
Vector3 v0 = grests[current_bone_idx].origin;
Vector3 v1 = grests[child_bone_idx].origin;
- Vector3 d = skel->get_bone_rest(child_bone_idx).origin.normalized();
- real_t dist = skel->get_bone_rest(child_bone_idx).origin.length();
+ Vector3 d = (v1 - v0).normalized();
+ real_t dist = v0.distance_to(v1);
// Find closest axis.
int closest = -1;
@@ -4067,6 +4066,10 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g
return p_id == 0 ? "Radius" : "Height";
}
+ if (Object::cast_to<SeparationRayShape3D>(*s)) {
+ return "Length";
+ }
+
return "";
}
@@ -4098,6 +4101,11 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p
return p_id == 0 ? cs2->get_radius() : cs2->get_height();
}
+ if (Object::cast_to<SeparationRayShape3D>(*s)) {
+ Ref<SeparationRayShape3D> cs2 = s;
+ return cs2->get_length();
+ }
+
return Variant();
}
@@ -4133,6 +4141,22 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i
ss->set_radius(d);
}
+ if (Object::cast_to<SeparationRayShape3D>(*s)) {
+ Ref<SeparationRayShape3D> rs = s;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb);
+ float d = ra.z;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ rs->set_length(d);
+ }
+
if (Object::cast_to<BoxShape3D>(*s)) {
Vector3 axis;
axis[p_id] = 1.0;
@@ -4287,6 +4311,20 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo
ur->commit_action();
}
+
+ if (Object::cast_to<SeparationRayShape3D>(*s)) {
+ Ref<SeparationRayShape3D> ss = s;
+ if (p_cancel) {
+ ss->set_length(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Separation Ray Shape Length"));
+ ur->add_do_method(ss.ptr(), "set_length", ss->get_length());
+ ur->add_undo_method(ss.ptr(), "set_length", p_restore);
+ ur->commit_action();
+ }
}
void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
@@ -4557,6 +4595,19 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines());
}
+ if (Object::cast_to<SeparationRayShape3D>(*s)) {
+ Ref<SeparationRayShape3D> rs = s;
+
+ Vector<Vector3> points;
+ points.push_back(Vector3());
+ points.push_back(Vector3(0, 0, rs->get_length()));
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(points);
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(0, 0, rs->get_length()));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
if (Object::cast_to<HeightMapShape3D>(*s)) {
Ref<HeightMapShape3D> hms = s;
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 291cafab2b..be5d756444 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1831,6 +1831,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
motion = Vector3(scale, scale, scale);
}
+ motion /= click.distance_to(_edit.center);
+
// Disable local transformation for TRANSFORM_VIEW
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp
index 0a949c8610..53c5b8dd70 100644
--- a/editor/plugins/packed_scene_translation_parser_plugin.cpp
+++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp
@@ -31,6 +31,7 @@
#include "packed_scene_translation_parser_plugin.h"
#include "core/io/resource_loader.h"
+#include "scene/gui/option_button.h"
#include "scene/resources/packed_scene.h"
void PackedSceneEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const {
@@ -50,21 +51,31 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path,
Ref<SceneState> state = Ref<PackedScene>(loaded_res)->get_state();
Vector<String> parsed_strings;
- String property_name;
- Variant property_value;
for (int i = 0; i < state->get_node_count(); i++) {
- if (!ClassDB::is_parent_class(state->get_node_type(i), "Control") && !ClassDB::is_parent_class(state->get_node_type(i), "Viewport")) {
+ String node_type = state->get_node_type(i);
+ if (!ClassDB::is_parent_class(node_type, "Control") && !ClassDB::is_parent_class(node_type, "Window")) {
continue;
}
+ // Find the `auto_translate` property, and abort the string parsing of the node if disabled.
+ bool auto_translating = true;
for (int j = 0; j < state->get_node_property_count(i); j++) {
- property_name = state->get_node_property_name(i, j);
- if (!lookup_properties.has(property_name)) {
- continue;
+ if (state->get_node_property_name(i, j) == "auto_translate" && (bool)state->get_node_property_value(i, j) == false) {
+ auto_translating = false;
+ break;
}
+ }
+ if (!auto_translating) {
+ continue;
+ }
- property_value = state->get_node_property_value(i, j);
+ for (int j = 0; j < state->get_node_property_count(i); j++) {
+ String property_name = state->get_node_property_name(i, j);
+ if (!lookup_properties.has(property_name) || (exception_list.has(node_type) && exception_list[node_type].has(property_name))) {
+ continue;
+ }
+ Variant property_value = state->get_node_property_value(i, j);
if (property_name == "script" && property_value.get_type() == Variant::OBJECT && !property_value.is_null()) {
// Parse built-in script.
Ref<Script> s = Object::cast_to<Script>(property_value);
@@ -76,7 +87,16 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path,
parsed_strings.append_array(temp);
r_ids_ctx_plural->append_array(ids_context_plural);
}
- } else if (property_name == "filters") {
+ } else if ((node_type == "MenuButton" || node_type == "OptionButton") && property_name == "items") {
+ Vector<String> str_values = property_value;
+ int incr_value = node_type == "MenuButton" ? PopupMenu::ITEM_PROPERTY_SIZE : OptionButton::ITEM_PROPERTY_SIZE;
+ for (int k = 0; k < str_values.size(); k += incr_value) {
+ String desc = str_values[k].get_slice(";", 1).strip_edges();
+ if (!desc.is_empty()) {
+ parsed_strings.push_back(desc);
+ }
+ }
+ } else if (node_type == "FileDialog" && property_name == "filters") {
// Extract FileDialog's filters property with values in format "*.png ; PNG Images","*.gd ; GDScript Files".
Vector<String> str_values = property_value;
for (int k = 0; k < str_values.size(); k++) {
@@ -105,12 +125,17 @@ PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlug
lookup_properties.insert("text");
lookup_properties.insert("hint_tooltip");
lookup_properties.insert("placeholder_text");
+ lookup_properties.insert("items");
+ lookup_properties.insert("title");
lookup_properties.insert("dialog_text");
lookup_properties.insert("filters");
lookup_properties.insert("script");
- //Add exception list (to prevent false positives)
- //line edit, text edit, richtextlabel
- //Set<String> exception_list;
- //exception_list.insert("RichTextLabel");
+ // Exception list (to prevent false positives).
+ exception_list.insert("LineEdit", Vector<StringName>());
+ exception_list["LineEdit"].append("text");
+ exception_list.insert("TextEdit", Vector<StringName>());
+ exception_list["TextEdit"].append("text");
+ exception_list.insert("CodeEdit", Vector<StringName>());
+ exception_list["CodeEdit"].append("text");
}
diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h
index e51d65414e..af0291b69c 100644
--- a/editor/plugins/packed_scene_translation_parser_plugin.h
+++ b/editor/plugins/packed_scene_translation_parser_plugin.h
@@ -37,7 +37,9 @@ class PackedSceneEditorTranslationParserPlugin : public EditorTranslationParserP
GDCLASS(PackedSceneEditorTranslationParserPlugin, EditorTranslationParserPlugin);
// Scene Node's properties that contain translation strings.
- Set<String> lookup_properties;
+ Set<StringName> lookup_properties;
+ // Properties from specific Nodes that should be ignored.
+ Map<StringName, Vector<StringName>> exception_list;
public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 3d2b3f4478..7ef5993ec5 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -37,6 +37,7 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_node.h"
#include "editor/editor_run_script.h"
#include "editor/editor_scale.h"
@@ -115,9 +116,9 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
}
/* Autoloads. */
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->value();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.value();
if (info.is_singleton) {
highlighter->add_keyword_color(info.name, usertype_color);
}
@@ -479,6 +480,29 @@ void ScriptEditor::_clear_execution(REF p_script) {
}
}
+void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) {
+ Ref<Script> script = Object::cast_to<Script>(*p_script);
+ if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) {
+ if (edit(p_script, p_line, 0, false)) {
+ editor->push_item(p_script.ptr());
+
+ ScriptEditorBase *se = _get_current_editor();
+ if (se) {
+ se->set_breakpoint(p_line, p_enabled);
+ }
+ }
+ }
+}
+
+void ScriptEditor::_clear_breakpoints() {
+ for (int i = 0; i < tab_container->get_child_count(); i++) {
+ ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
+ if (se) {
+ se->clear_breakpoints();
+ }
+ }
+}
+
ScriptEditorBase *ScriptEditor::_get_current_editor() const {
int selected = tab_container->get_current_tab();
if (selected < 0 || selected >= tab_container->get_child_count()) {
@@ -3484,6 +3508,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution));
debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution));
debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked));
+ debugger->get_default_debugger()->connect("set_breakpoint", callable_mp(this, &ScriptEditor::_set_breakpoint));
+ debugger->get_default_debugger()->connect("clear_breakpoints", callable_mp(this, &ScriptEditor::_clear_breakpoints));
menu_hb->add_spacer();
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index a57aeea5c0..e2420b4623 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -155,6 +155,8 @@ public:
virtual void tag_saved_version() = 0;
virtual void reload(bool p_soft) {}
virtual Array get_breakpoints() = 0;
+ virtual void set_breakpoint(int p_line, bool p_enabled) = 0;
+ virtual void clear_breakpoints() = 0;
virtual void add_callback(const String &p_function, PackedStringArray p_args) = 0;
virtual void update_settings() = 0;
virtual void set_debugger_active(bool p_active) = 0;
@@ -374,6 +376,8 @@ class ScriptEditor : public PanelContainer {
void _breaked(bool p_breaked, bool p_can_debug);
void _update_window_menu();
void _script_created(Ref<Script> p_script);
+ void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled);
+ void _clear_breakpoints();
ScriptEditorBase *_get_current_editor() const;
Array _get_open_script_editors() const;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 5f48106afc..89d91cc079 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -158,7 +158,6 @@ void ScriptTextEditor::enable_editor() {
editor_enabled = true;
_enable_code_editor();
- _set_theme_for_script();
_validate_script();
}
@@ -831,7 +830,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
if (info.is_singleton) {
EditorNode::get_singleton()->load_scene(info.path);
}
- } else if (p_symbol.is_rel_path()) {
+ } else if (p_symbol.is_relative_path()) {
// Every symbol other than absolute path is relative path so keep this condition at last.
String path = _get_absolute_path(p_symbol);
if (FileAccess::exists(path)) {
@@ -858,7 +857,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
ScriptLanguage::LookupResult result;
if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) {
text_edit->set_symbol_lookup_word_as_valid(true);
- } else if (p_symbol.is_rel_path()) {
+ } else if (p_symbol.is_relative_path()) {
String path = _get_absolute_path(p_symbol);
if (FileAccess::exists(path)) {
text_edit->set_symbol_lookup_word_as_valid(true);
@@ -1243,7 +1242,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
tx->set_caret_line(bpoints[bpoints.size() - 1]);
tx->center_viewport_to_caret();
} else {
- for (int i = bpoints.size(); i >= 0; i--) {
+ for (int i = bpoints.size() - 1; i >= 0; i--) {
int bline = bpoints[i];
if (bline < line) {
tx->unfold_line(bline);
@@ -1370,6 +1369,14 @@ Array ScriptTextEditor::get_breakpoints() {
return code_editor->get_text_editor()->get_breakpointed_lines();
}
+void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) {
+ code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled);
+}
+
+void ScriptTextEditor::clear_breakpoints() {
+ code_editor->get_text_editor()->clear_breakpointed_lines();
+}
+
void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) {
code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this);
}
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 1ca6f56ea1..4208d67f17 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -225,6 +225,8 @@ public:
virtual void reload(bool p_soft) override;
virtual Array get_breakpoints() override;
+ virtual void set_breakpoint(int p_line, bool p_enabled) override;
+ virtual void clear_breakpoints() override;
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
virtual void update_settings() override;
diff --git a/editor/plugins/skeleton_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp
index 7ef680d7ef..c350004f0f 100644
--- a/editor/plugins/skeleton_2d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_2d_editor_plugin.cpp
@@ -98,9 +98,10 @@ Skeleton2DEditor::Skeleton2DEditor() {
options->set_text(TTR("Skeleton2D"));
options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton2D"), SNAME("EditorIcons")));
- options->get_popup()->add_item(TTR("Make Rest Pose (From Bones)"), MENU_OPTION_MAKE_REST);
+ options->get_popup()->add_item(TTR("Reset to Rest Pose"), MENU_OPTION_MAKE_REST);
options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Set Bones to Rest Pose"), MENU_OPTION_SET_REST);
+ // Use the "Overwrite" word to highlight that this is a destructive operation.
+ options->get_popup()->add_item(TTR("Overwrite Rest Pose"), MENU_OPTION_SET_REST);
options->set_switch_on_hover(true);
options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton2DEditor::_menu_option));
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index 839e1c5f7a..6bf0042393 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -121,6 +121,8 @@ public:
virtual void set_edit_state(const Variant &p_state) override;
virtual Vector<String> get_functions() override;
virtual Array get_breakpoints() override;
+ virtual void set_breakpoint(int p_line, bool p_enabled) override{};
+ virtual void clear_breakpoints() override{};
virtual void goto_line(int p_line, bool p_with_error = false) override;
void goto_line_selection(int p_line, int p_begin, int p_end);
virtual void set_executing_line(int p_line) override;
diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp
index 0add83f64d..6e0a5b00b9 100644
--- a/editor/plugins/tiles/tile_atlas_view.cpp
+++ b/editor/plugins/tiles/tile_atlas_view.cpp
@@ -327,10 +327,12 @@ void TileAtlasView::_draw_base_tiles_shape_grid() {
Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_effective_texture_offset(tile_id, 0);
Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id);
- Vector2 origin = texture_region.position + (texture_region.size - tile_shape_size) / 2 + in_tile_base_offset;
// Draw only if the tile shape fits in the texture region
- tile_set->draw_tile_shape(base_tiles_shape_grid, Rect2(origin, tile_shape_size), grid_color);
+ Transform2D tile_xform;
+ tile_xform.set_origin(texture_region.position + texture_region.size / 2 + in_tile_base_offset);
+ tile_xform.set_scale(tile_shape_size);
+ tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, grid_color);
}
}
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index d406c2514c..2a75a743a7 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -124,7 +124,9 @@ void GenericTilePolygonEditor::_base_control_draw() {
base_control->draw_set_transform_matrix(xform);
// Draw the tile shape filled.
- tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), Color(1.0, 1.0, 1.0, 0.3), true);
+ Transform2D tile_xform;
+ tile_xform.set_scale(tile_size);
+ tile_set->draw_tile_shape(base_control, tile_xform, Color(1.0, 1.0, 1.0, 0.3), true);
// Draw the background.
if (background_texture.is_valid()) {
@@ -213,7 +215,7 @@ void GenericTilePolygonEditor::_base_control_draw() {
// Draw the tile shape line.
base_control->draw_set_transform_matrix(xform);
- tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), grid_color, false);
+ tile_set->draw_tile_shape(base_control, tile_xform, grid_color, false);
base_control->draw_set_transform_matrix(Transform2D());
}
@@ -1072,14 +1074,15 @@ void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Tran
ERR_FAIL_COND(!tile_data);
Vector2i tile_set_tile_size = tile_set->get_tile_size();
- Rect2i rect = Rect2i(-tile_set_tile_size / 2, tile_set_tile_size);
Color color = Color(1.0, 0.0, 0.0);
if (p_selected) {
Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color");
Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0);
color = selection_color;
}
- tile_set->draw_tile_shape(p_canvas_item, p_transform.xform(rect), color);
+ Transform2D tile_xform;
+ tile_xform.set_scale(tile_set_tile_size);
+ tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, color);
}
void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
@@ -1465,12 +1468,13 @@ void TileDataTerrainsEditor::_tile_set_changed() {
ERR_FAIL_COND(!tile_set.is_valid());
// Fix if wrong values are selected.
- if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) {
+ int terrain_set = int(dummy_object->get("terrain_set"));
+ if (terrain_set >= tile_set->get_terrain_sets_count()) {
+ terrain_set = -1;
dummy_object->set("terrain_set", -1);
}
- int terrain_set = int(dummy_object->get("terrain"));
if (terrain_set >= 0) {
- if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) {
+ if (int(dummy_object->get("terrain")) >= tile_set->get_terrains_count(terrain_set)) {
dummy_object->set("terrain", -1);
}
}
@@ -1513,9 +1517,10 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas
}
} else {
// Draw hovered tile.
- Vector2i tile_size = tile_set->get_tile_size();
- Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size));
- tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true);
+ Transform2D tile_xform;
+ tile_xform.set_origin(position);
+ tile_xform.set_scale(tile_set->get_tile_size());
+ tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true);
}
}
}
@@ -1685,9 +1690,10 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til
}
} else {
// Draw hovered tile.
- Vector2i tile_size = tile_set->get_tile_size();
- Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size));
- tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true);
+ Transform2D tile_xform;
+ tile_xform.set_origin(position);
+ tile_xform.set_scale(tile_set->get_tile_size());
+ tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true);
}
}
}
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 77084f551a..acbd5d70ff 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -636,8 +636,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
for (int y = rect.position.y; y < rect.get_end().y; y++) {
Vector2i coords = Vector2i(x, y);
if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) {
- Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size));
- tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false);
+ Transform2D tile_xform;
+ tile_xform.set_origin(tile_map->map_to_world(coords));
+ tile_xform.set_scale(tile_shape_size);
+ tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0), false);
}
}
}
@@ -734,10 +736,12 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f);
float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f);
- Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size));
+ Transform2D tile_xform;
+ tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y)));
+ tile_xform.set_scale(tile_shape_size);
Color color = grid_color;
color.a = color.a * opacity;
- tile_set->draw_tile_shape(p_overlay, cell_region, color, false);
+ tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, false);
}
}
}
@@ -745,11 +749,11 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
// Draw the preview.
for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) {
- Vector2i size = tile_set->get_tile_size();
- Vector2 position = tile_map->map_to_world(E->key()) - size / 2;
- Rect2 cell_region = xform.xform(Rect2(position, size));
+ Transform2D tile_xform;
+ tile_xform.set_origin(tile_map->map_to_world(E->key()));
+ tile_xform.set_scale(tile_set->get_tile_size());
if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
- tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true);
+ tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true);
} else {
if (tile_set->has_source(E->get().source_id)) {
TileSetSource *source = *tile_set->get_source(E->get().source_id);
@@ -791,10 +795,10 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
// Draw the tile.
p_overlay->draw_texture_rect_region(atlas_source->get_texture(), dest_rect, source_rect, modulate * Color(1.0, 1.0, 1.0, 0.5), transpose, tile_set->is_uv_clipping());
} else {
- tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true);
+ tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true);
}
} else {
- tile_set->draw_tile_shape(p_overlay, cell_region, Color(0.0, 0.0, 0.0, 0.5), true);
+ tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(0.0, 0.0, 0.0, 0.5), true);
}
}
}
@@ -3549,30 +3553,76 @@ void TileMapEditor::_update_layers_selection() {
tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
}
-void TileMapEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
+void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) {
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
ERR_FAIL_COND(!undo_redo);
TileMap *tile_map = Object::cast_to<TileMap>(p_edited);
- if (tile_map) {
- if (p_property == "layers_count") {
- int new_layers_count = (int)p_new_value;
- if (new_layers_count < tile_map->get_layers_count()) {
- List<PropertyInfo> property_list;
- tile_map->get_property_list(&property_list);
-
- for (PropertyInfo property_info : property_list) {
- Vector<String> components = String(property_info.name).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
- int index = components[0].trim_prefix("layer_").to_int();
- if (index >= new_layers_count) {
- undo_redo->add_undo_property(tile_map, property_info.name, tile_map->get(property_info.name));
- }
- }
+ if (!tile_map) {
+ return;
+ }
+
+ // Compute the array indices to save.
+ int begin = 0;
+ int end;
+ if (p_array_prefix == "layer_") {
+ end = tile_map->get_layers_count();
+ } else {
+ ERR_FAIL_MSG("Invalid array prefix for TileSet.");
+ }
+ if (p_from_index < 0) {
+ // Adding new.
+ if (p_to_pos >= 0) {
+ begin = p_to_pos;
+ } else {
+ end = 0; // Nothing to save when adding at the end.
+ }
+ } else if (p_to_pos < 0) {
+ // Removing.
+ begin = p_from_index;
+ } else {
+ // Moving.
+ begin = MIN(p_from_index, p_to_pos);
+ end = MIN(MAX(p_from_index, p_to_pos) + 1, end);
+ }
+
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
+ // Save layers' properties.
+ if (p_from_index < 0) {
+ undo_redo->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_undo_method(tile_map, "add_layer", p_from_index);
+ }
+
+ List<PropertyInfo> properties;
+ tile_map->get_property_list(&properties);
+ for (PropertyInfo pi : properties) {
+ if (pi.name.begins_with(p_array_prefix)) {
+ String str = pi.name.trim_prefix(p_array_prefix);
+ int to_char_index = 0;
+ while (to_char_index < str.length()) {
+ if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ break;
+ }
+ to_char_index++;
+ }
+ if (to_char_index > 0) {
+ int array_index = str.left(to_char_index).to_int();
+ if (array_index >= begin && array_index < end) {
+ ADD_UNDO(tile_map, pi.name);
}
}
}
}
+#undef ADD_UNDO
+
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_map, "add_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_map, "remove_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos);
+ }
}
bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
@@ -3643,8 +3693,10 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
0.8);
// Draw the scaled tile.
- Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size)));
- tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture);
+ Transform2D tile_xform;
+ tile_xform.set_origin(tile_map->map_to_world(coords));
+ tile_xform.set_scale(tile_shape_size);
+ tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, true, warning_pattern_texture);
}
// Draw the warning icon.
@@ -3700,10 +3752,12 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
float bottom_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.y, (float)(displayed_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f);
float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f);
- Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size));
+ Transform2D tile_xform;
+ tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y)));
+ tile_xform.set_scale(tile_shape_size);
Color color = grid_color;
color.a = color.a * opacity;
- tile_set->draw_tile_shape(p_overlay, cell_region, color, false);
+ tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, false);
}
}
}
@@ -3851,7 +3905,7 @@ TileMapEditor::TileMapEditor() {
_tab_changed(0);
// Registers UndoRedo inspector callback.
- EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileMapEditor::_undo_redo_inspector_callback));
+ EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileMap"), callable_mp(this, &TileMapEditor::_move_tile_map_array_element));
}
TileMapEditor::~TileMapEditor() {
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
index 6e2f2ce2ba..6126db59e9 100644
--- a/editor/plugins/tiles/tile_map_editor.h
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -341,7 +341,7 @@ private:
void _update_layers_selection();
// Inspector undo/redo callback.
- void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
+ void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos);
protected:
void _notification(int p_what);
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 432f48fa85..c3a3f40e00 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -1866,7 +1866,7 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
ERR_FAIL_COND(!undo_redo);
-#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property));
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited);
if (tile_data) {
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
index ba98a7d6b3..48d0d9b333 100644
--- a/editor/plugins/tiles/tile_set_editor.cpp
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -330,11 +330,192 @@ void TileSetEditor::_tile_set_changed() {
tile_set_changed_needs_update = true;
}
+void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) {
+ UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
+ ERR_FAIL_COND(!undo_redo);
+
+ TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
+ if (!tile_set) {
+ return;
+ }
+
+ Vector<String> components = String(p_array_prefix).split("/", true, 2);
+
+ // Compute the array indices to save.
+ int begin = 0;
+ int end;
+ if (p_array_prefix == "occlusion_layer_") {
+ end = tile_set->get_occlusion_layers_count();
+ } else if (p_array_prefix == "physics_layer_") {
+ end = tile_set->get_physics_layers_count();
+ } else if (p_array_prefix == "terrain_set_") {
+ end = tile_set->get_terrain_sets_count();
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
+ int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
+ end = tile_set->get_terrains_count(terrain_set);
+ } else if (p_array_prefix == "navigation_layer_") {
+ end = tile_set->get_navigation_layers_count();
+ } else if (p_array_prefix == "custom_data_layer_") {
+ end = tile_set->get_custom_data_layers_count();
+ } else {
+ ERR_FAIL_MSG("Invalid array prefix for TileSet.");
+ }
+ if (p_from_index < 0) {
+ // Adding new.
+ if (p_to_pos >= 0) {
+ begin = p_to_pos;
+ } else {
+ end = 0; // Nothing to save when adding at the end.
+ }
+ } else if (p_to_pos < 0) {
+ // Removing.
+ begin = p_from_index;
+ } else {
+ // Moving.
+ begin = MIN(p_from_index, p_to_pos);
+ end = MIN(MAX(p_from_index, p_to_pos) + 1, end);
+ }
+
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
+ // Save layers' properties.
+ List<PropertyInfo> properties;
+ tile_set->get_property_list(&properties);
+ for (PropertyInfo pi : properties) {
+ if (pi.name.begins_with(p_array_prefix)) {
+ String str = pi.name.trim_prefix(p_array_prefix);
+ int to_char_index = 0;
+ while (to_char_index < str.length()) {
+ if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ break;
+ }
+ to_char_index++;
+ }
+ if (to_char_index > 0) {
+ int array_index = str.left(to_char_index).to_int();
+ if (array_index >= begin && array_index < end) {
+ ADD_UNDO(tile_set, pi.name);
+ }
+ }
+ }
+ }
+
+ // Save properties for TileSetAtlasSources tile data
+ for (int i = 0; i < tile_set->get_source_count(); i++) {
+ int source_id = tile_set->get_source_id(i);
+
+ Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
+ if (tas.is_valid()) {
+ for (int j = 0; j < tas->get_tiles_count(); j++) {
+ Vector2i tile_id = tas->get_tile_id(j);
+ for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) {
+ int alternative_id = tas->get_alternative_tile_id(tile_id, k);
+ TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id));
+ ERR_FAIL_COND(!tile_data);
+
+ // Actually saving stuff.
+ if (p_array_prefix == "occlusion_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index));
+ }
+ } else if (p_array_prefix == "physics_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index));
+ for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index));
+ }
+ }
+ } else if (p_array_prefix == "terrain_set_") {
+ ADD_UNDO(tile_data, "terrain_set");
+ for (int terrain_set_index = begin; terrain_set_index < end; terrain_set_index++) {
+ for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
+ if (tile_data->is_valid_peering_bit_terrain(bit)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
+ }
+ }
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
+ for (int terrain_index = 0; terrain_index < TileSet::CELL_NEIGHBOR_MAX; terrain_index++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(terrain_index);
+ if (tile_data->is_valid_peering_bit_terrain(bit)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index]));
+ }
+ }
+ } else if (p_array_prefix == "navigation_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index));
+ }
+ } else if (p_array_prefix == "custom_data_layer_") {
+ for (int layer_index = begin; layer_index < end; layer_index++) {
+ ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index));
+ }
+ }
+ }
+ }
+ }
+ }
+#undef ADD_UNDO
+
+ // Add do method.
+ if (p_array_prefix == "occlusion_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "physics_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "terrain_set_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos);
+ }
+ } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") {
+ int terrain_set = components[0].trim_prefix("terrain_set_").to_int();
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "navigation_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos);
+ }
+ } else if (p_array_prefix == "custom_data_layer_") {
+ if (p_from_index < 0) {
+ undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos);
+ } else if (p_to_pos < 0) {
+ undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index);
+ } else {
+ undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos);
+ }
+ }
+}
+
void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
ERR_FAIL_COND(!undo_redo);
-#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property));
+#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property));
TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
if (tile_set) {
Vector<String> components = p_property.split("/", true, 3);
@@ -350,30 +531,7 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p
TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id));
ERR_FAIL_COND(!tile_data);
- if (p_property == "occlusion_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_occlusion_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) {
- ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index));
- }
- }
- } else if (p_property == "physics_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_physics_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) {
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index));
- for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) {
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index));
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index));
- ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index));
- }
- }
- }
- } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) ||
- (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") ||
- (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) {
+ if (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") {
ADD_UNDO(tile_data, "terrain_set");
for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) {
TileSet::CellNeighbor bit = TileSet::CellNeighbor(l);
@@ -381,22 +539,6 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p
ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l]));
}
}
- } else if (p_property == "navigation_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_navigation_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) {
- ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index));
- }
- }
- } else if (p_property == "custom_data_layers_count") {
- int new_layer_count = p_new_value;
- int old_layer_count = tile_set->get_custom_data_layers_count();
- if (new_layer_count < old_layer_count) {
- for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) {
- ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index));
- }
- }
} else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") {
int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int();
ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer));
@@ -531,6 +673,7 @@ TileSetEditor::TileSetEditor() {
tile_set_scenes_collection_source_editor->hide();
// Registers UndoRedo inspector callback.
+ EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element));
EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback));
}
diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h
index 970e3fabb6..fe854b2281 100644
--- a/editor/plugins/tiles/tile_set_editor.h
+++ b/editor/plugins/tiles/tile_set_editor.h
@@ -71,6 +71,7 @@ private:
void _tile_set_changed();
+ void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos);
void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
protected:
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 5b1da11f12..aaa085675c 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -2547,6 +2547,7 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa
}
}
}
+ _member_cancel();
VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr());
if (uniform) {
@@ -4041,7 +4042,7 @@ VisualShaderEditor::VisualShaderEditor() {
graph->get_zoom_hbox()->add_child(add_node);
add_node->set_text(TTR("Add Node..."));
graph->get_zoom_hbox()->move_child(add_node, 0);
- add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog), varray(false));
+ add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog), varray(false, VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PORT_TYPE_MAX));
preview_shader = memnew(Button);
preview_shader->set_flat(true);
diff --git a/editor/pot_generator.cpp b/editor/pot_generator.cpp
index b58b7e4cac..d57345cac1 100644
--- a/editor/pot_generator.cpp
+++ b/editor/pot_generator.cpp
@@ -108,7 +108,6 @@ void POTGenerator::_write_to_pot(const String &p_file) {
const String header =
"# LANGUAGE translation for " + project_name + " for the following files:\n" + extracted_files +
"#\n"
- "#\n"
"# FIRST AUTHOR < EMAIL @ADDRESS>, YEAR.\n"
"#\n"
"#, fuzzy\n"
@@ -116,8 +115,9 @@ void POTGenerator::_write_to_pot(const String &p_file) {
"msgstr \"\"\n"
"\"Project-Id-Version: " +
project_name + "\\n\"\n"
+ "\"MIME-Version: 1.0\\n\"\n"
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
- "\"Content-Transfer-Encoding: 8-bit\\n\"\n\n";
+ "\"Content-Transfer-Encoding: 8-bit\\n\"\n";
file->store_string(header);
@@ -129,6 +129,9 @@ void POTGenerator::_write_to_pot(const String &p_file) {
String plural = v_msgid_data[i].plural;
const Set<String> &locations = v_msgid_data[i].locations;
+ // Put the blank line at the start, to avoid a double at the end when closing the file.
+ file->store_line("");
+
// Write file locations.
for (Set<String>::Element *E = locations.front(); E; E = E->next()) {
file->store_line("#: " + E->get().trim_prefix("res://"));
@@ -142,13 +145,13 @@ void POTGenerator::_write_to_pot(const String &p_file) {
// Write msgid.
_write_msgid(file, msgid, false);
- // Write msgid_plural
+ // Write msgid_plural.
if (!plural.is_empty()) {
_write_msgid(file, plural, true);
file->store_line("msgstr[0] \"\"");
- file->store_line("msgstr[1] \"\"\n");
+ file->store_line("msgstr[1] \"\"");
} else {
- file->store_line("msgstr \"\"\n");
+ file->store_line("msgstr \"\"");
}
}
}
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 05cf3791f4..81554c9550 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -2051,6 +2051,10 @@ void ProjectManager::_open_selected_projects() {
args.push_back("--disable-crash-handler");
}
+ if (OS::get_singleton()->is_single_window()) {
+ args.push_back("--single-window");
+ }
+
String exec = OS::get_singleton()->get_executable_path();
Error err = OS::get_singleton()->create_process(exec, args);
ERR_FAIL_COND(err);
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index b8ccab78dd..db12e90540 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -222,7 +222,6 @@ void ProjectSettingsEditor::_add_feature_overrides() {
presets.insert("standalone");
presets.insert("32");
presets.insert("64");
- presets.insert("Server"); // Not available as an export platform yet, so it needs to be added manually
EditorExport *ee = EditorExport::get_singleton();
diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp
index d86e2656d4..9063b5c6f8 100644
--- a/editor/rename_dialog.cpp
+++ b/editor/rename_dialog.cpp
@@ -30,23 +30,19 @@
#include "rename_dialog.h"
+#ifdef MODULE_REGEX_ENABLED
+
#include "core/string/print_string.h"
#include "editor_node.h"
#include "editor_scale.h"
#include "editor_settings.h"
#include "editor_themes.h"
+#include "modules/regex/regex.h"
#include "plugins/script_editor_plugin.h"
#include "scene/gui/control.h"
#include "scene/gui/label.h"
#include "scene/gui/tab_container.h"
-#include "modules/modules_enabled.gen.h"
-#ifdef MODULE_REGEX_ENABLED
-#include "modules/regex/regex.h"
-#else
-#error "Can't build editor rename dialog without RegEx module."
-#endif
-
RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_undo_redo) {
scene_tree_editor = p_scene_tree_editor;
undo_redo = p_undo_redo;
@@ -655,3 +651,5 @@ void RenameDialog::_features_toggled(bool pressed) {
size.y = 0;
set_size(size);
}
+
+#endif // MODULE_REGEX_ENABLED
diff --git a/editor/rename_dialog.h b/editor/rename_dialog.h
index 76e99e3b66..7990862b37 100644
--- a/editor/rename_dialog.h
+++ b/editor/rename_dialog.h
@@ -31,6 +31,9 @@
#ifndef RENAME_DIALOG_H
#define RENAME_DIALOG_H
+#include "modules/modules_enabled.gen.h"
+#ifdef MODULE_REGEX_ENABLED
+
#include "scene/gui/check_box.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/option_button.h"
@@ -113,4 +116,6 @@ public:
~RenameDialog() {}
};
-#endif
+#endif // MODULE_REGEX_ENABLED
+
+#endif // RENAME_DIALOG_H
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 2ec4a088a2..4bc0905163 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -83,10 +83,12 @@ void SceneTreeDock::unhandled_key_input(const Ref<InputEvent> &p_event) {
return;
}
- if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) {
- _tool_selected(TOOL_BATCH_RENAME);
- } else if (ED_IS_SHORTCUT("scene_tree/rename", p_event)) {
+ if (ED_IS_SHORTCUT("scene_tree/rename", p_event)) {
_tool_selected(TOOL_RENAME);
+#ifdef MODULE_REGEX_ENABLED
+ } else if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) {
+ _tool_selected(TOOL_BATCH_RENAME);
+#endif // MODULE_REGEX_ENABLED
} else if (ED_IS_SHORTCUT("scene_tree/add_child_node", p_event)) {
_tool_selected(TOOL_NEW);
} else if (ED_IS_SHORTCUT("scene_tree/instance_scene", p_event)) {
@@ -336,6 +338,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
current_option = p_tool;
switch (p_tool) {
+#ifdef MODULE_REGEX_ENABLED
case TOOL_BATCH_RENAME: {
if (!profile_allow_editing) {
break;
@@ -344,6 +347,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
rename_dialog->popup_centered();
}
} break;
+#endif // MODULE_REGEX_ENABLED
case TOOL_RENAME: {
if (!profile_allow_editing) {
break;
@@ -2807,11 +2811,13 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
}
}
+#ifdef MODULE_REGEX_ENABLED
if (profile_allow_editing && selection.size() > 1) {
//this is not a commonly used action, it makes no sense for it to be where it was nor always present.
menu->add_separator();
menu->add_icon_shortcut(get_theme_icon(SNAME("Rename"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/batch_rename"), TOOL_BATCH_RENAME);
}
+#endif // MODULE_REGEX_ENABLED
menu->add_separator();
menu->add_icon_item(get_theme_icon(SNAME("Help"), SNAME("EditorIcons")), TTR("Open Documentation"), TOOL_OPEN_DOCUMENTATION);
@@ -2837,6 +2843,11 @@ void SceneTreeDock::set_filter(const String &p_filter) {
scene_tree->set_filter(p_filter);
}
+void SceneTreeDock::save_branch_to_file(String p_directory) {
+ new_scene_from_dialog->set_current_dir(p_directory);
+ _tool_selected(TOOL_NEW_SCENE_FROM);
+}
+
void SceneTreeDock::_focus_node() {
Node *node = scene_tree->get_selected();
ERR_FAIL_COND(!node);
@@ -3316,8 +3327,10 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
create_dialog->connect("create", callable_mp(this, &SceneTreeDock::_create));
create_dialog->connect("favorites_updated", callable_mp(this, &SceneTreeDock::_update_create_root_dialog));
+#ifdef MODULE_REGEX_ENABLED
rename_dialog = memnew(RenameDialog(scene_tree, &editor_data->get_undo_redo()));
add_child(rename_dialog);
+#endif // MODULE_REGEX_ENABLED
script_create_dialog = memnew(ScriptCreateDialog);
script_create_dialog->set_inheritance_base_type("Node");
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 387a35acbb..ece0ca5ca4 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -62,7 +62,9 @@ class SceneTreeDock : public VBoxContainer {
TOOL_COPY,
TOOL_PASTE,
TOOL_RENAME,
+#ifdef MODULE_REGEX_ENABLED
TOOL_BATCH_RENAME,
+#endif // MODULE_REGEX_ENABLED
TOOL_REPLACE,
TOOL_EXTEND_SCRIPT,
TOOL_ATTACH_SCRIPT,
@@ -105,7 +107,9 @@ class SceneTreeDock : public VBoxContainer {
int current_option;
CreateDialog *create_dialog;
+#ifdef MODULE_REGEX_ENABLED
RenameDialog *rename_dialog;
+#endif // MODULE_REGEX_ENABLED
Button *button_add;
Button *button_instance;
@@ -269,6 +273,7 @@ protected:
public:
String get_filter();
void set_filter(const String &p_filter);
+ void save_branch_to_file(String p_directory);
void _focus_node();
diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp
index e7ba80677d..d54bf73028 100644
--- a/editor/scene_tree_editor.cpp
+++ b/editor/scene_tree_editor.cpp
@@ -291,10 +291,12 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
}
}
+ // Display the node name in all tooltips so that long node names can be previewed
+ // without having to rename them.
if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) {
item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
- String tooltip = TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class();
+ String tooltip = String(p_node->get_name()) + "\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path() + "\n" + TTR("Type:") + " " + p_node->get_class();
if (p_node->get_editor_description() != String()) {
tooltip += "\n\n" + p_node->get_editor_description();
}
@@ -303,7 +305,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
} else if (p_node != get_scene_node() && p_node->get_filename() != "" && can_open_instance) {
item->add_button(0, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_SUBSCENE, false, TTR("Open in Editor"));
- String tooltip = TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class();
+ String tooltip = String(p_node->get_name()) + "\n" + TTR("Instance:") + " " + p_node->get_filename() + "\n" + TTR("Type:") + " " + p_node->get_class();
if (p_node->get_editor_description() != String()) {
tooltip += "\n\n" + p_node->get_editor_description();
}
@@ -315,7 +317,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
type = p_node->get_class();
}
- String tooltip = TTR("Type:") + " " + type;
+ String tooltip = String(p_node->get_name()) + "\n" + TTR("Type:") + " " + type;
if (p_node->get_editor_description() != String()) {
tooltip += "\n\n" + p_node->get_editor_description();
}
diff --git a/editor/translations/el.po b/editor/translations/el.po
index e773b011a4..93b5941f64 100644
--- a/editor/translations/el.po
+++ b/editor/translations/el.po
@@ -6,7 +6,7 @@
# Georgios Katsanakis <geo.elgeo@gmail.com>, 2019.
# Overloaded <manoschool@yahoo.gr>, 2019.
# Eternal Death <eternaldeath0001@gmail.com>, 2019.
-# Overloaded @ Orama Interactive https://orama-interactive.com/ <manoschool@yahoo.gr>, 2020.
+# Overloaded @ Orama Interactive http://orama-interactive.com/ <manoschool@yahoo.gr>, 2020.
# pandektis <pandektis@gmail.com>, 2020.
# KostasMSC <kargyris@athtech.gr>, 2020.
# lawfulRobot <czavantias@gmail.com>, 2020, 2021.