summaryrefslogtreecommitdiff
path: root/editor/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'editor/plugins')
-rw-r--r--editor/plugins/SCsub2
-rw-r--r--editor/plugins/abstract_polygon_2d_editor.cpp4
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.cpp6
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.cpp6
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp28
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp42
-rw-r--r--editor/plugins/animation_player_editor_plugin.h4
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp14
-rw-r--r--editor/plugins/animation_tree_editor_plugin.cpp2
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp25
-rw-r--r--editor/plugins/asset_library_editor_plugin.h2
-rw-r--r--editor/plugins/audio_stream_editor_plugin.cpp17
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp922
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h39
-rw-r--r--editor/plugins/collision_polygon_3d_editor_plugin.cpp108
-rw-r--r--editor/plugins/collision_polygon_3d_editor_plugin.h5
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/curve_editor_plugin.cpp10
-rw-r--r--editor/plugins/editor_preview_plugins.cpp98
-rw-r--r--editor/plugins/font_editor_plugin.cpp8
-rw-r--r--editor/plugins/font_editor_plugin.h2
-rw-r--r--editor/plugins/gpu_particles_2d_editor_plugin.cpp14
-rw-r--r--editor/plugins/gpu_particles_3d_editor_plugin.cpp6
-rw-r--r--editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp2
-rw-r--r--editor/plugins/gradient_editor_plugin.cpp2
-rw-r--r--editor/plugins/input_event_editor_plugin.cpp122
-rw-r--r--editor/plugins/input_event_editor_plugin.h79
-rw-r--r--editor/plugins/lightmap_gi_editor_plugin.cpp (renamed from editor/plugins/baked_lightmap_editor_plugin.cpp)42
-rw-r--r--editor/plugins/lightmap_gi_editor_plugin.h (renamed from editor/plugins/baked_lightmap_editor_plugin.h)16
-rw-r--r--editor/plugins/material_editor_plugin.cpp42
-rw-r--r--editor/plugins/mesh_editor_plugin.cpp14
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp19
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.h1
-rw-r--r--editor/plugins/mesh_library_editor_plugin.cpp8
-rw-r--r--editor/plugins/multimesh_editor_plugin.cpp6
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp531
-rw-r--r--editor/plugins/node_3d_editor_plugin.h43
-rw-r--r--editor/plugins/occluder_instance_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/ot_features_plugin.cpp4
-rw-r--r--editor/plugins/ot_features_plugin.h2
-rw-r--r--editor/plugins/path_2d_editor_plugin.cpp6
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp12
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp14
-rw-r--r--editor/plugins/resource_preloader_editor_plugin.cpp12
-rw-r--r--editor/plugins/root_motion_editor_plugin.cpp5
-rw-r--r--editor/plugins/root_motion_editor_plugin.h2
-rw-r--r--editor/plugins/script_editor_plugin.cpp183
-rw-r--r--editor/plugins/script_editor_plugin.h8
-rw-r--r--editor/plugins/script_text_editor.cpp251
-rw-r--r--editor/plugins/script_text_editor.h11
-rw-r--r--editor/plugins/shader_editor_plugin.cpp261
-rw-r--r--editor/plugins/shader_editor_plugin.h19
-rw-r--r--editor/plugins/shader_file_editor_plugin.cpp2
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp38
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h14
-rw-r--r--editor/plugins/sprite_2d_editor_plugin.cpp6
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp142
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h8
-rw-r--r--editor/plugins/style_box_editor_plugin.cpp4
-rw-r--r--editor/plugins/style_box_editor_plugin.h2
-rw-r--r--editor/plugins/text_editor.cpp85
-rw-r--r--editor/plugins/text_editor.h5
-rw-r--r--editor/plugins/texture_3d_editor_plugin.cpp6
-rw-r--r--editor/plugins/texture_editor_plugin.cpp2
-rw-r--r--editor/plugins/texture_layered_editor_plugin.cpp10
-rw-r--r--editor/plugins/texture_region_editor_plugin.cpp22
-rw-r--r--editor/plugins/theme_editor_plugin.cpp3112
-rw-r--r--editor/plugins/theme_editor_plugin.h283
-rw-r--r--editor/plugins/theme_editor_preview.cpp464
-rw-r--r--editor/plugins/theme_editor_preview.h118
-rw-r--r--editor/plugins/tile_map_editor_plugin.cpp2329
-rw-r--r--editor/plugins/tile_map_editor_plugin.h242
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp3680
-rw-r--r--editor/plugins/tile_set_editor_plugin.h298
-rw-r--r--editor/plugins/tiles/SCsub5
-rw-r--r--editor/plugins/tiles/tile_atlas_view.cpp680
-rw-r--r--editor/plugins/tiles/tile_atlas_view.h160
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp2467
-rw-r--r--editor/plugins/tiles/tile_data_editors.h408
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp3525
-rw-r--r--editor/plugins/tiles/tile_map_editor.h343
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp2322
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.h283
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp512
-rw-r--r--editor/plugins/tiles/tile_set_editor.h84
-rw-r--r--editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp511
-rw-r--r--editor/plugins/tiles/tile_set_scenes_collection_source_editor.h139
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.cpp277
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.h114
-rw-r--r--editor/plugins/version_control_editor_plugin.cpp5
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp867
-rw-r--r--editor/plugins/visual_shader_editor_plugin.h46
-rw-r--r--editor/plugins/voxel_gi_editor_plugin.cpp (renamed from editor/plugins/gi_probe_editor_plugin.cpp)77
-rw-r--r--editor/plugins/voxel_gi_editor_plugin.h (renamed from editor/plugins/gi_probe_editor_plugin.h)24
94 files changed, 17573 insertions, 9233 deletions
diff --git a/editor/plugins/SCsub b/editor/plugins/SCsub
index 359d04e5df..10a65b427e 100644
--- a/editor/plugins/SCsub
+++ b/editor/plugins/SCsub
@@ -3,3 +3,5 @@
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")
+
+SConscript("tiles/SCsub")
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp
index 80d0a7db60..e6f7ec1fbf 100644
--- a/editor/plugins/abstract_polygon_2d_editor.cpp
+++ b/editor/plugins/abstract_polygon_2d_editor.cpp
@@ -266,7 +266,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
if (mb->is_pressed()) {
- if (mb->get_control() || mb->get_shift() || mb->get_alt()) {
+ if (mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
return false;
}
@@ -399,7 +399,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
Vector2 cpoint = _get_node()->get_global_transform().affine_inverse().xform(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint)));
//Move the point in a single axis. Should only work when editing a polygon and while holding shift.
- if (mode == MODE_EDIT && mm->get_shift()) {
+ if (mode == MODE_EDIT && mm->is_shift_pressed()) {
Vector2 old_point = pre_move_edit.get(selected_point.vertex);
if (ABS(cpoint.x - old_point.x) > ABS(cpoint.y - old_point.y)) {
cpoint.y = old_point.y;
diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp
index f7c0ebcfaf..b6dd9474d3 100644
--- a/editor/plugins/animation_blend_space_1d_editor.cpp
+++ b/editor/plugins/animation_blend_space_1d_editor.cpp
@@ -386,7 +386,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) {
} else {
String type = menu->get_item_metadata(p_index);
- Object *obj = ClassDB::instance(type);
+ Object *obj = ClassDB::instantiate(type);
ERR_FAIL_COND(!obj);
AnimationNode *an = Object::cast_to<AnimationNode>(obj);
ERR_FAIL_COND(!an);
@@ -413,7 +413,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) {
void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) {
Ref<AnimationNodeAnimation> anim;
- anim.instance();
+ anim.instantiate();
anim->set_animation(animations_to_add[p_index]);
@@ -594,7 +594,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
add_child(top_hb);
Ref<ButtonGroup> bg;
- bg.instance();
+ bg.instantiate();
tool_blend = memnew(Button);
tool_blend->set_flat(true);
diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp
index e719df53d5..359df95bce 100644
--- a/editor/plugins/animation_blend_space_2d_editor.cpp
+++ b/editor/plugins/animation_blend_space_2d_editor.cpp
@@ -308,7 +308,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) {
} else {
String type = menu->get_item_metadata(p_index);
- Object *obj = ClassDB::instance(type);
+ Object *obj = ClassDB::instantiate(type);
ERR_FAIL_COND(!obj);
AnimationNode *an = Object::cast_to<AnimationNode>(obj);
ERR_FAIL_COND(!an);
@@ -335,7 +335,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) {
void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) {
Ref<AnimationNodeAnimation> anim;
- anim.instance();
+ anim.instantiate();
anim->set_animation(animations_to_add[p_index]);
@@ -818,7 +818,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
add_child(top_hb);
Ref<ButtonGroup> bg;
- bg.instance();
+ bg.instantiate();
tool_blend = memnew(Button);
tool_blend->set_flat(true);
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index 48fb507bb1..dcde89f177 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -139,7 +139,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
name->set_expand_to_text_length_enabled(true);
node->add_child(name);
node->set_slot(0, false, 0, Color(), true, 0, get_theme_color("font_color", "Label"));
- name->connect("text_entered", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed), varray(agnode), CONNECT_DEFERRED);
+ name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed), varray(agnode), CONNECT_DEFERRED);
name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out), varray(name, agnode), CONNECT_DEFERRED);
base = 1;
node->set_show_close_button(true);
@@ -231,18 +231,16 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected), varray(options, E->get()), CONNECT_DEFERRED);
}
- if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) {
- Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode");
- Color c = sb->get_border_color();
- Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0);
- mono_color.a = 0.85;
- c = mono_color;
-
- node->add_theme_color_override("title_color", c);
- c.a = 0.7;
- node->add_theme_color_override("close_color", c);
- node->add_theme_color_override("resizer_color", c);
- }
+ Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode");
+ Color c = sb->get_border_color();
+ Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0);
+ mono_color.a = 0.85;
+ c = mono_color;
+
+ node->add_theme_color_override("title_color", c);
+ c.a = 0.7;
+ node->add_theme_color_override("close_color", c);
+ node->add_theme_color_override("resizer_color", c);
}
List<AnimationNodeBlendTree::NodeConnection> connections;
@@ -290,14 +288,14 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
ERR_FAIL_COND(!anode.is_valid());
base_name = anode->get_class();
} else if (add_options[p_idx].type != String()) {
- AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(add_options[p_idx].type));
+ AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(add_options[p_idx].type));
ERR_FAIL_COND(!an);
anode = Ref<AnimationNode>(an);
base_name = add_options[p_idx].name;
} else {
ERR_FAIL_COND(add_options[p_idx].script.is_null());
String base_type = add_options[p_idx].script->get_instance_base_type();
- AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(base_type));
+ AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(base_type));
ERR_FAIL_COND(!an);
anode = Ref<AnimationNode>(an);
anode->set_script(add_options[p_idx].script);
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 612a8f30a4..2b92943f7e 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -121,11 +121,11 @@ void AnimationPlayerEditor::_notification(int p_what) {
Ref<Image> reset_img = reset_icon->get_image();
Ref<Image> autoplay_reset_img;
Size2 icon_size = Size2(autoplay_img->get_width(), autoplay_img->get_height());
- autoplay_reset_img.instance();
+ autoplay_reset_img.instantiate();
autoplay_reset_img->create(icon_size.x * 2, icon_size.y, false, autoplay_img->get_format());
autoplay_reset_img->blit_rect(autoplay_img, Rect2(Point2(), icon_size), Point2());
autoplay_reset_img->blit_rect(reset_img, Rect2(Point2(), icon_size), Point2(icon_size.x, 0));
- autoplay_reset_icon.instance();
+ autoplay_reset_icon.instantiate();
autoplay_reset_icon->create_from_image(autoplay_reset_img);
}
stop->set_icon(get_theme_icon("Stop", "EditorIcons"));
@@ -569,8 +569,10 @@ void AnimationPlayerEditor::_animation_blend() {
blend_editor.dialog->popup_centered(Size2(400, 400) * EDSCALE);
blend_editor.tree->set_hide_root(true);
- blend_editor.tree->set_column_min_width(0, 10);
- blend_editor.tree->set_column_min_width(1, 3);
+ blend_editor.tree->set_column_expand_ratio(0, 10);
+ blend_editor.tree->set_column_clip_content(0, true);
+ blend_editor.tree->set_column_expand_ratio(1, 3);
+ blend_editor.tree->set_column_clip_content(1, true);
List<StringName> anims;
player->get_animation_list(&anims);
@@ -994,7 +996,7 @@ void AnimationPlayerEditor::_animation_duplicate() {
}
}
-void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) {
+void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool p_timeline_only) {
if (updating || !player || player->is_playing()) {
return;
};
@@ -1015,18 +1017,18 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) {
pos = Math::snapped(pos, _get_editor_step());
}
- if (player->is_valid() && !p_set) {
- float cpos = player->get_current_animation_position();
+ if (!p_timeline_only) {
+ if (player->is_valid() && !p_set) {
+ float cpos = player->get_current_animation_position();
- player->seek_delta(pos, pos - cpos);
- } else {
- player->stop(true);
- player->seek(pos, true);
+ player->seek_delta(pos, pos - cpos);
+ } else {
+ player->stop(true);
+ player->seek(pos, true);
+ }
}
track_editor->set_anim_pos(pos);
-
- updating = true;
};
void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) {
@@ -1048,7 +1050,7 @@ void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len)
frame->set_max(p_len);
}
-void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag) {
+void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only) {
timeline_position = p_pos;
if (!is_visible_in_tree()) {
@@ -1070,7 +1072,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag)
updating = true;
frame->set_value(Math::snapped(p_pos, _get_editor_step()));
updating = false;
- _seek_value_changed(p_pos, !p_drag);
+ _seek_value_changed(p_pos, !p_drag, p_timeline_only);
}
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
@@ -1222,10 +1224,10 @@ void AnimationPlayerEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventKey> k = p_ev;
- if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->get_alt() && !k->get_control() && !k->get_metakey()) {
+ if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) {
switch (k->get_keycode()) {
case KEY_A: {
- if (!k->get_shift()) {
+ if (!k->is_shift_pressed()) {
_play_bw_from_pressed();
} else {
_play_bw_pressed();
@@ -1237,7 +1239,7 @@ void AnimationPlayerEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) {
accept_event();
} break;
case KEY_D: {
- if (!k->get_shift()) {
+ if (!k->is_shift_pressed()) {
_play_from_pressed();
} else {
_play_pressed();
@@ -1693,8 +1695,8 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected));
file->connect("file_selected", callable_mp(this, &AnimationPlayerEditor::_dialog_action));
- frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true));
- scale->connect("text_entered", callable_mp(this, &AnimationPlayerEditor::_scale_changed));
+ frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true, false));
+ scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed));
renaming = false;
last_active = false;
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 2f6bf55e4c..5c2348f86b 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -187,7 +187,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _scale_changed(const String &p_scale);
void _dialog_action(String p_file);
void _seek_frame_changed(const String &p_frame);
- void _seek_value_changed(float p_value, bool p_set = false);
+ void _seek_value_changed(float p_value, bool p_set = false, bool p_timeline_only = false);
void _blend_editor_next_changed(const int p_idx);
void _list_changed();
@@ -197,7 +197,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_player_changed(Object *p_pl);
- void _animation_key_editor_seek(float p_pos, bool p_drag);
+ void _animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only = false);
void _animation_key_editor_anim_len_changed(float p_len);
void _unhandled_key_input(const Ref<InputEvent> &p_ev);
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index a9709bbb16..94e526922d 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -124,7 +124,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
}
// select node or push a field inside
- if (mb.is_valid() && !mb->get_shift() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb.is_valid() && !mb->is_shift_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
selected_transition_from = StringName();
selected_transition_to = StringName();
selected_node = StringName();
@@ -237,7 +237,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
}
//connect nodes
- if (mb.is_valid() && ((tool_select->is_pressed() && mb->get_shift()) || tool_connect->is_pressed()) && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) {
+ if (mb.is_valid() && ((tool_select->is_pressed() && mb->is_shift_pressed()) || tool_connect->is_pressed()) && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) {
for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected
connecting = true;
@@ -257,7 +257,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
} else {
Ref<AnimationNodeStateMachineTransition> tr;
- tr.instance();
+ tr.instantiate();
tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()));
updating = true;
@@ -423,7 +423,7 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) {
} else {
String type = menu->get_item_metadata(p_index);
- Object *obj = ClassDB::instance(type);
+ Object *obj = ClassDB::instantiate(type);
ERR_FAIL_COND(!obj);
AnimationNode *an = Object::cast_to<AnimationNode>(obj);
ERR_FAIL_COND(!an);
@@ -462,7 +462,7 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) {
void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) {
Ref<AnimationNodeAnimation> anim;
- anim.instance();
+ anim.instantiate();
anim->set_animation(animations_to_add[p_index]);
@@ -1213,7 +1213,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
add_child(top_hb);
Ref<ButtonGroup> bg;
- bg.instance();
+ bg.instantiate();
tool_select = memnew(Button);
tool_select->set_flat(true);
@@ -1329,7 +1329,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
name_edit = memnew(LineEdit);
name_edit_popup->add_child(name_edit);
name_edit->set_anchors_and_offsets_preset(PRESET_WIDE);
- name_edit->connect("text_entered", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited));
+ name_edit->connect("text_submitted", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited));
name_edit->connect("focus_exited", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited_focus_out));
open_file = memnew(EditorFileDialog);
diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp
index c33b06ff32..e90665f84d 100644
--- a/editor/plugins/animation_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_tree_editor_plugin.cpp
@@ -76,7 +76,7 @@ void AnimationTreeEditor::_update_path() {
}
Ref<ButtonGroup> group;
- group.instance();
+ group.instantiate();
Button *b = memnew(Button);
b->set_text("Root");
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index fd47d9964e..cd61ebd418 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -86,7 +86,7 @@ void EditorAssetLibraryItem::_bind_methods() {
EditorAssetLibraryItem::EditorAssetLibraryItem() {
Ref<StyleBoxEmpty> border;
- border.instance();
+ border.instantiate();
border->set_default_margin(SIDE_LEFT, 5 * EDSCALE);
border->set_default_margin(SIDE_RIGHT, 5 * EDSCALE);
border->set_default_margin(SIDE_BOTTOM, 5 * EDSCALE);
@@ -155,7 +155,7 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const
thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos);
Ref<ImageTexture> tex;
- tex.instance();
+ tex.instantiate();
tex->create_from_image(thumbnail);
preview_images[i].button->set_icon(tex);
@@ -240,7 +240,6 @@ void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, cons
preview.video_link = p_url;
preview.is_video = p_video;
preview.button = memnew(Button);
- preview.button->set_flat(true);
preview.button->set_icon(previews->get_theme_icon("ThumbnailWait", "EditorIcons"));
preview.button->set_toggle_mode(true);
preview.button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click), varray(p_id));
@@ -465,7 +464,7 @@ void EditorAssetLibraryItemDownload::_make_request() {
retry->hide();
download->cancel_request();
- download->set_download_file(EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip");
+ download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip");
Error err = download->request(host);
if (err != OK) {
@@ -703,7 +702,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB
PackedByteArray image_data = p_data;
if (use_cache) {
- String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
+ String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
FileAccess *file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ);
@@ -762,7 +761,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB
}
Ref<ImageTexture> tex;
- tex.instance();
+ tex.instantiate();
tex->create_from_image(image);
obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, tex);
@@ -782,7 +781,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons
if (p_code != HTTPClient::RESPONSE_NOT_MODIFIED) {
for (int i = 0; i < headers.size(); i++) {
if (headers[i].findn("ETag:") == 0) { // Save etag
- String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
+ String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
String new_etag = headers[i].substr(headers[i].find(":") + 1, headers[i].length()).strip_edges();
FileAccess *file;
@@ -830,7 +829,7 @@ void EditorAssetLibrary::_update_image_queue() {
List<int> to_delete;
for (Map<int, ImageQueue>::Element *E = image_queue.front(); E; E = E->next()) {
if (!E->get().active && current_images < max_images) {
- String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text());
+ String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text());
Vector<String> headers;
if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) {
@@ -1099,11 +1098,9 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const
Dictionary d;
{
- Variant js;
- String errs;
- int errl;
- JSON::parse(str, js, errs, errl);
- d = js;
+ JSON json;
+ json.parse(str);
+ d = json.get_data();
}
RequestType requested = requesting;
@@ -1438,7 +1435,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) {
library_scroll_bg->add_child(library_scroll);
Ref<StyleBoxEmpty> border2;
- border2.instance();
+ border2.instantiate();
border2->set_default_margin(SIDE_LEFT, 15 * EDSCALE);
border2->set_default_margin(SIDE_RIGHT, 35 * EDSCALE);
border2->set_default_margin(SIDE_BOTTOM, 15 * EDSCALE);
diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h
index 11eae9e041..c6ca1ecd4f 100644
--- a/editor/plugins/asset_library_editor_plugin.h
+++ b/editor/plugins/asset_library_editor_plugin.h
@@ -282,7 +282,7 @@ class EditorAssetLibrary : public PanelContainer {
void _search(int p_page = 0);
void _rerun_search(int p_ignore);
void _search_text_changed(const String &p_text = "");
- void _search_text_entered(const String &p_text = "");
+ void _search_text_submitted(const String &p_text = "");
void _api_request(const String &p_request, RequestType p_request_type, const String &p_arguments = "");
void _http_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data);
void _http_download_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data);
diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp
index 3553450672..3b54b30b54 100644
--- a/editor/plugins/audio_stream_editor_plugin.cpp
+++ b/editor/plugins/audio_stream_editor_plugin.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
+#include "core/os/keyboard.h"
#include "editor/audio_stream_preview.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
@@ -144,23 +145,26 @@ void AudioStreamEditor::_draw_indicator() {
Rect2 rect = _preview->get_rect();
float len = stream->get_length();
float ofs_x = _current / len * rect.size.width;
- _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), get_theme_color("accent_color", "Editor"), 1);
+ const Color color = get_theme_color("accent_color", "Editor");
+ _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), color, Math::round(2 * EDSCALE));
+ _indicator->draw_texture(
+ get_theme_icon("TimelineIndicator", "EditorIcons"),
+ Point2(ofs_x - get_theme_icon("TimelineIndicator", "EditorIcons")->get_width() * 0.5, 0),
+ color);
_current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /");
}
void AudioStreamEditor::_on_input_indicator(Ref<InputEvent> p_event) {
- Ref<InputEventMouseButton> mb = p_event;
-
- if (mb.is_valid()) {
+ const Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
if (mb->is_pressed()) {
_seek_to(mb->get_position().x);
}
_dragging = mb->is_pressed();
}
- Ref<InputEventMouseMotion> mm = p_event;
-
+ const Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (_dragging) {
_seek_to(mm->get_position().x);
@@ -228,6 +232,7 @@ AudioStreamEditor::AudioStreamEditor() {
hbox->add_child(_play_button);
_play_button->set_focus_mode(Control::FOCUS_NONE);
_play_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_play));
+ _play_button->set_shortcut(ED_SHORTCUT("inspector/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), KEY_SPACE));
_stop_button = memnew(Button);
_stop_button->set_flat(true);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 6ac47595dc..7282475ddf 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -332,7 +332,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
snap_target[0] = SNAP_TARGET_NONE;
snap_target[1] = SNAP_TARGET_NONE;
- bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+ bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CTRL);
// Smart snap using the canvas position
Vector2 output = p_target;
@@ -460,7 +460,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
}
float CanvasItemEditor::snap_angle(float p_target, float p_start) const {
- if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL)) && snap_rotation_step != 0) {
+ if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) && snap_rotation_step != 0) {
if (snap_relative) {
return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step);
} else {
@@ -481,11 +481,11 @@ void CanvasItemEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) {
}
if (k.is_valid()) {
- if (k->get_keycode() == KEY_CONTROL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT) {
+ if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT) {
viewport->update();
}
- if (k->is_pressed() && !k->get_control() && !k->is_echo()) {
+ if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo()) {
if ((grid_snap_active || show_grid) && multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->is_shortcut(p_ev)) {
// Multiply the grid size
grid_step_multiplier = MIN(grid_step_multiplier + 1, 12);
@@ -666,93 +666,6 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel
}
}
-void CanvasItemEditor::_get_bones_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items) {
- Point2 screen_pos = transform.xform(p_pos);
-
- for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) {
- Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().from));
-
- Vector<Vector2> bone_shape;
- if (!_get_bone_shape(&bone_shape, nullptr, E)) {
- continue;
- }
-
- // Check if the point is inside the Polygon2D
- if (Geometry2D::is_point_in_polygon(screen_pos, bone_shape)) {
- // Check if the item is already in the list
- bool duplicate = false;
- for (int i = 0; i < r_items.size(); i++) {
- if (r_items[i].item == from_node) {
- duplicate = true;
- break;
- }
- }
- if (duplicate) {
- continue;
- }
-
- // Else, add it
- _SelectResult res;
- res.item = from_node;
- res.z_index = from_node ? from_node->get_z_index() : 0;
- res.has_z = from_node;
- r_items.push_back(res);
- }
- }
-}
-
-bool CanvasItemEditor::_get_bone_shape(Vector<Vector2> *shape, Vector<Vector2> *outline_shape, Map<BoneKey, BoneList>::Element *bone) {
- int bone_width = EditorSettings::get_singleton()->get("editors/2d/bone_width");
- int bone_outline_width = EditorSettings::get_singleton()->get("editors/2d/bone_outline_size");
-
- Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(bone->key().from));
- Node2D *to_node = Object::cast_to<Node2D>(ObjectDB::get_instance(bone->key().to));
-
- if (!from_node) {
- return false;
- }
- if (!from_node->is_inside_tree()) {
- return false; //may have been removed
- }
-
- if (!to_node && bone->get().length == 0) {
- return false;
- }
-
- Vector2 from = transform.xform(from_node->get_global_position());
- Vector2 to;
-
- if (to_node) {
- to = transform.xform(to_node->get_global_position());
- } else {
- to = transform.xform(from_node->get_global_transform().xform(Vector2(bone->get().length, 0)));
- }
-
- Vector2 rel = to - from;
- Vector2 relt = rel.orthogonal().normalized() * bone_width;
- Vector2 reln = rel.normalized();
- Vector2 reltn = relt.normalized();
-
- if (shape) {
- shape->clear();
- shape->push_back(from);
- shape->push_back(from + rel * 0.2 + relt);
- shape->push_back(to);
- shape->push_back(from + rel * 0.2 - relt);
- }
-
- if (outline_shape) {
- outline_shape->clear();
- outline_shape->push_back(from + (-reln - reltn) * bone_outline_width);
- outline_shape->push_back(from + (-reln + reltn) * bone_outline_width);
- outline_shape->push_back(from + rel * 0.2 + relt + reltn * bone_outline_width);
- outline_shape->push_back(to + (reln + reltn) * bone_outline_width);
- outline_shape->push_back(to + (reln - reltn) * bone_outline_width);
- outline_shape->push_back(from + rel * 0.2 - relt - reltn * bone_outline_width);
- }
- return true;
-}
-
void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {
if (!p_node) {
return;
@@ -886,50 +799,6 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2
return output;
}
-void CanvasItemEditor::_save_canvas_item_ik_chain(const CanvasItem *p_canvas_item, List<float> *p_bones_length, List<Dictionary> *p_bones_state) {
- if (p_bones_length) {
- *p_bones_length = List<float>();
- }
- if (p_bones_state) {
- *p_bones_state = List<Dictionary>();
- }
-
- const Node2D *bone = Object::cast_to<Node2D>(p_canvas_item);
- if (bone && bone->has_meta("_edit_bone_")) {
- // Check if we have an IK chain
- List<const Node2D *> bone_ik_list;
- bool ik_found = false;
- bone = Object::cast_to<Node2D>(bone->get_parent());
- while (bone) {
- bone_ik_list.push_back(bone);
- if (bone->has_meta("_edit_ik_")) {
- ik_found = true;
- break;
- } else if (!bone->has_meta("_edit_bone_")) {
- break;
- }
- bone = Object::cast_to<Node2D>(bone->get_parent());
- }
-
- //Save the bone state and length if we have an IK chain
- if (ik_found) {
- bone = Object::cast_to<Node2D>(p_canvas_item);
- Transform2D bone_xform = bone->get_global_transform();
- for (List<const Node2D *>::Element *bone_E = bone_ik_list.front(); bone_E; bone_E = bone_E->next()) {
- bone_xform = bone_xform * bone->get_transform().affine_inverse();
- const Node2D *parent_bone = bone_E->get();
- if (p_bones_length) {
- p_bones_length->push_back(parent_bone->get_global_transform().get_origin().distance_to(bone->get_global_position()));
- }
- if (p_bones_state) {
- p_bones_state->push_back(parent_bone->_edit_get_state());
- }
- bone = parent_bone;
- }
- }
- }
-}
-
void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) {
for (List<CanvasItem *>::Element *E = p_canvas_items.front(); E; E = E->next()) {
CanvasItem *canvas_item = E->get();
@@ -942,31 +811,15 @@ void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items
} else {
se->pre_drag_rect = Rect2();
}
-
- // If we have a bone, save the state of all nodes in the IK chain
- _save_canvas_item_ik_chain(canvas_item, &(se->pre_drag_bones_length), &(se->pre_drag_bones_undo_state));
}
}
}
-void CanvasItemEditor::_restore_canvas_item_ik_chain(CanvasItem *p_canvas_item, const List<Dictionary> *p_bones_state) {
- CanvasItem *canvas_item = p_canvas_item;
- for (const List<Dictionary>::Element *E = p_bones_state->front(); E; E = E->next()) {
- canvas_item = Object::cast_to<CanvasItem>(canvas_item->get_parent());
- canvas_item->_edit_set_state(E->get());
- }
-}
-
void CanvasItemEditor::_restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones) {
for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
CanvasItem *canvas_item = E->get();
CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
- if (se) {
- canvas_item->_edit_set_state(se->undo_state);
- if (restore_bones) {
- _restore_canvas_item_ik_chain(canvas_item, &(se->pre_drag_bones_undo_state));
- }
- }
+ canvas_item->_edit_set_state(se->undo_state);
}
}
@@ -1269,12 +1122,12 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve
bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid() && !p_already_accepted) {
- const bool pan_on_scroll = bool(EditorSettings::get_singleton()->get("editors/2d/scroll_to_pan")) && !b->get_control();
+ const bool pan_on_scroll = bool(EditorSettings::get_singleton()->get("editors/2d/scroll_to_pan")) && !b->is_ctrl_pressed();
if (pan_on_scroll) {
// Perform horizontal scrolling first so we can check for Shift being held.
if (b->is_pressed() &&
- (b->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || (b->get_shift() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP))) {
+ (b->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || (b->is_shift_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP))) {
// Pan left
view_offset.x -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
update_viewport();
@@ -1282,7 +1135,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
}
if (b->is_pressed() &&
- (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->get_shift() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN))) {
+ (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->is_shift_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN))) {
// Pan right
view_offset.x += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
update_viewport();
@@ -1296,11 +1149,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
update_viewport();
} else {
- float new_zoom = _get_next_zoom_value(-1);
+ zoom_widget->set_zoom_by_increments(-1);
if (b->get_factor() != 1.f) {
- new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f);
+ zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f));
}
- _zoom_on_position(new_zoom, b->get_position());
+ _zoom_on_position(zoom_widget->get_zoom(), b->get_position());
}
return true;
}
@@ -1311,11 +1164,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor();
update_viewport();
} else {
- float new_zoom = _get_next_zoom_value(1);
+ zoom_widget->set_zoom_by_increments(1);
if (b->get_factor() != 1.f) {
- new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f);
+ zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f));
}
- _zoom_on_position(new_zoom, b->get_position());
+ _zoom_on_position(zoom_widget->get_zoom(), b->get_position());
}
return true;
}
@@ -1388,15 +1241,16 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid() && !p_already_accepted) {
- // If control key pressed, then zoom instead of pan
- if (pan_gesture->get_control()) {
+ // If ctrl key pressed, then zoom instead of pan.
+ if (pan_gesture->is_ctrl_pressed()) {
const float factor = pan_gesture->get_delta().y;
- float new_zoom = _get_next_zoom_value(-1);
+ zoom_widget->set_zoom_by_increments(1);
if (factor != 1.f) {
- new_zoom = zoom * ((new_zoom / zoom - 1.f) * factor + 1.f);
+ zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * factor + 1.f));
}
- _zoom_on_position(new_zoom, pan_gesture->get_position());
+ _zoom_on_position(zoom_widget->get_zoom(), pan_gesture->get_position());
+
return true;
}
@@ -1496,76 +1350,6 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
return false;
}
-void CanvasItemEditor::_solve_IK(Node2D *leaf_node, Point2 target_position) {
- CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(leaf_node);
- if (se) {
- int nb_bones = se->pre_drag_bones_undo_state.size();
- if (nb_bones > 0) {
- // Build the node list
- Point2 leaf_pos = target_position;
-
- List<Node2D *> joints_list;
- List<Point2> joints_pos;
- Node2D *joint = leaf_node;
- Transform2D joint_transform = leaf_node->get_global_transform_with_canvas();
- for (int i = 0; i < nb_bones + 1; i++) {
- joints_list.push_back(joint);
- joints_pos.push_back(joint_transform.get_origin());
- joint_transform = joint_transform * joint->get_transform().affine_inverse();
- joint = Object::cast_to<Node2D>(joint->get_parent());
- }
- Point2 root_pos = joints_list.back()->get()->get_global_transform_with_canvas().get_origin();
-
- // Restraints the node to a maximum distance is necessary
- float total_len = 0;
- for (List<float>::Element *E = se->pre_drag_bones_length.front(); E; E = E->next()) {
- total_len += E->get();
- }
- if ((root_pos.distance_to(leaf_pos)) > total_len) {
- Vector2 rel = leaf_pos - root_pos;
- rel = rel.normalized() * total_len;
- leaf_pos = root_pos + rel;
- }
- joints_pos[0] = leaf_pos;
-
- // Run the solver
- int solver_iterations = 64;
- float solver_k = 0.3;
-
- // Build the position list
- for (int i = 0; i < solver_iterations; i++) {
- // Handle the leaf joint
- int node_id = 0;
- for (List<float>::Element *E = se->pre_drag_bones_length.front(); E; E = E->next()) {
- Vector2 direction = (joints_pos[node_id + 1] - joints_pos[node_id]).normalized();
- int len = E->get();
- if (E == se->pre_drag_bones_length.front()) {
- joints_pos[1] = joints_pos[1].lerp(joints_pos[0] + len * direction, solver_k);
- } else if (E == se->pre_drag_bones_length.back()) {
- joints_pos[node_id] = joints_pos[node_id].lerp(joints_pos[node_id + 1] - len * direction, solver_k);
- } else {
- Vector2 center = (joints_pos[node_id + 1] + joints_pos[node_id]) / 2.0;
- joints_pos[node_id] = joints_pos[node_id].lerp(center - (direction * len) / 2.0, solver_k);
- joints_pos[node_id + 1] = joints_pos[node_id + 1].lerp(center + (direction * len) / 2.0, solver_k);
- }
- node_id++;
- }
- }
-
- // Set the position
- for (int node_id = joints_list.size() - 1; node_id > 0; node_id--) {
- Point2 current = (joints_list[node_id - 1]->get_global_position() - joints_list[node_id]->get_global_position()).normalized();
- Point2 target = (joints_pos[node_id - 1] - joints_list[node_id]->get_global_position()).normalized();
- float rot = current.angle_to(target);
- if (joints_list[node_id]->get_global_transform().basis_determinant() < 0) {
- rot = -rot;
- }
- joints_list[node_id]->rotate(rot);
- }
- }
- }
-}
-
bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
Ref<InputEventMouseMotion> m = p_event;
@@ -1573,7 +1357,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
// Start rotation
if (drag_type == DRAG_NONE) {
if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) {
- if ((b->get_command() && !b->get_alt() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) {
+ if ((b->is_command_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) {
List<CanvasItem *> selection = _get_edited_canvas_items();
// Remove not movable nodes
@@ -1654,7 +1438,7 @@ bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEven
Ref<InputEventMouseButton> b = p_event;
// Open a sub-scene on double-click
- if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && b->is_doubleclick() && tool == TOOL_SELECT) {
+ if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) {
List<CanvasItem *> selection = _get_edited_canvas_items();
if (selection.size() == 1) {
CanvasItem *canvas_item = selection[0];
@@ -1739,7 +1523,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) {
Vector2 new_anchor = xform.xform(snap_point(previous_anchor + (drag_to - drag_from), SNAP_GRID | SNAP_OTHER_NODES, SNAP_NODE_PARENT | SNAP_NODE_SIDES | SNAP_NODE_CENTER, control));
new_anchor = _position_to_anchor(control, new_anchor).snapped(Vector2(0.001, 0.001));
- bool use_single_axis = m->get_shift();
+ bool use_single_axis = m->is_shift_pressed();
Vector2 drag_vector = xform.xform(drag_to) - xform.xform(drag_from);
bool use_y = Math::abs(drag_vector.y) > Math::abs(drag_vector.x);
@@ -1887,8 +1671,8 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) {
//Reset state
canvas_item->_edit_set_state(se->undo_state);
- bool uniform = m->get_shift();
- bool symmetric = m->get_alt();
+ bool uniform = m->is_shift_pressed();
+ bool symmetric = m->is_alt_pressed();
Rect2 local_rect = canvas_item->_edit_get_rect();
float aspect = local_rect.get_size().y / local_rect.get_size().x;
@@ -2027,7 +1811,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
// Drag resize handles
if (drag_type == DRAG_NONE) {
- if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && ((b->get_alt() && b->get_control()) || tool == TOOL_SCALE)) {
+ if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_ctrl_pressed()) || tool == TOOL_SCALE)) {
List<CanvasItem *> selection = _get_edited_canvas_items();
if (selection.size() == 1) {
CanvasItem *canvas_item = selection[0];
@@ -2073,8 +1857,8 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
Transform2D unscaled_transform = (transform * parent_xform * canvas_item->_edit_get_transform()).orthonormalized();
Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;
- bool uniform = m->get_shift();
- bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+ bool uniform = m->is_shift_pressed();
+ bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL);
Point2 drag_from_local = simple_xform.xform(drag_from);
Point2 drag_to_local = simple_xform.xform(drag_to);
@@ -2166,7 +1950,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
if (drag_type == DRAG_NONE) {
//Start moving the nodes
if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) {
- if ((b->get_alt() && !b->get_control()) || tool == TOOL_MOVE) {
+ if ((b->is_alt_pressed() && !b->is_ctrl_pressed()) || tool == TOOL_MOVE) {
List<CanvasItem *> selection = _get_edited_canvas_items();
drag_selection.clear();
@@ -2207,14 +1991,6 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
if (drag_type == DRAG_MOVE || drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) {
// Move the nodes
if (m.is_valid()) {
- // Save the ik chain for reapplying before IK solve
- Vector<List<Dictionary>> all_bones_ik_states;
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- List<Dictionary> bones_ik_states;
- _save_canvas_item_ik_chain(E->get(), nullptr, &bones_ik_states);
- all_bones_ik_states.push_back(bones_ik_states);
- }
-
_restore_canvas_item_state(drag_selection, true);
drag_to = transform.affine_inverse().xform(m->get_position());
@@ -2234,7 +2010,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
new_pos.x = previous_pos.x;
}
- bool single_axis = m->get_shift();
+ bool single_axis = m->is_shift_pressed();
if (single_axis) {
if (ABS(new_pos.x - previous_pos.x) > ABS(new_pos.y - previous_pos.y)) {
new_pos.y = previous_pos.y;
@@ -2243,25 +2019,12 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
}
}
- bool force_no_IK = m->get_alt();
int index = 0;
for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
CanvasItem *canvas_item = E->get();
- CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
- if (se) {
- Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform();
-
- Node2D *node2d = Object::cast_to<Node2D>(canvas_item);
- if (node2d && se->pre_drag_bones_undo_state.size() > 0 && !force_no_IK) {
- real_t initial_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation();
- _restore_canvas_item_ik_chain(node2d, &(all_bones_ik_states[index]));
- real_t final_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation();
- node2d->rotate(initial_leaf_node_rotation - final_leaf_node_rotation);
- _solve_IK(node2d, new_pos);
- } else {
- canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
- }
- }
+ Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform();
+
+ canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
index++;
}
return true;
@@ -2324,18 +2087,10 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
}
if (drag_selection.size() > 0) {
- // Save the ik chain for reapplying before IK solve
- Vector<List<Dictionary>> all_bones_ik_states;
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- List<Dictionary> bones_ik_states;
- _save_canvas_item_ik_chain(E->get(), nullptr, &bones_ik_states);
- all_bones_ik_states.push_back(bones_ik_states);
- }
-
_restore_canvas_item_state(drag_selection, true);
- bool move_local_base = k->get_alt();
- bool move_local_base_rotated = k->get_control() || k->get_metakey();
+ bool move_local_base = k->is_alt_pressed();
+ bool move_local_base_rotated = k->is_ctrl_pressed() || k->is_meta_pressed();
Vector2 dir;
if (k->get_keycode() == KEY_UP) {
@@ -2347,12 +2102,12 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
} else if (k->get_keycode() == KEY_RIGHT) {
dir += Vector2(1, 0);
}
- if (k->get_shift()) {
+ if (k->is_shift_pressed()) {
dir *= grid_step * Math::pow(2.0, grid_step_multiplier);
}
drag_to += dir;
- if (k->get_shift()) {
+ if (k->is_shift_pressed()) {
drag_to = drag_to.snapped(grid_step * Math::pow(2.0, grid_step_multiplier));
}
@@ -2383,21 +2138,9 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
int index = 0;
for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
CanvasItem *canvas_item = E->get();
- CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
- if (se) {
- Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform();
-
- Node2D *node2d = Object::cast_to<Node2D>(canvas_item);
- if (node2d && se->pre_drag_bones_undo_state.size() > 0) {
- real_t initial_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation();
- _restore_canvas_item_ik_chain(node2d, &(all_bones_ik_states[index]));
- real_t final_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation();
- node2d->rotate(initial_leaf_node_rotation - final_leaf_node_rotation);
- _solve_IK(node2d, new_pos);
- } else {
- canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
- }
- }
+ Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform();
+
+ canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
index++;
}
}
@@ -2441,18 +2184,18 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
if (drag_type == DRAG_NONE) {
if (b.is_valid() &&
- ((b->get_button_index() == MOUSE_BUTTON_RIGHT && b->get_alt() && tool == TOOL_SELECT) ||
+ ((b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) ||
(b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_LIST_SELECT))) {
// Popup the selection menu list
Point2 click = transform.affine_inverse().xform(b->get_position());
- _get_canvas_items_at_pos(click, selection_results, b->get_alt() && tool != TOOL_LIST_SELECT);
+ _get_canvas_items_at_pos(click, selection_results, b->is_alt_pressed() && tool != TOOL_LIST_SELECT);
if (selection_results.size() == 1) {
CanvasItem *item = selection_results[0].item;
selection_results.clear();
- _select_click_on_item(item, click, b->get_shift());
+ _select_click_on_item(item, click, b->is_shift_pressed());
return true;
} else if (!selection_results.is_empty()) {
@@ -2496,14 +2239,14 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
selection_menu->set_item_tooltip(i, String(item->get_name()) + "\nType: " + item->get_class() + "\nPath: " + node_path);
}
- selection_menu_additive_selection = b->get_shift();
+ selection_menu_additive_selection = b->is_shift_pressed();
selection_menu->set_position(get_screen_transform().xform(b->get_position()));
selection_menu->popup();
return true;
}
}
- if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->get_control()) {
+ if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_ctrl_pressed()) {
add_node_menu->set_position(get_global_transform().xform(get_local_mouse_position()));
add_node_menu->set_size(Vector2(1, 1));
add_node_menu->popup();
@@ -2523,23 +2266,17 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
// Find the item to select
CanvasItem *canvas_item = nullptr;
- // Retrieve the bones
Vector<_SelectResult> selection = Vector<_SelectResult>();
- _get_bones_at_pos(click, selection);
+ // Retrieve the canvas items
+ selection = Vector<_SelectResult>();
+ _get_canvas_items_at_pos(click, selection);
if (!selection.is_empty()) {
canvas_item = selection[0].item;
- } else {
- // Retrieve the canvas items
- selection = Vector<_SelectResult>();
- _get_canvas_items_at_pos(click, selection);
- if (!selection.is_empty()) {
- canvas_item = selection[0].item;
- }
}
if (!canvas_item) {
// Start a box selection
- if (!b->get_shift()) {
+ if (!b->is_shift_pressed()) {
// Clear the selection if not additive
editor_selection->clear();
viewport->update();
@@ -2551,26 +2288,42 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
box_selecting_to = drag_from;
return true;
} else {
- bool still_selected = _select_click_on_item(canvas_item, click, b->get_shift());
+ bool still_selected = _select_click_on_item(canvas_item, click, b->is_shift_pressed());
// Start dragging
if (still_selected) {
// Drag the node(s) if requested
- List<CanvasItem *> selection2 = _get_edited_canvas_items();
+ drag_start_origin = click;
+ drag_type = DRAG_QUEUED;
+ }
+ // Select the item
+ return true;
+ }
+ }
+ }
- drag_selection.clear();
- for (int i = 0; i < selection2.size(); i++) {
- if (_is_node_movable(selection2[i], true)) {
- drag_selection.push_back(selection2[i]);
- }
- }
+ if (drag_type == DRAG_QUEUED) {
+ if (b.is_valid() && !b->is_pressed()) {
+ drag_type = DRAG_NONE;
+ return true;
+ }
+ if (m.is_valid()) {
+ Point2 click = transform.affine_inverse().xform(m->get_position());
+ bool movement_threshold_passed = drag_start_origin.distance_to(click) > 10 * EDSCALE;
+ if (m.is_valid() && movement_threshold_passed) {
+ List<CanvasItem *> selection2 = _get_edited_canvas_items();
- if (selection2.size() > 0) {
- drag_type = DRAG_MOVE;
- drag_from = click;
- _save_canvas_item_state(drag_selection);
+ drag_selection.clear();
+ for (int i = 0; i < selection2.size(); i++) {
+ if (_is_node_movable(selection2[i], true)) {
+ drag_selection.push_back(selection2[i]);
}
}
- // Select the item
+
+ if (selection2.size() > 0) {
+ drag_type = DRAG_MOVE;
+ drag_from = click;
+ _save_canvas_item_state(drag_selection);
+ }
return true;
}
}
@@ -3573,7 +3326,7 @@ void CanvasItemEditor::_draw_selection() {
}
// Draw the move handles
- bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+ bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL);
bool is_alt = Input::get_singleton()->is_key_pressed(KEY_ALT);
if (tool == TOOL_MOVE && show_transformation_gizmos) {
if (_is_node_movable(canvas_item)) {
@@ -3733,65 +3486,6 @@ void CanvasItemEditor::_draw_axis() {
}
}
-void CanvasItemEditor::_draw_bones() {
- RID ci = viewport->get_canvas_item();
-
- if (skeleton_show_bones) {
- Color bone_color1 = EditorSettings::get_singleton()->get("editors/2d/bone_color1");
- Color bone_color2 = EditorSettings::get_singleton()->get("editors/2d/bone_color2");
- Color bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color");
- Color bone_outline_color = EditorSettings::get_singleton()->get("editors/2d/bone_outline_color");
- Color bone_selected_color = EditorSettings::get_singleton()->get("editors/2d/bone_selected_color");
-
- for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) {
- Vector<Vector2> bone_shape;
- Vector<Vector2> bone_shape_outline;
- if (!_get_bone_shape(&bone_shape, &bone_shape_outline, E)) {
- continue;
- }
-
- Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().from));
- if (!from_node->is_visible_in_tree()) {
- continue;
- }
-
- Vector<Color> colors;
- if (from_node->has_meta("_edit_ik_")) {
- colors.push_back(bone_ik_color);
- colors.push_back(bone_ik_color);
- colors.push_back(bone_ik_color);
- colors.push_back(bone_ik_color);
- } else {
- colors.push_back(bone_color1);
- colors.push_back(bone_color2);
- colors.push_back(bone_color1);
- colors.push_back(bone_color2);
- }
-
- Vector<Color> outline_colors;
-
- if (editor_selection->is_selected(from_node)) {
- outline_colors.push_back(bone_selected_color);
- outline_colors.push_back(bone_selected_color);
- outline_colors.push_back(bone_selected_color);
- outline_colors.push_back(bone_selected_color);
- outline_colors.push_back(bone_selected_color);
- outline_colors.push_back(bone_selected_color);
- } else {
- outline_colors.push_back(bone_outline_color);
- outline_colors.push_back(bone_outline_color);
- outline_colors.push_back(bone_outline_color);
- outline_colors.push_back(bone_outline_color);
- outline_colors.push_back(bone_outline_color);
- outline_colors.push_back(bone_outline_color);
- }
-
- RenderingServer::get_singleton()->canvas_item_add_polygon(ci, bone_shape_outline, outline_colors);
- RenderingServer::get_singleton()->canvas_item_add_primitive(ci, bone_shape, colors, Vector<Vector2>(), RID());
- }
- }
-}
-
void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {
ERR_FAIL_COND(!p_node);
@@ -3907,72 +3601,6 @@ void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p
}
}
-bool CanvasItemEditor::_build_bones_list(Node *p_node) {
- ERR_FAIL_COND_V(!p_node, false);
-
- bool has_child_bones = false;
-
- for (int i = 0; i < p_node->get_child_count(); i++) {
- if (_build_bones_list(p_node->get_child(i))) {
- has_child_bones = true;
- }
- }
-
- CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node);
- Node *scene = editor->get_edited_scene();
- if (!canvas_item || !canvas_item->is_visible() || (canvas_item != scene && canvas_item->get_owner() != scene && canvas_item != scene->get_deepest_editable_node(canvas_item))) {
- return false;
- }
-
- Node *parent = canvas_item->get_parent();
-
- if (Object::cast_to<Bone2D>(canvas_item)) {
- if (Object::cast_to<Bone2D>(parent)) {
- // Add as bone->parent relationship
- BoneKey bk;
- bk.from = parent->get_instance_id();
- bk.to = canvas_item->get_instance_id();
- if (!bone_list.has(bk)) {
- BoneList b;
- b.length = 0;
- bone_list[bk] = b;
- }
-
- bone_list[bk].last_pass = bone_last_frame;
- }
-
- if (!has_child_bones) {
- // Add a last bone if the Bone2D has no Bone2D child
- BoneKey bk;
- bk.from = canvas_item->get_instance_id();
- bk.to = ObjectID();
- if (!bone_list.has(bk)) {
- BoneList b;
- b.length = 0;
- bone_list[bk] = b;
- }
- bone_list[bk].last_pass = bone_last_frame;
- }
-
- return true;
- }
-
- if (canvas_item->has_meta("_edit_bone_")) {
- // Add a "custom bone"
- BoneKey bk;
- bk.from = parent->get_instance_id();
- bk.to = canvas_item->get_instance_id();
- if (!bone_list.has(bk)) {
- BoneList b;
- b.length = 0;
- bone_list[bk] = b;
- }
- bone_list[bk].last_pass = bone_last_frame;
- }
-
- return false;
-}
-
void CanvasItemEditor::_draw_viewport() {
// Update the transform
transform = Transform2D();
@@ -4032,7 +3660,6 @@ void CanvasItemEditor::_draw_viewport() {
force_over_plugin_list->forward_canvas_force_draw_over_viewport(viewport);
}
- _draw_bones();
if (show_rulers) {
_draw_rulers();
}
@@ -4158,8 +3785,8 @@ void CanvasItemEditor::_notification(int p_what) {
}
Bone2D *bone = Object::cast_to<Bone2D>(b);
- if (bone && bone->get_default_length() != E->get().length) {
- E->get().length = bone->get_default_length();
+ if (bone && bone->get_length() != E->get().length) {
+ E->get().length = bone->get_length();
viewport->update();
}
}
@@ -4174,18 +3801,11 @@ void CanvasItemEditor::_notification(int p_what) {
AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));
_keying_changed();
- get_tree()->connect("node_added", callable_mp(this, &CanvasItemEditor::_tree_changed), varray());
- get_tree()->connect("node_removed", callable_mp(this, &CanvasItemEditor::_tree_changed), varray());
} else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
select_sb->set_texture(get_theme_icon("EditorRect2D", "EditorIcons"));
}
- if (p_what == NOTIFICATION_EXIT_TREE) {
- get_tree()->disconnect("node_added", callable_mp(this, &CanvasItemEditor::_tree_changed));
- get_tree()->disconnect("node_removed", callable_mp(this, &CanvasItemEditor::_tree_changed));
- }
-
if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
select_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons"));
list_select_button->set_icon(get_theme_icon("ListSelect", "EditorIcons"));
@@ -4219,9 +3839,6 @@ void CanvasItemEditor::_notification(int p_what) {
key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55));
animation_menu->set_icon(get_theme_icon("GuiTabMenuHl", "EditorIcons"));
- zoom_minus->set_icon(get_theme_icon("ZoomLess", "EditorIcons"));
- zoom_plus->set_icon(get_theme_icon("ZoomMore", "EditorIcons"));
-
presets_menu->set_icon(get_theme_icon("ControlLayout", "EditorIcons"));
PopupMenu *p = presets_menu->get_popup();
@@ -4323,46 +3940,6 @@ void CanvasItemEditor::edit(CanvasItem *p_canvas_item) {
}
}
-void CanvasItemEditor::_queue_update_bone_list() {
- if (bone_list_dirty) {
- return;
- }
-
- call_deferred("_update_bone_list");
- bone_list_dirty = true;
-}
-
-void CanvasItemEditor::_update_bone_list() {
- bone_last_frame++;
-
- if (editor->get_edited_scene()) {
- _build_bones_list(editor->get_edited_scene());
- }
-
- List<Map<BoneKey, BoneList>::Element *> bone_to_erase;
- for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) {
- if (E->get().last_pass != bone_last_frame) {
- bone_to_erase.push_back(E);
- continue;
- }
-
- Node *node = Object::cast_to<Node>(ObjectDB::get_instance(E->key().from));
- if (!node || !node->is_inside_tree() || (node != get_tree()->get_edited_scene_root() && !get_tree()->get_edited_scene_root()->is_a_parent_of(node))) {
- bone_to_erase.push_back(E);
- continue;
- }
- }
- while (bone_to_erase.size()) {
- bone_list.erase(bone_to_erase.front()->get());
- bone_to_erase.pop_front();
- }
- bone_list_dirty = false;
-}
-
-void CanvasItemEditor::_tree_changed(Node *) {
- _queue_update_bone_list();
-}
-
void CanvasItemEditor::_update_scrollbars() {
updating_scroll = true;
@@ -4378,11 +3955,9 @@ void CanvasItemEditor::_update_scrollbars() {
Size2 screen_rect = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height));
- _queue_update_bone_list();
-
// Calculate scrollable area.
Rect2 canvas_item_rect = Rect2(Point2(), screen_rect);
- if (editor->get_edited_scene()) {
+ if (editor->is_inside_tree() && editor->get_edited_scene()) {
Rect2 content_rect = _get_encompassing_rect(editor->get_edited_scene());
canvas_item_rect.expand_to(content_rect.position);
canvas_item_rect.expand_to(content_rect.position + content_rect.size);
@@ -4579,33 +4154,6 @@ void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) {
undo_redo->commit_action();
}
-float CanvasItemEditor::_get_next_zoom_value(int p_increment_count) const {
- // Base increment factor defined as the twelveth root of two.
- // This allow a smooth geometric evolution of the zoom, with the advantage of
- // visiting all integer power of two scale factors.
- // note: this is analogous to the 'semitones' interval in the music world
- // In order to avoid numerical imprecisions, we compute and edit a zoom index
- // with the following relation: zoom = 2 ^ (index / 12)
-
- if (zoom < CMP_EPSILON || p_increment_count == 0) {
- return 1.f;
- }
-
- // Remove Editor scale from the index computation
- float zoom_noscale = zoom / MAX(1, EDSCALE);
-
- // zoom = 2**(index/12) => log2(zoom) = index/12
- float closest_zoom_index = Math::round(Math::log(zoom_noscale) * 12.f / Math::log(2.f));
-
- float new_zoom_index = closest_zoom_index + p_increment_count;
- float new_zoom = Math::pow(2.f, new_zoom_index / 12.f);
-
- // Restore Editor scale transformation
- new_zoom *= MAX(1, EDSCALE);
-
- return new_zoom;
-}
-
void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
@@ -4630,36 +4178,12 @@ void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
view_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;
}
- _update_zoom_label();
+ zoom_widget->set_zoom(zoom);
update_viewport();
}
-void CanvasItemEditor::_update_zoom_label() {
- String zoom_text;
- // The zoom level displayed is relative to the editor scale
- // (like in most image editors). Its lower bound is clamped to 1 as some people
- // lower the editor scale to increase the available real estate,
- // even if their display doesn't have a particularly low DPI.
- if (zoom >= 10) {
- // Don't show a decimal when the zoom level is higher than 1000 %.
- zoom_text = TS->format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100))) + " " + TS->percent_sign();
- } else {
- zoom_text = TS->format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, 0.1))) + " " + TS->percent_sign();
- }
-
- zoom_reset->set_text(zoom_text);
-}
-
-void CanvasItemEditor::_button_zoom_minus() {
- _zoom_on_position(_get_next_zoom_value(-6), viewport_scrollable->get_size() / 2.0);
-}
-
-void CanvasItemEditor::_button_zoom_reset() {
- _zoom_on_position(1.0 * MAX(1, EDSCALE), viewport_scrollable->get_size() / 2.0);
-}
-
-void CanvasItemEditor::_button_zoom_plus() {
- _zoom_on_position(_get_next_zoom_value(6), viewport_scrollable->get_size() / 2.0);
+void CanvasItemEditor::_update_zoom(float p_zoom) {
+ _zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0);
}
void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) {
@@ -4718,7 +4242,7 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
}
if (key_rot && p_rotation) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), p_on_existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
}
if (key_scale && p_scale) {
AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
@@ -4750,7 +4274,7 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), p_on_existing);
}
if (key_rot) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), p_on_existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation", F->get()->get_rotation(), p_on_existing);
}
if (key_scale) {
AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), p_on_existing);
@@ -4766,7 +4290,7 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing);
}
if (key_rot) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), p_on_existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing);
}
if (key_scale) {
AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing);
@@ -4793,11 +4317,11 @@ void CanvasItemEditor::_button_toggle_anchor_mode(bool p_status) {
void CanvasItemEditor::_update_override_camera_button(bool p_game_running) {
if (p_game_running) {
override_camera_button->set_disabled(false);
- override_camera_button->set_tooltip(TTR("Game Camera Override\nOverrides game camera with editor viewport camera."));
+ override_camera_button->set_tooltip(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera."));
} else {
override_camera_button->set_disabled(true);
override_camera_button->set_pressed(false);
- override_camera_button->set_tooltip(TTR("Game Camera Override\nNo game instance running."));
+ override_camera_button->set_tooltip(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature."));
}
}
@@ -4890,10 +4414,19 @@ void CanvasItemEditor::_popup_callback(int p_op) {
snap_dialog->popup_centered(Size2(220, 160) * EDSCALE);
} break;
case SKELETON_SHOW_BONES: {
- skeleton_show_bones = !skeleton_show_bones;
- int idx = skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES);
- skeleton_menu->get_popup()->set_item_checked(idx, skeleton_show_bones);
- viewport->update();
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ // Add children nodes so they are processed
+ for (int child = 0; child < E->get()->get_child_count(); child++) {
+ selection.push_back(E->get()->get_child(child));
+ }
+
+ Bone2D *bone_2d = Object::cast_to<Bone2D>(E->get());
+ if (!bone_2d || !bone_2d->is_inside_tree()) {
+ continue;
+ }
+ bone_2d->_editor_set_show_bone_gizmo(!bone_2d->_editor_get_show_bone_gizmo());
+ }
} break;
case SHOW_HELPERS: {
show_helpers = !show_helpers;
@@ -5242,107 +4775,45 @@ void CanvasItemEditor::_popup_callback(int p_op) {
} break;
case SKELETON_MAKE_BONES: {
Map<Node *, Object *> &selection = editor_selection->get_selection();
+ Node *editor_root = EditorNode::get_singleton()->get_edited_scene()->get_tree()->get_edited_scene_root();
- undo_redo->create_action(TTR("Create Custom Bone(s) from Node(s)"));
+ undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)"));
for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) {
Node2D *n2d = Object::cast_to<Node2D>(E->key());
- if (!n2d) {
- continue;
- }
- if (!n2d->is_visible_in_tree()) {
- continue;
- }
- if (!n2d->get_parent_item()) {
- continue;
- }
- if (n2d->has_meta("_edit_bone_") && n2d->get_meta("_edit_bone_")) {
- continue;
- }
-
- undo_redo->add_do_method(n2d, "set_meta", "_edit_bone_", true);
- undo_redo->add_undo_method(n2d, "remove_meta", "_edit_bone_");
- }
- undo_redo->add_do_method(this, "_queue_update_bone_list");
- undo_redo->add_undo_method(this, "_queue_update_bone_list");
- undo_redo->add_do_method(viewport, "update");
- undo_redo->add_undo_method(viewport, "update");
- undo_redo->commit_action();
- } break;
- case SKELETON_CLEAR_BONES: {
- Map<Node *, Object *> &selection = editor_selection->get_selection();
+ Bone2D *new_bone = memnew(Bone2D);
+ String new_bone_name = n2d->get_name();
+ new_bone_name += "Bone2D";
+ new_bone->set_name(new_bone_name);
+ new_bone->set_transform(n2d->get_transform());
- undo_redo->create_action(TTR("Clear Bones"));
- for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) {
- Node2D *n2d = Object::cast_to<Node2D>(E->key());
- if (!n2d) {
- continue;
- }
- if (!n2d->is_visible_in_tree()) {
+ Node *n2d_parent = n2d->get_parent();
+ if (!n2d_parent) {
continue;
}
- if (!n2d->has_meta("_edit_bone_")) {
- continue;
- }
-
- undo_redo->add_do_method(n2d, "remove_meta", "_edit_bone_");
- undo_redo->add_undo_method(n2d, "set_meta", "_edit_bone_", n2d->get_meta("_edit_bone_"));
- }
- undo_redo->add_do_method(this, "_queue_update_bone_list");
- undo_redo->add_undo_method(this, "_queue_update_bone_list");
- undo_redo->add_do_method(viewport, "update");
- undo_redo->add_undo_method(viewport, "update");
- undo_redo->commit_action();
- } break;
- case SKELETON_SET_IK_CHAIN: {
- List<Node *> selection = editor_selection->get_selected_node_list();
+ undo_redo->add_do_method(n2d_parent, "add_child", new_bone);
+ undo_redo->add_do_method(n2d_parent, "remove_child", n2d);
+ undo_redo->add_do_method(new_bone, "add_child", n2d);
+ undo_redo->add_do_method(n2d, "set_transform", Transform2D());
+ undo_redo->add_do_method(this, "_set_owner_for_node_and_children", new_bone, editor_root);
- undo_redo->create_action(TTR("Make IK Chain"));
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get());
- if (!canvas_item || !canvas_item->is_visible_in_tree()) {
- continue;
- }
- if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
- continue;
- }
- if (canvas_item->has_meta("_edit_ik_") && canvas_item->get_meta("_edit_ik_")) {
- continue;
- }
-
- undo_redo->add_do_method(canvas_item, "set_meta", "_edit_ik_", true);
- undo_redo->add_undo_method(canvas_item, "remove_meta", "_edit_ik_");
+ undo_redo->add_undo_method(new_bone, "remove_child", n2d);
+ undo_redo->add_undo_method(n2d_parent, "add_child", n2d);
+ undo_redo->add_undo_method(n2d, "set_transform", new_bone->get_transform());
+ undo_redo->add_undo_method(new_bone, "queue_free");
+ undo_redo->add_undo_method(this, "_set_owner_for_node_and_children", n2d, editor_root);
}
- undo_redo->add_do_method(viewport, "update");
- undo_redo->add_undo_method(viewport, "update");
undo_redo->commit_action();
} break;
- case SKELETON_CLEAR_IK_CHAIN: {
- Map<Node *, Object *> &selection = editor_selection->get_selection();
-
- undo_redo->create_action(TTR("Clear IK Chain"));
- for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *n2d = Object::cast_to<CanvasItem>(E->key());
- if (!n2d) {
- continue;
- }
- if (!n2d->is_visible_in_tree()) {
- continue;
- }
- if (!n2d->has_meta("_edit_ik_")) {
- continue;
- }
-
- undo_redo->add_do_method(n2d, "remove_meta", "_edit_ik_");
- undo_redo->add_undo_method(n2d, "set_meta", "_edit_ik_", n2d->get_meta("_edit_ik_"));
- }
- undo_redo->add_do_method(viewport, "update");
- undo_redo->add_undo_method(viewport, "update");
- undo_redo->commit_action();
+ }
+}
- } break;
+void CanvasItemEditor::_set_owner_for_node_and_children(Node *p_node, Node *p_owner) {
+ p_node->set_owner(p_owner);
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ _set_owner_for_node_and_children(p_node->get_child(i), p_owner);
}
}
@@ -5401,7 +4872,7 @@ void CanvasItemEditor::_focus_selection(int p_op) {
zoom = scale_x < scale_y ? scale_x : scale_y;
zoom *= 0.90;
viewport->update();
- _update_zoom_label();
+ zoom_widget->set_zoom(zoom);
call_deferred("_popup_callback", VIEW_CENTER_TO_SELECTION);
}
}
@@ -5411,11 +4882,11 @@ void CanvasItemEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_override_camera_button", "game_running"), &CanvasItemEditor::_update_override_camera_button);
ClassDB::bind_method("_get_editor_data", &CanvasItemEditor::_get_editor_data);
ClassDB::bind_method("_unhandled_key_input", &CanvasItemEditor::_unhandled_key_input);
- ClassDB::bind_method("_queue_update_bone_list", &CanvasItemEditor::_update_bone_list);
- ClassDB::bind_method("_update_bone_list", &CanvasItemEditor::_update_bone_list);
- ClassDB::bind_method("_reset_create_position", &CanvasItemEditor::_reset_create_position);
ClassDB::bind_method(D_METHOD("set_state"), &CanvasItemEditor::set_state);
ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport);
+ ClassDB::bind_method(D_METHOD("_zoom_on_position"), &CanvasItemEditor::_zoom_on_position);
+
+ ClassDB::bind_method("_set_owner_for_node_and_children", &CanvasItemEditor::_set_owner_for_node_and_children);
ADD_SIGNAL(MethodInfo("item_lock_status_changed"));
ADD_SIGNAL(MethodInfo("item_group_status_changed"));
@@ -5446,14 +4917,13 @@ Dictionary CanvasItemEditor::get_state() const {
state["show_rulers"] = show_rulers;
state["show_guides"] = show_guides;
state["show_helpers"] = show_helpers;
- state["show_zoom_control"] = zoom_hb->is_visible();
+ state["show_zoom_control"] = zoom_widget->is_visible();
state["show_edit_locks"] = show_edit_locks;
state["show_transformation_gizmos"] = show_transformation_gizmos;
state["snap_rotation"] = snap_rotation;
state["snap_scale"] = snap_scale;
state["snap_relative"] = snap_relative;
state["snap_pixel"] = snap_pixel;
- state["skeleton_show_bones"] = skeleton_show_bones;
return state;
}
@@ -5464,7 +4934,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) {
// Compensate the editor scale, so that the editor scale can be changed
// and the zoom level will still be the same (relative to the editor scale).
zoom = float(p_state["zoom"]) * MAX(1, EDSCALE);
- _update_zoom_label();
+ zoom_widget->set_zoom(zoom);
}
if (state.has("ofs")) {
@@ -5594,7 +5064,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) {
if (state.has("show_zoom_control")) {
// This one is not user-controllable, but instrumentable
- zoom_hb->set_visible(state["show_zoom_control"]);
+ zoom_widget->set_visible(state["show_zoom_control"]);
}
if (state.has("snap_rotation")) {
@@ -5621,12 +5091,6 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) {
snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel);
}
- if (state.has("skeleton_show_bones")) {
- skeleton_show_bones = state["skeleton_show_bones"];
- int idx = skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES);
- skeleton_menu->get_popup()->set_item_checked(idx, skeleton_show_bones);
- }
-
if (update_scrollbars) {
_update_scrollbars();
}
@@ -5709,8 +5173,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
selected_from_canvas = false;
anchors_mode = false;
- skeleton_show_bones = true;
-
drag_type = DRAG_NONE;
drag_from = Vector2();
drag_to = Vector2();
@@ -5726,7 +5188,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
bone_last_frame = 0;
- bone_list_dirty = false;
tool = TOOL_SELECT;
undo_redo = p_editor->get_undo_redo();
editor = p_editor;
@@ -5770,11 +5231,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
controls_vb = memnew(VBoxContainer);
controls_vb->set_begin(Point2(5, 5));
- zoom_hb = memnew(HBoxContainer);
- // Bring the zoom percentage closer to the zoom buttons
- zoom_hb->add_theme_constant_override("separation", Math::round(-8 * EDSCALE));
- controls_vb->add_child(zoom_hb);
-
viewport = memnew(CanvasItemEditorViewport(p_editor, this));
viewport_scrollable->add_child(viewport);
viewport->set_mouse_filter(MOUSE_FILTER_PASS);
@@ -5821,38 +5277,20 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
viewport->add_child(controls_vb);
- zoom_minus = memnew(Button);
- zoom_minus->set_flat(true);
- zoom_hb->add_child(zoom_minus);
- zoom_minus->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_minus));
- zoom_minus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_minus", TTR("Zoom Out"), KEY_MASK_CMD | KEY_MINUS));
- zoom_minus->set_shortcut_context(this);
- zoom_minus->set_focus_mode(FOCUS_NONE);
-
- zoom_reset = memnew(Button);
- zoom_reset->set_flat(true);
- zoom_hb->add_child(zoom_reset);
- zoom_reset->add_theme_constant_override("outline_size", 1);
- zoom_reset->add_theme_color_override("font_outline_color", Color(0, 0, 0));
- zoom_reset->add_theme_color_override("font_color", Color(1, 1, 1));
- zoom_reset->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_reset));
- zoom_reset->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_reset", TTR("Zoom Reset"), KEY_MASK_CMD | KEY_0));
- zoom_reset->set_shortcut_context(this);
- zoom_reset->set_focus_mode(FOCUS_NONE);
- zoom_reset->set_text_align(Button::TextAlign::ALIGN_CENTER);
- // Prevent the button's size from changing when the text size changes
- zoom_reset->set_custom_minimum_size(Size2(75 * EDSCALE, 0));
-
- zoom_plus = memnew(Button);
- zoom_plus->set_flat(true);
- zoom_hb->add_child(zoom_plus);
- zoom_plus->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_plus));
- zoom_plus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_plus", TTR("Zoom In"), KEY_MASK_CMD | KEY_EQUAL)); // Usually direct access key for PLUS
- zoom_plus->set_shortcut_context(this);
- zoom_plus->set_focus_mode(FOCUS_NONE);
+ zoom_widget = memnew(EditorZoomWidget);
+ controls_vb->add_child(zoom_widget);
+ zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
+ zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom));
updating_scroll = false;
+ // Add some margin to the left for better aesthetics.
+ // This prevents the first button's hover/pressed effect from "touching" the panel's border,
+ // which looks ugly.
+ Control *margin_left = memnew(Control);
+ hb->add_child(margin_left);
+ margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE);
+
select_button = memnew(Button);
select_button->set_flat(true);
hb->add_child(select_button);
@@ -5985,24 +5423,32 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
lock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(LOCK_SELECTED));
lock_button->set_tooltip(TTR("Lock the selected object in place (can't be moved)."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ lock_button->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KEY_MASK_CMD | KEY_L));
unlock_button = memnew(Button);
unlock_button->set_flat(true);
hb->add_child(unlock_button);
unlock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(UNLOCK_SELECTED));
unlock_button->set_tooltip(TTR("Unlock the selected object (can be moved)."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ unlock_button->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_L));
group_button = memnew(Button);
group_button->set_flat(true);
hb->add_child(group_button);
group_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(GROUP_SELECTED));
group_button->set_tooltip(TTR("Makes sure the object's children are not selectable."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ group_button->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KEY_MASK_CMD | KEY_G));
ungroup_button = memnew(Button);
ungroup_button->set_flat(true);
hb->add_child(ungroup_button);
ungroup_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(UNGROUP_SELECTED));
ungroup_button->set_tooltip(TTR("Restores the object's children's ability to be selected."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ ungroup_button->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G));
hb->add_child(memnew(VSeparator));
@@ -6014,13 +5460,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
p = skeleton_menu->get_popup();
p->set_hide_on_checkable_item_selection(false);
- p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTR("Show Bones")), SKELETON_SHOW_BONES);
- p->add_separator();
- p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_set_ik_chain", TTR("Make IK Chain")), SKELETON_SET_IK_CHAIN);
- p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_clear_ik_chain", TTR("Clear IK Chain")), SKELETON_CLEAR_IK_CHAIN);
+ p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTR("Show Bones")), SKELETON_SHOW_BONES);
p->add_separator();
- p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Custom Bone(s) from Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B), SKELETON_MAKE_BONES);
- p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_clear_bones", TTR("Clear Custom Bones")), SKELETON_CLEAR_BONES);
+ p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Bone2D Node(s) from Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B), SKELETON_MAKE_BONES);
p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback));
hb->add_child(memnew(VSeparator));
@@ -6044,7 +5486,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
p = view_menu->get_popup();
p->set_hide_on_checkable_item_selection(false);
- p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_MASK_CTRL | KEY_G), SHOW_GRID);
+ p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_NUMBERSIGN), SHOW_GRID);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES);
@@ -6089,7 +5531,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
key_loc_button = memnew(Button);
key_loc_button->set_toggle_mode(true);
- key_loc_button->set_flat(true);
key_loc_button->set_pressed(true);
key_loc_button->set_focus_mode(FOCUS_NONE);
key_loc_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_POS));
@@ -6098,7 +5539,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
key_rot_button = memnew(Button);
key_rot_button->set_toggle_mode(true);
- key_rot_button->set_flat(true);
key_rot_button->set_pressed(true);
key_rot_button->set_focus_mode(FOCUS_NONE);
key_rot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_ROT));
@@ -6107,14 +5547,12 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
key_scale_button = memnew(Button);
key_scale_button->set_toggle_mode(true);
- key_scale_button->set_flat(true);
key_scale_button->set_focus_mode(FOCUS_NONE);
key_scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_SCALE));
key_scale_button->set_tooltip(TTR("Scale mask for inserting keys."));
animation_hb->add_child(key_scale_button);
key_insert_button = memnew(Button);
- key_insert_button->set_flat(true);
key_insert_button->set_focus_mode(FOCUS_NONE);
key_insert_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_KEY));
key_insert_button->set_tooltip(TTR("Insert keys (based on mask)."));
@@ -6193,12 +5631,12 @@ void CanvasItemEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
canvas_item_editor->show();
canvas_item_editor->set_physics_process(true);
- RenderingServer::get_singleton()->viewport_set_hide_canvas(editor->get_scene_root()->get_viewport_rid(), false);
+ RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), false);
} else {
canvas_item_editor->hide();
canvas_item_editor->set_physics_process(false);
- RenderingServer::get_singleton()->viewport_set_hide_canvas(editor->get_scene_root()->get_viewport_rid(), true);
+ RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), true);
}
}
@@ -6268,7 +5706,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons
label_desc->show();
} else {
if (scene.is_valid()) {
- Node *instance = scene->instance();
+ Node *instance = scene->instantiate();
if (instance) {
preview_node->add_child(instance);
}
@@ -6381,26 +5819,26 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons
return false;
}
- Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
- if (!instanced_scene) { // error on instancing
+ Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ if (!instantiated_scene) { // error on instancing
return false;
}
if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing
- if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instanced_scene)) {
- memdelete(instanced_scene);
+ if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) {
+ memdelete(instantiated_scene);
return false;
}
}
- instanced_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path));
+ instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path));
- editor_data->get_undo_redo().add_do_method(parent, "add_child", instanced_scene);
- editor_data->get_undo_redo().add_do_method(instanced_scene, "set_owner", editor->get_edited_scene());
- editor_data->get_undo_redo().add_do_reference(instanced_scene);
- editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene);
+ editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene);
+ editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene());
+ editor_data->get_undo_redo().add_do_reference(instantiated_scene);
+ editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instantiated_scene);
- String new_name = parent->validate_child_name(instanced_scene);
+ String new_name = parent->validate_child_name(instantiated_scene);
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name);
editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
@@ -6411,11 +5849,11 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons
target_pos = canvas_item_editor->snap_point(target_pos);
target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos);
// Preserve instance position of the original scene.
- CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instanced_scene);
+ CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene);
if (instance_ci) {
target_pos += instance_ci->_edit_get_position();
}
- editor_data->get_undo_redo().add_do_method(instanced_scene, "set_position", target_pos);
+ editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_position", target_pos);
}
return true;
@@ -6498,7 +5936,7 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
if (d.has("type")) {
if (String(d["type"]) == "files") {
Vector<String> files = d["files"];
- bool can_instance = false;
+ bool can_instantiate = false;
for (int i = 0; i < files.size(); i++) { // check if dragged files contain resource or scene can be created at least once
RES res = ResourceLoader::load(files[i]);
if (res.is_null()) {
@@ -6507,11 +5945,11 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
String type = res->get_class();
if (type == "PackedScene") {
Ref<PackedScene> sdata = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
- Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
- if (!instanced_scene) {
+ Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ if (!instantiated_scene) {
continue;
}
- memdelete(instanced_scene);
+ memdelete(instantiated_scene);
} else if (type == "Texture2D" ||
type == "ImageTexture" ||
type == "ViewportTexture" ||
@@ -6526,10 +5964,10 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
} else {
continue;
}
- can_instance = true;
+ can_instantiate = true;
break;
}
- if (can_instance) {
+ if (can_instantiate) {
if (!preview_node->get_parent()) { // create preview only once
_create_preview(files);
}
@@ -6537,7 +5975,7 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);
label->set_text(vformat(TTR("Adding %s..."), default_type));
}
- return can_instance;
+ return can_instantiate;
}
}
label->hide();
@@ -6662,7 +6100,7 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte
vbc->add_child(btn_group);
btn_group->set_h_size_flags(0);
- button_group.instance();
+ button_group.instantiate();
for (int i = 0; i < types.size(); i++) {
CheckBox *check = memnew(CheckBox);
btn_group->add_child(check);
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 62a9b1e162..7b64d0cb5d 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -33,6 +33,7 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
+#include "editor/editor_zoom_widget.h"
#include "scene/gui/box_container.h"
#include "scene/gui/check_box.h"
#include "scene/gui/label.h"
@@ -186,10 +187,7 @@ private:
VIEW_FRAME_TO_SELECTION,
PREVIEW_CANVAS_SCALE,
SKELETON_MAKE_BONES,
- SKELETON_CLEAR_BONES,
- SKELETON_SHOW_BONES,
- SKELETON_SET_IK_CHAIN,
- SKELETON_CLEAR_IK_CHAIN
+ SKELETON_SHOW_BONES
};
enum DragType {
@@ -208,6 +206,7 @@ private:
DRAG_ANCHOR_BOTTOM_RIGHT,
DRAG_ANCHOR_BOTTOM_LEFT,
DRAG_ANCHOR_ALL,
+ DRAG_QUEUED,
DRAG_MOVE,
DRAG_MOVE_X,
DRAG_MOVE_Y,
@@ -222,7 +221,6 @@ private:
DRAG_KEY_MOVE
};
- EditorSelection *editor_selection;
bool selection_menu_additive_selection;
Tool tool;
@@ -233,10 +231,6 @@ private:
VScrollBar *v_scroll;
HBoxContainer *hb;
- Button *zoom_minus;
- Button *zoom_reset;
- Button *zoom_plus;
-
Map<Control *, Timer *> popup_temporarily_timers;
Label *warning_child_of_container;
@@ -280,7 +274,6 @@ private:
bool snap_scale;
bool snap_relative;
bool snap_pixel;
- bool skeleton_show_bones;
bool key_pos;
bool key_rot;
bool key_scale;
@@ -387,6 +380,7 @@ private:
Control *top_ruler;
Control *left_ruler;
+ Point2 drag_start_origin;
DragType drag_type;
Point2 drag_from;
Point2 drag_to;
@@ -415,7 +409,6 @@ private:
bool _is_node_movable(const Node *p_node, bool p_popup_warning = false);
void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<_SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
void _get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked = false);
- void _get_bones_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items);
void _find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
bool _select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append);
@@ -426,9 +419,7 @@ private:
void _add_canvas_item(CanvasItem *p_canvas_item);
- void _save_canvas_item_ik_chain(const CanvasItem *p_canvas_item, List<float> *p_bones_length, List<Dictionary> *p_bones_state);
void _save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones = false);
- void _restore_canvas_item_ik_chain(CanvasItem *p_canvas_item, const List<Dictionary> *p_bones_state);
void _restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones = false);
void _commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones = false);
@@ -448,8 +439,6 @@ private:
void _reset_create_position();
UndoRedo *undo_redo;
- bool _build_bones_list(Node *p_node);
- bool _get_bone_shape(Vector<Vector2> *shape, Vector<Vector2> *outline_shape, Map<BoneKey, BoneList>::Element *bone);
List<CanvasItem *> _get_edited_canvas_items(bool retreive_locked = false, bool remove_canvas_item_if_parent_in_selection = true);
Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list);
@@ -479,7 +468,6 @@ private:
void _draw_control_helpers(Control *control);
void _draw_selection();
void _draw_axis();
- void _draw_bones();
void _draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
void _draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
void _draw_hover();
@@ -506,8 +494,6 @@ private:
void _focus_selection(int p_op);
- void _solve_IK(Node2D *leaf_node, Point2 target_position);
-
SnapTarget snap_target[2];
Transform2D snap_transform;
void _snap_if_closer_float(
@@ -536,13 +522,9 @@ private:
void _button_toggle_anchor_mode(bool p_status);
VBoxContainer *controls_vb;
- HBoxContainer *zoom_hb;
- float _get_next_zoom_value(int p_increment_count) const;
+ EditorZoomWidget *zoom_widget;
+ void _update_zoom(float p_zoom);
void _zoom_on_position(float p_zoom, Point2 p_position = Point2());
- void _update_zoom_label();
- void _button_zoom_minus();
- void _button_zoom_reset();
- void _button_zoom_plus();
void _button_toggle_smart_snap(bool p_status);
void _button_toggle_grid_snap(bool p_status);
void _button_override_camera(bool p_pressed);
@@ -553,14 +535,11 @@ private:
HSplitContainer *palette_split;
VSplitContainer *bottom_split;
- bool bone_list_dirty;
- void _queue_update_bone_list();
- void _update_bone_list();
- void _tree_changed(Node *);
-
void _popup_warning_temporarily(Control *p_control, const float p_duration);
void _popup_warning_depop(Control *p_control);
+ void _set_owner_for_node_and_children(Node *p_node, Node *p_owner);
+
friend class CanvasItemEditorPlugin;
protected:
@@ -648,6 +627,8 @@ public:
bool is_anchors_mode_enabled() { return anchors_mode; };
+ EditorSelection *editor_selection;
+
CanvasItemEditor(EditorNode *p_editor);
};
diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp
index b50a497ccf..6f90d278bd 100644
--- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp
+++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp
@@ -32,8 +32,8 @@
#include "canvas_item_editor_plugin.h"
#include "core/input/input.h"
+#include "core/io/file_access.h"
#include "core/math/geometry_2d.h"
-#include "core/os/file_access.h"
#include "core/os/keyboard.h"
#include "editor/editor_settings.h"
#include "node_3d_editor_plugin.h"
@@ -108,8 +108,8 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
return false;
}
- Transform gt = node->get_global_transform();
- Transform gi = gt.affine_inverse();
+ Transform3D gt = node->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
float depth = _get_depth() * 0.5;
Vector3 n = gt.basis.get_axis(2).normalized();
Plane p(gt.origin + n * depth, n);
@@ -175,7 +175,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
case MODE_EDIT: {
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
if (mb->is_pressed()) {
- if (mb->get_control()) {
+ if (mb->is_ctrl_pressed()) {
if (poly.size() < 3) {
undo_redo->create_action(TTR("Edit Poly"));
undo_redo->add_undo_method(node, "set_polygon", poly);
@@ -317,7 +317,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
Vector2 cpoint(spoint.x, spoint.y);
- if (snap_ignore && !Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+ if (snap_ignore && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
snap_ignore = false;
}
@@ -358,9 +358,9 @@ void CollisionPolygon3DEditor::_polygon_draw() {
float depth = _get_depth() * 0.5;
- imgeom->clear();
+ imesh->clear_surfaces();
imgeom->set_material_override(line_material);
- imgeom->begin(Mesh::PRIMITIVE_LINES, Ref<Texture2D>());
+ imesh->surface_begin(Mesh::PRIMITIVE_LINES);
Rect2 rect;
@@ -382,10 +382,10 @@ void CollisionPolygon3DEditor::_polygon_draw() {
Vector3 point = Vector3(p.x, p.y, depth);
Vector3 next_point = Vector3(p2.x, p2.y, depth);
- imgeom->set_color(Color(1, 0.3, 0.1, 0.8));
- imgeom->add_vertex(point);
- imgeom->set_color(Color(1, 0.3, 0.1, 0.8));
- imgeom->add_vertex(next_point);
+ imesh->surface_set_color(Color(1, 0.3, 0.1, 0.8));
+ imesh->surface_add_vertex(point);
+ imesh->surface_set_color(Color(1, 0.3, 0.1, 0.8));
+ imesh->surface_add_vertex(next_point);
//Color col=Color(1,0.3,0.1,0.8);
//vpc->draw_line(point,next_point,col,2);
@@ -402,45 +402,43 @@ void CollisionPolygon3DEditor::_polygon_draw() {
r.size.y = rect.size.y;
r.size.z = 0;
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position);
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(0.3, 0, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position);
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(0.0, 0.3, 0));
-
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0) - Vector3(0.3, 0, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0) + Vector3(0, 0.3, 0));
-
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0) - Vector3(0, 0.3, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0) + Vector3(0.3, 0, 0));
-
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + r.size);
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + r.size - Vector3(0.3, 0, 0));
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + r.size);
- imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2));
- imgeom->add_vertex(r.position + r.size - Vector3(0.0, 0.3, 0));
-
- imgeom->end();
-
- m->clear_surfaces();
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position);
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(0.3, 0, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position);
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(0.0, 0.3, 0));
+
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0) - Vector3(0.3, 0, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0) + Vector3(0, 0.3, 0));
+
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0) - Vector3(0, 0.3, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0) + Vector3(0.3, 0, 0));
+
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + r.size);
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + r.size - Vector3(0.3, 0, 0));
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + r.size);
+ imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2));
+ imesh->surface_add_vertex(r.position + r.size - Vector3(0.0, 0.3, 0));
+
+ imesh->surface_end();
if (poly.size() == 0) {
return;
@@ -515,8 +513,10 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) {
mode = MODE_EDIT;
wip_active = false;
- imgeom = memnew(ImmediateGeometry3D);
- imgeom->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001)));
+ imgeom = memnew(MeshInstance3D);
+ imesh.instantiate();
+ imgeom->set_mesh(imesh);
+ imgeom->set_transform(Transform3D(Basis(), Vector3(0, 0, 0.00001)));
line_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
line_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
@@ -537,9 +537,9 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) {
pointsm = memnew(MeshInstance3D);
imgeom->add_child(pointsm);
- m.instance();
+ m.instantiate();
pointsm->set_mesh(m);
- pointsm->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001)));
+ pointsm->set_transform(Transform3D(Basis(), Vector3(0, 0, 0.00001)));
snap_ignore = false;
}
diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/collision_polygon_3d_editor_plugin.h
index c66518e3e5..5db0f7308a 100644
--- a/editor/plugins/collision_polygon_3d_editor_plugin.h
+++ b/editor/plugins/collision_polygon_3d_editor_plugin.h
@@ -34,8 +34,8 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "scene/3d/collision_polygon_3d.h"
-#include "scene/3d/immediate_geometry_3d.h"
#include "scene/3d/mesh_instance_3d.h"
+#include "scene/resources/immediate_mesh.h"
class CanvasItemEditor;
@@ -60,7 +60,8 @@ class CollisionPolygon3DEditor : public HBoxContainer {
EditorNode *editor;
Panel *panel;
Node3D *node;
- ImmediateGeometry3D *imgeom;
+ Ref<ImmediateMesh> imesh;
+ MeshInstance3D *imgeom;
MeshInstance3D *pointsm;
Ref<ArrayMesh> m;
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
index 3403aeceba..6a56cd31d1 100644
--- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp
+++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
@@ -74,7 +74,7 @@ void CPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
void CPUParticles2DEditorPlugin::_generate_emission_mask() {
Ref<Image> img;
- img.instance();
+ img.instantiate();
Error err = ImageLoader::load_image(source_emission_file, img);
ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index db999f50ab..706243fe25 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -127,6 +127,8 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
case MOUSE_BUTTON_LEFT:
_dragging = true;
break;
+ default:
+ break;
}
}
@@ -168,8 +170,8 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
// Snap to "round" coordinates when holding Ctrl.
// Be more precise when holding Shift as well.
float snap_threshold;
- if (mm.get_control()) {
- snap_threshold = mm.get_shift() ? 0.025 : 0.1;
+ if (mm.is_ctrl_pressed()) {
+ snap_threshold = mm.is_shift_pressed() ? 0.025 : 0.1;
} else {
snap_threshold = 0.0;
}
@@ -776,7 +778,7 @@ void EditorInspectorPluginCurve::parse_begin(Object *p_object) {
CurveEditorPlugin::CurveEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginCurve> curve_plugin;
- curve_plugin.instance();
+ curve_plugin.instantiate();
EditorInspector::add_inspector_plugin(curve_plugin);
get_editor_interface()->get_resource_previewer()->add_preview_generator(memnew(CurvePreviewGenerator));
@@ -798,7 +800,7 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons
int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
thumbnail_size *= EDSCALE;
Ref<Image> img_ref;
- img_ref.instance();
+ img_ref.instantiate();
Image &im = **img_ref;
im.create(thumbnail_size, thumbnail_size / 2, false, Image::FORMAT_RGBA8);
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 87eeb82101..a233d66d82 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -174,7 +174,7 @@ Ref<Texture2D> EditorImagePreviewPlugin::generate(const RES &p_from, const Size2
post_process_preview(img);
Ref<ImageTexture> ptex;
- ptex.instance();
+ ptex.instantiate();
ptex->create_from_image(img);
return ptex;
@@ -219,7 +219,7 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const RES &p_from, const Size
}
Ref<Image> img;
- img.instance();
+ img.instantiate();
img->create(bm->get_size().width, bm->get_size().height, false, Image::FORMAT_L8, data);
if (img->is_compressed()) {
@@ -265,7 +265,7 @@ Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const RES &p_from, const
}
Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const {
- String temp_path = EditorSettings::get_singleton()->get_cache_dir();
+ String temp_path = EditorPaths::get_singleton()->get_cache_dir();
String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text();
cache_base = temp_path.plus_file("resthumb-" + cache_base);
@@ -278,7 +278,7 @@ Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &
}
Ref<Image> img;
- img.instance();
+ img.instantiate();
Error err = img->load(path);
if (err == OK) {
Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture));
@@ -359,12 +359,12 @@ EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() {
camera = RS::get_singleton()->camera_create();
RS::get_singleton()->viewport_attach_camera(viewport, camera);
- RS::get_singleton()->camera_set_transform(camera, Transform(Basis(), Vector3(0, 0, 3)));
+ RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3)));
RS::get_singleton()->camera_set_perspective(camera, 45, 0.1, 10);
light = RS::get_singleton()->directional_light_create();
light_instance = RS::get_singleton()->instance_create2(light, scenario);
- RS::get_singleton()->instance_set_transform(light_instance, Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
+ RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
light2 = RS::get_singleton()->directional_light_create();
RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
@@ -372,7 +372,7 @@ EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() {
light_instance2 = RS::get_singleton()->instance_create2(light2, scenario);
- RS::get_singleton()->instance_set_transform(light_instance2, Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
+ RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
sphere = RS::get_singleton()->mesh_create();
sphere_instance = RS::get_singleton()->instance_create2(sphere, scenario);
@@ -487,23 +487,30 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
List<String> kwors;
scr->get_language()->get_reserved_words(&kwors);
+ Set<String> control_flow_keywords;
Set<String> keywords;
for (List<String>::Element *E = kwors.front(); E; E = E->next()) {
- keywords.insert(E->get());
+ if (scr->get_language()->is_control_flow_keyword(E->get())) {
+ control_flow_keywords.insert(E->get());
+ } else {
+ keywords.insert(E->get());
+ }
}
int line = 0;
int col = 0;
Ref<Image> img;
- img.instance();
+ img.instantiate();
int thumbnail_size = MAX(p_size.x, p_size.y);
img->create(thumbnail_size, thumbnail_size, false, Image::FORMAT_RGBA8);
Color bg_color = EditorSettings::get_singleton()->get("text_editor/highlighting/background_color");
Color keyword_color = EditorSettings::get_singleton()->get("text_editor/highlighting/keyword_color");
+ Color control_flow_keyword_color = EditorSettings::get_singleton()->get("text_editor/highlighting/control_flow_keyword_color");
Color text_color = EditorSettings::get_singleton()->get("text_editor/highlighting/text_color");
Color symbol_color = EditorSettings::get_singleton()->get("text_editor/highlighting/symbol_color");
+ Color comment_color = EditorSettings::get_singleton()->get("text_editor/highlighting/comment_color");
if (bg_color.a == 0) {
bg_color = Color(0, 0, 0, 0);
@@ -522,36 +529,50 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
col = x0;
bool prev_is_text = false;
+ bool in_control_flow_keyword = false;
bool in_keyword = false;
+ bool in_comment = false;
for (int i = 0; i < code.length(); i++) {
char32_t c = code[i];
if (c > 32) {
if (col < thumbnail_size) {
Color color = text_color;
- if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) {
- //make symbol a little visible
- color = symbol_color;
- in_keyword = false;
- } else if (!prev_is_text && _is_text_char(c)) {
- int pos = i;
+ if (c == '#') {
+ in_comment = true;
+ }
- while (_is_text_char(code[pos])) {
- pos++;
- }
- String word = code.substr(i, pos - i);
- if (keywords.has(word)) {
- in_keyword = true;
+ if (in_comment) {
+ color = comment_color;
+ } else {
+ if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) {
+ //make symbol a little visible
+ color = symbol_color;
+ in_control_flow_keyword = false;
+ in_keyword = false;
+ } else if (!prev_is_text && _is_text_char(c)) {
+ int pos = i;
+
+ while (_is_text_char(code[pos])) {
+ pos++;
+ }
+ String word = code.substr(i, pos - i);
+ if (control_flow_keywords.has(word)) {
+ in_control_flow_keyword = true;
+ } else if (keywords.has(word)) {
+ in_keyword = true;
+ }
+
+ } else if (!_is_text_char(c)) {
+ in_keyword = false;
}
- } else if (!_is_text_char(c)) {
- in_keyword = false;
- }
-
- if (in_keyword) {
- color = keyword_color;
+ if (in_control_flow_keyword) {
+ color = control_flow_keyword_color;
+ } else if (in_keyword) {
+ color = keyword_color;
+ }
}
-
Color ul = color;
ul.a *= 0.5;
img->set_pixel(col, y0 + line * 2, bg_color.blend(ul));
@@ -559,11 +580,15 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
prev_is_text = _is_text_char(c);
}
+ col++;
} else {
prev_is_text = false;
+ in_control_flow_keyword = false;
in_keyword = false;
if (c == '\n') {
+ in_comment = false;
+
col = x0;
line++;
if (line >= available_height / 2) {
@@ -571,9 +596,10 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
}
} else if (c == '\t') {
col += 3;
+ } else {
+ col++;
}
}
- col++;
}
post_process_preview(img);
@@ -662,7 +688,7 @@ Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const RES &p_from, const
Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture));
Ref<Image> image;
- image.instance();
+ image.instantiate();
image->create(w, h, false, Image::FORMAT_RGB8, img);
ptex->create_from_image(image);
return ptex;
@@ -694,7 +720,7 @@ Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2
AABB aabb = mesh->get_aabb();
Vector3 ofs = aabb.position + aabb.size * 0.5;
aabb.position -= ofs;
- Transform xform;
+ Transform3D xform;
xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math_PI * 0.125);
xform.basis = Basis().rotated(Vector3(1, 0, 0), Math_PI * 0.125) * xform.basis;
AABB rot_aabb = xform.xform(aabb);
@@ -754,20 +780,20 @@ EditorMeshPreviewPlugin::EditorMeshPreviewPlugin() {
camera = RS::get_singleton()->camera_create();
RS::get_singleton()->viewport_attach_camera(viewport, camera);
- RS::get_singleton()->camera_set_transform(camera, Transform(Basis(), Vector3(0, 0, 3)));
+ RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3)));
//RS::get_singleton()->camera_set_perspective(camera,45,0.1,10);
RS::get_singleton()->camera_set_orthogonal(camera, 1.0, 0.01, 1000.0);
light = RS::get_singleton()->directional_light_create();
light_instance = RS::get_singleton()->instance_create2(light, scenario);
- RS::get_singleton()->instance_set_transform(light_instance, Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
+ RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
light2 = RS::get_singleton()->directional_light_create();
RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
//RS::get_singleton()->light_set_color(light2, RS::LIGHT_COLOR_SPECULAR, Color(0.0, 0.0, 0.0));
light_instance2 = RS::get_singleton()->instance_create2(light2, scenario);
- RS::get_singleton()->instance_set_transform(light_instance2, Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
+ RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
//sphere = RS::get_singleton()->mesh_create();
mesh_instance = RS::get_singleton()->instance_create();
@@ -806,7 +832,7 @@ struct FSample {
};
static FSample _samples[] = {
- { "hani", U"漢語" },
+ { "hani", U"漢字" },
{ "armn", U"Աբ" },
{ "copt", U"Αα" },
{ "cyrl", U"Аб" },
@@ -855,7 +881,7 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path,
if (res->is_class("Font")) {
sampled_font = res->duplicate();
} else if (res->is_class("FontData")) {
- sampled_font.instance();
+ sampled_font.instantiate();
sampled_font->add_data(res->duplicate());
}
diff --git a/editor/plugins/font_editor_plugin.cpp b/editor/plugins/font_editor_plugin.cpp
index fa58eb5480..e385a84087 100644
--- a/editor/plugins/font_editor_plugin.cpp
+++ b/editor/plugins/font_editor_plugin.cpp
@@ -56,7 +56,7 @@ struct FSample {
};
static FSample _samples[] = {
- { "hani", U"漢語" },
+ { "hani", U"漢字" },
{ "armn", U"Աբ" },
{ "copt", U"Αα" },
{ "cyrl", U"Аб" },
@@ -119,7 +119,7 @@ void FontDataPreview::set_data(const Ref<FontData> &p_data) {
}
FontDataPreview::FontDataPreview() {
- line.instance();
+ line.instantiate();
}
/*************************************************************************/
@@ -290,7 +290,7 @@ void EditorInspectorPluginFont::parse_begin(Object *p_object) {
add_custom_control(editor);
}
-bool EditorInspectorPluginFont::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) {
+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/", "");
@@ -326,6 +326,6 @@ bool EditorInspectorPluginFont::parse_property(Object *p_object, Variant::Type p
FontEditorPlugin::FontEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginFont> fd_plugin;
- fd_plugin.instance();
+ fd_plugin.instantiate();
EditorInspector::add_inspector_plugin(fd_plugin);
}
diff --git a/editor/plugins/font_editor_plugin.h b/editor/plugins/font_editor_plugin.h
index 04e6c1dac7..71464003a0 100644
--- a/editor/plugins/font_editor_plugin.h
+++ b/editor/plugins/font_editor_plugin.h
@@ -94,7 +94,7 @@ class EditorInspectorPluginFont : public EditorInspectorPlugin {
public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
- virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) override;
+ virtual bool 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 = false) override;
};
/*************************************************************************/
diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp
index b447304a3f..37f900280b 100644
--- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp
+++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp
@@ -146,7 +146,7 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() {
}
Ref<Image> img;
- img.instance();
+ img.instantiate();
Error err = ImageLoader::load_image(source_emission_file, img);
ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
@@ -270,11 +270,11 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() {
}
}
- img.instance();
+ img.instantiate();
img->create(w, h, false, Image::FORMAT_RGF, texdata);
Ref<ImageTexture> imgt;
- imgt.instance();
+ imgt.instantiate();
imgt->create_from_image(img);
pm->set_emission_point_texture(imgt);
@@ -291,10 +291,10 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() {
}
}
- img.instance();
+ img.instantiate();
img->create(w, h, false, Image::FORMAT_RGBA8, colordata);
- imgt.instance();
+ imgt.instantiate();
imgt->create_from_image(img);
pm->set_emission_color_texture(imgt);
}
@@ -314,10 +314,10 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() {
}
}
- img.instance();
+ img.instantiate();
img->create(w, h, false, Image::FORMAT_RGF, normdata);
- imgt.instance();
+ imgt.instantiate();
imgt->create_from_image(img);
pm->set_emission_normal_texture(imgt);
diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
index 89d6aaa5f9..571bcf9c4a 100644
--- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp
+++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
@@ -177,7 +177,7 @@ void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) {
return;
}
- Transform geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform();
+ Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform();
int gc = geometry.size();
Face3 *w = geometry.ptrw();
@@ -359,7 +359,7 @@ void GPUParticles3DEditor::_generate_emission_points() {
Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img));
Ref<ImageTexture> tex;
- tex.instance();
+ tex.instantiate();
Ref<ParticlesMaterial> material = node->get_process_material();
ERR_FAIL_COND(material.is_null());
@@ -387,7 +387,7 @@ void GPUParticles3DEditor::_generate_emission_points() {
Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2));
Ref<ImageTexture> tex2;
- tex2.instance();
+ tex2.instantiate();
material->set_emission_normal_texture(tex2);
} else {
diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp
index 8c4928b7cb..a2dee4a1dc 100644
--- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp
+++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp
@@ -137,7 +137,7 @@ void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String
Ref<ConfigFile> config;
- config.instance();
+ config.instantiate();
if (FileAccess::exists(p_path + ".import")) {
config->load(p_path + ".import");
}
diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp
index 46fa00f730..355bdb69d8 100644
--- a/editor/plugins/gradient_editor_plugin.cpp
+++ b/editor/plugins/gradient_editor_plugin.cpp
@@ -92,6 +92,6 @@ void EditorInspectorPluginGradient::parse_begin(Object *p_object) {
GradientEditorPlugin::GradientEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginGradient> plugin;
- plugin.instance();
+ plugin.instantiate();
add_inspector_plugin(plugin);
}
diff --git a/editor/plugins/input_event_editor_plugin.cpp b/editor/plugins/input_event_editor_plugin.cpp
new file mode 100644
index 0000000000..f1aa10844b
--- /dev/null
+++ b/editor/plugins/input_event_editor_plugin.cpp
@@ -0,0 +1,122 @@
+/*************************************************************************/
+/* input_event_editor_plugin.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 "input_event_editor_plugin.h"
+
+void InputEventConfigContainer::_bind_methods() {
+}
+
+void InputEventConfigContainer::_configure_pressed() {
+ config_dialog->popup_and_configure(input_event);
+}
+
+void InputEventConfigContainer::_event_changed() {
+ input_event_text->set_text(input_event->as_text());
+}
+
+void InputEventConfigContainer::_config_dialog_confirmed() {
+ Ref<InputEvent> ie = config_dialog->get_event();
+ input_event->copy_from(ie);
+ _event_changed();
+}
+
+Size2 InputEventConfigContainer::get_minimum_size() const {
+ // Don't bother with a minimum x size for the control - we don't want the inspector
+ // to jump in size if a long text is placed in the label (e.g. Joypad Axis description)
+ return Size2(0, HBoxContainer::get_minimum_size().y);
+}
+
+void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> k = p_event;
+ Ref<InputEventMouseButton> m = p_event;
+ Ref<InputEventJoypadButton> jb = p_event;
+ Ref<InputEventJoypadMotion> jm = p_event;
+
+ if (k.is_valid()) {
+ config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_KEY);
+ } else if (m.is_valid()) {
+ config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_MOUSE_BUTTON);
+ } else if (jb.is_valid()) {
+ config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_BUTTON);
+ } else if (jm.is_valid()) {
+ config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_MOTION);
+ }
+
+ input_event = p_event;
+ _event_changed();
+ input_event->connect("changed", callable_mp(this, &InputEventConfigContainer::_event_changed));
+}
+
+InputEventConfigContainer::InputEventConfigContainer() {
+ MarginContainer *mc = memnew(MarginContainer);
+ mc->add_theme_constant_override("margin_left", 10);
+ mc->add_theme_constant_override("margin_right", 10);
+ mc->add_theme_constant_override("margin_top", 10);
+ mc->add_theme_constant_override("margin_bottom", 10);
+ add_child(mc);
+
+ HBoxContainer *hb = memnew(HBoxContainer);
+ mc->add_child(hb);
+
+ open_config_button = memnew(Button);
+ open_config_button->set_text("Configure");
+ open_config_button->connect("pressed", callable_mp(this, &InputEventConfigContainer::_configure_pressed));
+ hb->add_child(open_config_button);
+
+ input_event_text = memnew(Label);
+ hb->add_child(input_event_text);
+
+ config_dialog = memnew(InputEventConfigurationDialog);
+ config_dialog->connect("confirmed", callable_mp(this, &InputEventConfigContainer::_config_dialog_confirmed));
+ add_child(config_dialog);
+}
+
+bool EditorInspectorPluginInputEvent::can_handle(Object *p_object) {
+ Ref<InputEventKey> k = Ref<InputEventKey>(p_object);
+ Ref<InputEventMouseButton> m = Ref<InputEventMouseButton>(p_object);
+ Ref<InputEventJoypadButton> jb = Ref<InputEventJoypadButton>(p_object);
+ Ref<InputEventJoypadMotion> jm = Ref<InputEventJoypadMotion>(p_object);
+
+ return k.is_valid() || m.is_valid() || jb.is_valid() || jm.is_valid();
+}
+
+void EditorInspectorPluginInputEvent::parse_begin(Object *p_object) {
+ Ref<InputEvent> ie = Ref<InputEvent>(p_object);
+
+ InputEventConfigContainer *picker_controls = memnew(InputEventConfigContainer);
+ picker_controls->set_event(ie);
+ add_custom_control(picker_controls);
+}
+
+InputEventEditorPlugin::InputEventEditorPlugin(EditorNode *p_node) {
+ Ref<EditorInspectorPluginInputEvent> plugin;
+ plugin.instantiate();
+ add_inspector_plugin(plugin);
+}
diff --git a/editor/plugins/input_event_editor_plugin.h b/editor/plugins/input_event_editor_plugin.h
new file mode 100644
index 0000000000..bc8293c9e5
--- /dev/null
+++ b/editor/plugins/input_event_editor_plugin.h
@@ -0,0 +1,79 @@
+/*************************************************************************/
+/* input_event_editor_plugin.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 INPUT_EVENT_EDITOR_PLUGIN_H
+#define INPUT_EVENT_EDITOR_PLUGIN_H
+
+#include "editor/action_map_editor.h"
+#include "editor/editor_inspector.h"
+#include "editor/editor_node.h"
+
+class InputEventConfigContainer : public HBoxContainer {
+ GDCLASS(InputEventConfigContainer, HBoxContainer);
+
+ Label *input_event_text;
+ Button *open_config_button;
+
+ Ref<InputEvent> input_event;
+ InputEventConfigurationDialog *config_dialog;
+
+ void _config_dialog_confirmed();
+ void _configure_pressed();
+
+ void _event_changed();
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual Size2 get_minimum_size() const override;
+ void set_event(const Ref<InputEvent> &p_event);
+
+ InputEventConfigContainer();
+};
+
+class EditorInspectorPluginInputEvent : public EditorInspectorPlugin {
+ GDCLASS(EditorInspectorPluginInputEvent, EditorInspectorPlugin);
+
+public:
+ virtual bool can_handle(Object *p_object) override;
+ virtual void parse_begin(Object *p_object) override;
+};
+
+class InputEventEditorPlugin : public EditorPlugin {
+ GDCLASS(InputEventEditorPlugin, EditorPlugin);
+
+public:
+ virtual String get_name() const override { return "InputEvent"; }
+
+ InputEventEditorPlugin(EditorNode *p_node);
+};
+
+#endif // INPUT_EVENT_EDITOR_PLUGIN_H
diff --git a/editor/plugins/baked_lightmap_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp
index 470b61bf40..484fdabfe1 100644
--- a/editor/plugins/baked_lightmap_editor_plugin.cpp
+++ b/editor/plugins/lightmap_gi_editor_plugin.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* baked_lightmap_editor_plugin.cpp */
+/* lightmap_gi_editor_plugin.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,11 +28,11 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "baked_lightmap_editor_plugin.h"
+#include "lightmap_gi_editor_plugin.h"
-void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) {
+void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {
if (lightmap) {
- BakedLightmap::BakeError err;
+ LightmapGI::BakeError err;
if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) {
err = lightmap->bake(lightmap, p_file, bake_func_step);
} else {
@@ -42,7 +42,7 @@ void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) {
bake_func_end();
switch (err) {
- case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: {
+ case LightmapGI::BAKE_ERROR_NO_SAVE_PATH: {
String scene_path = lightmap->get_filename();
if (scene_path == String()) {
scene_path = lightmap->get_owner()->get_filename();
@@ -57,10 +57,10 @@ void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) {
file_dialog->popup_file_dialog();
} break;
- case BakedLightmap::BAKE_ERROR_NO_MESHES:
+ case LightmapGI::BAKE_ERROR_NO_MESHES:
EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on."));
break;
- case BakedLightmap::BAKE_ERROR_CANT_CREATE_IMAGE:
+ case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE:
EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable."));
break;
default: {
@@ -69,12 +69,12 @@ void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) {
}
}
-void BakedLightmapEditorPlugin::_bake() {
+void LightmapGIEditorPlugin::_bake() {
_bake_select_file("");
}
-void BakedLightmapEditorPlugin::edit(Object *p_object) {
- BakedLightmap *s = Object::cast_to<BakedLightmap>(p_object);
+void LightmapGIEditorPlugin::edit(Object *p_object) {
+ LightmapGI *s = Object::cast_to<LightmapGI>(p_object);
if (!s) {
return;
}
@@ -82,11 +82,11 @@ void BakedLightmapEditorPlugin::edit(Object *p_object) {
lightmap = s;
}
-bool BakedLightmapEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("BakedLightmap");
+bool LightmapGIEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("LightmapGI");
}
-void BakedLightmapEditorPlugin::make_visible(bool p_visible) {
+void LightmapGIEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
bake->show();
} else {
@@ -94,9 +94,9 @@ void BakedLightmapEditorPlugin::make_visible(bool p_visible) {
}
}
-EditorProgress *BakedLightmapEditorPlugin::tmp_progress = nullptr;
+EditorProgress *LightmapGIEditorPlugin::tmp_progress = nullptr;
-bool BakedLightmapEditorPlugin::bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh) {
+bool LightmapGIEditorPlugin::bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh) {
if (!tmp_progress) {
tmp_progress = memnew(EditorProgress("bake_lightmaps", TTR("Bake Lightmaps"), 1000, false));
ERR_FAIL_COND_V(tmp_progress == nullptr, false);
@@ -104,18 +104,18 @@ bool BakedLightmapEditorPlugin::bake_func_step(float p_progress, const String &p
return tmp_progress->step(p_description, p_progress * 1000, p_refresh);
}
-void BakedLightmapEditorPlugin::bake_func_end() {
+void LightmapGIEditorPlugin::bake_func_end() {
if (tmp_progress != nullptr) {
memdelete(tmp_progress);
tmp_progress = nullptr;
}
}
-void BakedLightmapEditorPlugin::_bind_methods() {
- ClassDB::bind_method("_bake", &BakedLightmapEditorPlugin::_bake);
+void LightmapGIEditorPlugin::_bind_methods() {
+ ClassDB::bind_method("_bake", &LightmapGIEditorPlugin::_bake);
}
-BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) {
+LightmapGIEditorPlugin::LightmapGIEditorPlugin(EditorNode *p_node) {
editor = p_node;
bake = memnew(Button);
bake->set_flat(true);
@@ -130,9 +130,9 @@ BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) {
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
file_dialog->add_filter("*.lmbake ; LightMap Bake");
file_dialog->set_title(TTR("Select lightmap bake file:"));
- file_dialog->connect("file_selected", callable_mp(this, &BakedLightmapEditorPlugin::_bake_select_file));
+ file_dialog->connect("file_selected", callable_mp(this, &LightmapGIEditorPlugin::_bake_select_file));
bake->add_child(file_dialog);
}
-BakedLightmapEditorPlugin::~BakedLightmapEditorPlugin() {
+LightmapGIEditorPlugin::~LightmapGIEditorPlugin() {
}
diff --git a/editor/plugins/baked_lightmap_editor_plugin.h b/editor/plugins/lightmap_gi_editor_plugin.h
index d291c377d9..12d080d6be 100644
--- a/editor/plugins/baked_lightmap_editor_plugin.h
+++ b/editor/plugins/lightmap_gi_editor_plugin.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* baked_lightmap_editor_plugin.h */
+/* lightmap_gi_editor_plugin.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -33,13 +33,13 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
-#include "scene/3d/baked_lightmap.h"
+#include "scene/3d/lightmap_gi.h"
#include "scene/resources/material.h"
-class BakedLightmapEditorPlugin : public EditorPlugin {
- GDCLASS(BakedLightmapEditorPlugin, EditorPlugin);
+class LightmapGIEditorPlugin : public EditorPlugin {
+ GDCLASS(LightmapGIEditorPlugin, EditorPlugin);
- BakedLightmap *lightmap;
+ LightmapGI *lightmap;
Button *bake;
EditorNode *editor;
@@ -56,14 +56,14 @@ protected:
static void _bind_methods();
public:
- virtual String get_name() const override { return "BakedLightmap"; }
+ virtual String get_name() const override { return "LightmapGI"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
- BakedLightmapEditorPlugin(EditorNode *p_node);
- ~BakedLightmapEditorPlugin();
+ LightmapGIEditorPlugin(EditorNode *p_node);
+ ~LightmapGIEditorPlugin();
};
#endif
diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp
index ad99ad7808..17a634ee14 100644
--- a/editor/plugins/material_editor_plugin.cpp
+++ b/editor/plugins/material_editor_plugin.cpp
@@ -111,7 +111,7 @@ MaterialEditor::MaterialEditor() {
vc->set_anchors_and_offsets_preset(PRESET_WIDE);
viewport = memnew(SubViewport);
Ref<World3D> world_3d;
- world_3d.instance();
+ world_3d.instantiate();
viewport->set_world_3d(world_3d); //use own world
vc->add_child(viewport);
viewport->set_disable_input(true);
@@ -119,17 +119,17 @@ MaterialEditor::MaterialEditor() {
viewport->set_msaa(Viewport::MSAA_4X);
camera = memnew(Camera3D);
- camera->set_transform(Transform(Basis(), Vector3(0, 0, 3)));
+ camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 3)));
camera->set_perspective(45, 0.1, 10);
camera->make_current();
viewport->add_child(camera);
light1 = memnew(DirectionalLight3D);
- light1->set_transform(Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
+ light1->set_transform(Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
viewport->add_child(light1);
light2 = memnew(DirectionalLight3D);
- light2->set_transform(Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
+ light2->set_transform(Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
light2->set_color(Color(0.7, 0.7, 0.7));
viewport->add_child(light2);
@@ -139,16 +139,16 @@ MaterialEditor::MaterialEditor() {
box_instance = memnew(MeshInstance3D);
viewport->add_child(box_instance);
- Transform box_xform;
+ Transform3D box_xform;
box_xform.basis.rotate(Vector3(1, 0, 0), Math::deg2rad(25.0));
box_xform.basis = box_xform.basis * Basis().rotated(Vector3(0, 1, 0), Math::deg2rad(-25.0));
box_xform.basis.scale(Vector3(0.8, 0.8, 0.8));
box_xform.origin.y = 0.2;
box_instance->set_transform(box_xform);
- sphere_mesh.instance();
+ sphere_mesh.instantiate();
sphere_instance->set_mesh(sphere_mesh);
- box_mesh.instance();
+ box_mesh.instantiate();
box_instance->set_mesh(box_mesh);
set_custom_minimum_size(Size2(1, 150) * EDSCALE);
@@ -223,7 +223,7 @@ void EditorInspectorPluginMaterial::parse_begin(Object *p_object) {
}
EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() {
- env.instance();
+ env.instantiate();
Ref<Sky> sky = memnew(Sky());
env->set_sky(sky);
env->set_background(Environment::BG_COLOR);
@@ -233,7 +233,7 @@ EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() {
MaterialEditorPlugin::MaterialEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginMaterial> plugin;
- plugin.instance();
+ plugin.instantiate();
add_inspector_plugin(plugin);
}
@@ -251,10 +251,10 @@ Ref<Resource> StandardMaterial3DConversionPlugin::convert(const Ref<Resource> &p
ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>());
Ref<ShaderMaterial> smat;
- smat.instance();
+ smat.instantiate();
Ref<Shader> shader;
- shader.instance();
+ shader.instantiate();
String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid());
@@ -295,10 +295,10 @@ Ref<Resource> ParticlesMaterialConversionPlugin::convert(const Ref<Resource> &p_
ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>());
Ref<ShaderMaterial> smat;
- smat.instance();
+ smat.instantiate();
Ref<Shader> shader;
- shader.instance();
+ shader.instantiate();
String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid());
@@ -332,10 +332,10 @@ Ref<Resource> CanvasItemMaterialConversionPlugin::convert(const Ref<Resource> &p
ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>());
Ref<ShaderMaterial> smat;
- smat.instance();
+ smat.instantiate();
Ref<Shader> shader;
- shader.instance();
+ shader.instantiate();
String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid());
@@ -369,10 +369,10 @@ Ref<Resource> ProceduralSkyMaterialConversionPlugin::convert(const Ref<Resource>
ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>());
Ref<ShaderMaterial> smat;
- smat.instance();
+ smat.instantiate();
Ref<Shader> shader;
- shader.instance();
+ shader.instantiate();
String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid());
@@ -406,10 +406,10 @@ Ref<Resource> PanoramaSkyMaterialConversionPlugin::convert(const Ref<Resource> &
ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>());
Ref<ShaderMaterial> smat;
- smat.instance();
+ smat.instantiate();
Ref<Shader> shader;
- shader.instance();
+ shader.instantiate();
String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid());
@@ -443,10 +443,10 @@ Ref<Resource> PhysicalSkyMaterialConversionPlugin::convert(const Ref<Resource> &
ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>());
Ref<ShaderMaterial> smat;
- smat.instance();
+ smat.instantiate();
Ref<Shader> shader;
- shader.instance();
+ shader.instantiate();
String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid());
diff --git a/editor/plugins/mesh_editor_plugin.cpp b/editor/plugins/mesh_editor_plugin.cpp
index 9d29c31522..fcc6b84efb 100644
--- a/editor/plugins/mesh_editor_plugin.cpp
+++ b/editor/plugins/mesh_editor_plugin.cpp
@@ -65,7 +65,7 @@ void MeshEditor::_notification(int p_what) {
}
void MeshEditor::_update_rotation() {
- Transform t;
+ Transform3D t;
t.basis.rotate(Vector3(0, 1, 0), -rot_y);
t.basis.rotate(Vector3(1, 0, 0), -rot_x);
rotation->set_transform(t);
@@ -85,7 +85,7 @@ void MeshEditor::edit(Ref<Mesh> p_mesh) {
if (m != 0) {
m = 1.0 / m;
m *= 0.5;
- Transform xform;
+ Transform3D xform;
xform.basis.scale(Vector3(m, m, m));
xform.origin = -xform.basis.xform(ofs); //-ofs*m;
//xform.origin.z -= aabb.get_longest_axis_size() * 2;
@@ -110,23 +110,23 @@ void MeshEditor::_bind_methods() {
MeshEditor::MeshEditor() {
viewport = memnew(SubViewport);
Ref<World3D> world_3d;
- world_3d.instance();
+ world_3d.instantiate();
viewport->set_world_3d(world_3d); //use own world
add_child(viewport);
viewport->set_disable_input(true);
viewport->set_msaa(Viewport::MSAA_2X);
set_stretch(true);
camera = memnew(Camera3D);
- camera->set_transform(Transform(Basis(), Vector3(0, 0, 1.1)));
+ camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1)));
camera->set_perspective(45, 0.1, 10);
viewport->add_child(camera);
light1 = memnew(DirectionalLight3D);
- light1->set_transform(Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
+ light1->set_transform(Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
viewport->add_child(light1);
light2 = memnew(DirectionalLight3D);
- light2->set_transform(Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
+ light2->set_transform(Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
light2->set_color(Color(0.7, 0.7, 0.7));
viewport->add_child(light2);
@@ -182,6 +182,6 @@ void EditorInspectorPluginMesh::parse_begin(Object *p_object) {
MeshEditorPlugin::MeshEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginMesh> plugin;
- plugin.instance();
+ plugin.instantiate();
add_inspector_plugin(plugin);
}
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index 0d2b2ea2f5..7434accc1a 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -153,14 +153,18 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
ur->add_undo_method(node->get_parent(), "remove_child", cshape);
ur->commit_action();
} break;
- case MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE: {
+
+ case MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE:
+ case MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE: {
if (node == get_tree()->get_edited_scene_root()) {
err_dialog->set_text(TTR("Can't create a single convex collision shape for the scene root."));
err_dialog->popup_centered();
return;
}
- Ref<Shape3D> shape = mesh->create_convex_shape();
+ bool simplify = (p_option == MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE);
+
+ Ref<Shape3D> shape = mesh->create_convex_shape(true, simplify);
if (shape.is_null()) {
err_dialog->set_text(TTR("Couldn't create a single convex collision shape."));
@@ -169,7 +173,11 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
}
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
- ur->create_action(TTR("Create Single Convex Shape"));
+ if (simplify) {
+ ur->create_action(TTR("Create Simplified Convex Shape"));
+ } else {
+ ur->create_action(TTR("Create Single Convex Shape"));
+ }
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shape);
@@ -186,6 +194,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
ur->commit_action();
} break;
+
case MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES: {
if (node == get_tree()->get_edited_scene_root()) {
err_dialog->set_text(TTR("Can't create multiple convex collision shapes for the scene root."));
@@ -441,8 +450,10 @@ MeshInstance3DEditor::MeshInstance3DEditor() {
options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection."));
options->get_popup()->add_item(TTR("Create Single Convex Collision Sibling"), MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE);
options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection."));
+ options->get_popup()->add_item(TTR("Create Simplified Convex Collision Sibling"), MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE);
+ options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy."));
options->get_popup()->add_item(TTR("Create Multiple Convex Collision Siblings"), MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES);
- options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between the two above options."));
+ options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision."));
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Create Navigation Mesh"), MENU_OPTION_CREATE_NAVMESH);
options->get_popup()->add_separator();
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h
index 69f494de7f..98b667c978 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.h
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.h
@@ -43,6 +43,7 @@ class MeshInstance3DEditor : public Control {
MENU_OPTION_CREATE_STATIC_TRIMESH_BODY,
MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE,
MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE,
+ MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE,
MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES,
MENU_OPTION_CREATE_NAVMESH,
MENU_OPTION_CREATE_OUTLINE_MESH,
diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp
index 6f1f243444..5d1d29cbc8 100644
--- a/editor/plugins/mesh_library_editor_plugin.cpp
+++ b/editor/plugins/mesh_library_editor_plugin.cpp
@@ -127,7 +127,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library,
continue;
}
- //Transform shape_transform = sb->shape_owner_get_transform(E->get());
+ //Transform3D shape_transform = sb->shape_owner_get_transform(E->get());
//shape_transform.set_origin(shape_transform.get_origin() - phys_offset);
@@ -147,7 +147,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library,
p_library->set_item_shapes(id, collisions);
Ref<NavigationMesh> navmesh;
- Transform navmesh_transform;
+ Transform3D navmesh_transform;
for (int j = 0; j < mi->get_child_count(); j++) {
Node *child2 = mi->get_child(j);
if (!Object::cast_to<NavigationRegion3D>(child2)) {
@@ -170,7 +170,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library,
if (true) {
Vector<Ref<Mesh>> meshes;
- Vector<Transform> transforms;
+ Vector<Transform3D> transforms;
Vector<int> ids = p_library->get_item_list();
for (int i = 0; i < ids.size(); i++) {
if (mesh_instances.find(ids[i])) {
@@ -193,7 +193,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library,
void MeshLibraryEditor::_import_scene_cbk(const String &p_str) {
Ref<PackedScene> ps = ResourceLoader::load(p_str, "PackedScene");
ERR_FAIL_COND(ps.is_null());
- Node *scene = ps->instance();
+ Node *scene = ps->instantiate();
ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'.");
diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp
index 19c6dcf402..48b885930f 100644
--- a/editor/plugins/multimesh_editor_plugin.cpp
+++ b/editor/plugins/multimesh_editor_plugin.cpp
@@ -111,7 +111,7 @@ void MultiMeshEditor::_populate() {
return;
}
- Transform geom_xform = node->get_global_transform().affine_inverse() * ss_instance->get_global_transform();
+ Transform3D geom_xform = node->get_global_transform().affine_inverse() * ss_instance->get_global_transform();
Vector<Face3> geometry = ss_instance->get_faces(VisualInstance3D::FACES_SOLID);
@@ -167,7 +167,7 @@ void MultiMeshEditor::_populate() {
float _scale = populate_scale->get_value();
int axis = populate_axis->get_selected();
- Transform axis_xform;
+ Transform3D axis_xform;
if (axis == Vector3::AXIS_Z) {
axis_xform.rotate(Vector3(1, 0, 0), -Math_PI * 0.5);
}
@@ -191,7 +191,7 @@ void MultiMeshEditor::_populate() {
Vector3 normal = face.get_plane().normal;
Vector3 op_axis = (face.vertex[0] - face.vertex[1]).normalized();
- Transform xform;
+ Transform3D xform;
xform.set_look_at(pos, pos + op_axis, normal);
xform = xform * axis_xform;
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 023d91be30..72a63539b3 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "core/math/camera_matrix.h"
+#include "core/math/math_funcs.h"
#include "core/os/keyboard.h"
#include "core/string/print_string.h"
#include "core/templates/sort_array.h"
@@ -85,10 +86,10 @@ void ViewportRotationControl::_notification(int p_what) {
axis_menu_options.clear();
axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT);
axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP);
- axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT);
+ axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR);
axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT);
axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM);
- axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR);
+ axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT);
axis_colors.clear();
axis_colors.push_back(get_theme_color("axis_x_color", "Editor"));
@@ -309,7 +310,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
bool manipulated = Input::get_singleton()->get_mouse_button_mask() & (2 | 4);
manipulated |= Input::get_singleton()->is_key_pressed(KEY_SHIFT);
manipulated |= Input::get_singleton()->is_key_pressed(KEY_ALT);
- manipulated |= Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+ manipulated |= Input::get_singleton()->is_key_pressed(KEY_CTRL);
float orbit_inertia = MAX(0.00001, manipulated ? manip_orbit_inertia : free_orbit_inertia);
float translation_inertia = MAX(0.0001, manipulated ? manip_translation_inertia : free_translation_inertia);
@@ -344,7 +345,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
equal = false;
}
- if (!equal || p_interp_delta == 0 || is_freelook_active() || is_orthogonal != orthogonal) {
+ if (!equal || p_interp_delta == 0 || is_orthogonal != orthogonal) {
camera->set_global_transform(to_camera_transform(camera_cursor));
if (orthogonal) {
@@ -361,8 +362,8 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
}
}
-Transform Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const {
- Transform camera_transform;
+Transform3D Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const {
+ Transform3D camera_transform;
camera_transform.translate(p_cursor.pos);
camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot);
camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot);
@@ -410,7 +411,7 @@ float Node3DEditorViewport::get_fov() const {
return CLAMP(spatial_editor->get_fov(), MIN_FOV, MAX_FOV);
}
-Transform Node3DEditorViewport::_get_camera_transform() const {
+Transform3D Node3DEditorViewport::_get_camera_transform() const {
return camera->get_global_transform();
}
@@ -466,22 +467,31 @@ void Node3DEditorViewport::_select_clicked(bool p_append, bool p_single, bool p_
}
void Node3DEditorViewport::_select(Node *p_node, bool p_append, bool p_single) {
- if (!p_append) {
- editor_selection->clear();
- }
-
- if (editor_selection->is_selected(p_node)) {
- //erase
- editor_selection->remove_node(p_node);
+ // Add or remove a single node from the selection
+ if (p_append && p_single) {
+ if (editor_selection->is_selected(p_node)) {
+ // Already in the selection, remove it from the selected nodes
+ editor_selection->remove_node(p_node);
+ } else {
+ // Add the item to the selection
+ editor_selection->add_node(p_node);
+ }
+ } else if (p_append && !p_single) {
+ // Add the item to the selection
+ editor_selection->add_node(p_node);
} else {
+ // No append; single select
+ editor_selection->clear();
editor_selection->add_node(p_node);
- }
-
- if (p_single) {
+ // Reselect
if (Engine::get_singleton()->is_editor_hint()) {
editor->call("edit_node", p_node);
}
}
+
+ if (editor_selection->get_selected_node_list().size() == 1) {
+ editor->push_item(editor_selection->get_selected_node_list()[0]);
+ }
}
ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle, bool p_alt_select) {
@@ -631,7 +641,7 @@ Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
}
Vector2 screen_he = cm.get_viewport_half_extents();
- Transform camera_transform;
+ Transform3D camera_transform;
camera_transform.translate(cursor.pos);
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
@@ -794,22 +804,22 @@ static int _get_key_modifier_setting(const String &p_property) {
case 3:
return KEY_META;
case 4:
- return KEY_CONTROL;
+ return KEY_CTRL;
}
return 0;
}
static int _get_key_modifier(Ref<InputEventWithModifiers> e) {
- if (e->get_shift()) {
+ if (e->is_shift_pressed()) {
return KEY_SHIFT;
}
- if (e->get_alt()) {
+ if (e->is_alt_pressed()) {
return KEY_ALT;
}
- if (e->get_control()) {
- return KEY_CONTROL;
+ if (e->is_ctrl_pressed()) {
+ return KEY_CTRL;
}
- if (e->get_metakey()) {
+ if (e->is_meta_pressed()) {
return KEY_META;
}
return 0;
@@ -829,7 +839,7 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
Vector3 ray_pos = _get_ray_pos(Vector2(p_screenpos.x, p_screenpos.y));
Vector3 ray = _get_ray(Vector2(p_screenpos.x, p_screenpos.y));
- Transform gt = spatial_editor->get_gizmo_transform();
+ Transform3D gt = spatial_editor->get_gizmo_transform();
float gs = gizmo_scale;
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) {
@@ -1024,7 +1034,7 @@ bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) {
}
void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
- _find_items_at_pos(b->get_position(), clicked_includes_current, selection_results, b->get_shift());
+ _find_items_at_pos(b->get_position(), clicked_includes_current, selection_results, b->is_shift_pressed());
Node *scene = editor->get_edited_scene();
@@ -1037,7 +1047,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
}
}
- clicked_wants_append = b->get_shift();
+ clicked_wants_append = b->is_shift_pressed();
if (selection_results.size() == 1) {
clicked = selection_results[0].item->get_instance_id();
@@ -1149,7 +1159,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (_edit.mode == TRANSFORM_NONE && b->is_pressed()) {
- if (b->get_alt()) {
+ if (b->is_alt_pressed()) {
if (nav_scheme == NAVIGATION_MAYA) {
break;
}
@@ -1234,7 +1244,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
case MOUSE_BUTTON_LEFT: {
if (b->is_pressed()) {
NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
- if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->get_alt()) {
+ if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->is_alt_pressed()) {
break;
}
@@ -1244,6 +1254,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
_edit.mouse_pos = b->get_position();
+ _edit.original_mouse_pos = b->get_position();
_edit.snap = spatial_editor->is_snap_enabled();
_edit.mode = TRANSFORM_NONE;
@@ -1262,7 +1273,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
int handle = -1;
Vector3 point;
Vector3 normal;
- bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, b->get_shift());
+ bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, b->is_shift_pressed());
if (inters && handle != -1) {
_edit.gizmo = seg;
_edit.gizmo_handle = handle;
@@ -1279,7 +1290,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
clicked = ObjectID();
clicked_includes_current = false;
- if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->get_command()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
+ if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
/* HANDLE ROTATION */
if (get_selected_count() == 0) {
break; //bye
@@ -1314,11 +1325,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
int gizmo_handle = -1;
- clicked = _select_ray(b->get_position(), b->get_shift(), clicked_includes_current, &gizmo_handle, b->get_shift());
+ clicked = _select_ray(b->get_position(), b->is_shift_pressed(), clicked_includes_current, &gizmo_handle, b->is_shift_pressed());
//clicking is always deferred to either move or release
- clicked_wants_append = b->get_shift();
+ clicked_wants_append = b->is_shift_pressed();
if (clicked.is_null()) {
if (!clicked_wants_append) {
@@ -1396,6 +1407,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
} break;
+ default:
+ break;
}
}
@@ -1441,16 +1454,17 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(n + ": " + String(v));
} else if (m->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
- if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) {
+ if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) {
nav_mode = NAVIGATION_ORBIT;
- } else if (nav_scheme == NAVIGATION_MODO && m->get_alt() && m->get_shift()) {
+ } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) {
nav_mode = NAVIGATION_PAN;
- } else if (nav_scheme == NAVIGATION_MODO && m->get_alt() && m->get_control()) {
+ } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_ctrl_pressed()) {
nav_mode = NAVIGATION_ZOOM;
- } else if (nav_scheme == NAVIGATION_MODO && m->get_alt()) {
+ } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed()) {
nav_mode = NAVIGATION_ORBIT;
} else {
- if (clicked.is_valid()) {
+ bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 10 * EDSCALE;
+ if (clicked.is_valid() && movement_threshold_passed) {
if (!clicked_includes_current) {
_select_clicked(clicked_wants_append, true);
// Processing was deferred.
@@ -1579,10 +1593,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform original = se->original;
- Transform original_local = se->original_local;
- Transform base = Transform(Basis(), _edit.center);
- Transform t;
+ Transform3D original = se->original;
+ Transform3D original_local = se->original_local;
+ Transform3D base = Transform3D(Basis(), _edit.center);
+ Transform3D t;
Vector3 local_scale;
if (local_coords) {
@@ -1608,7 +1622,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
motion.snap(Vector3(snap, snap, snap));
}
- Transform r;
+ Transform3D r;
r.basis.scale(motion + Vector3(1, 1, 1));
t = base * (r * (base.inverse() * original));
@@ -1701,8 +1715,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform original = se->original;
- Transform t;
+ Transform3D original = se->original;
+ Transform3D t;
if (local_coords) {
if (_edit.snap || spatial_editor->is_snap_enabled()) {
@@ -1797,10 +1811,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform t;
+ Transform3D t;
if (local_coords) {
- Transform original_local = se->original_local;
+ Transform3D original_local = se->original_local;
Basis rot = Basis(axis, angle);
t.basis = original_local.get_basis().orthonormalized() * rot;
@@ -1811,9 +1825,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
sp->set_scale(original_local.basis.get_scale()); // re-apply original scale
} else {
- Transform original = se->original;
- Transform r;
- Transform base = Transform(Basis(), _edit.center);
+ Transform3D original = se->original;
+ Transform3D r;
+ Transform3D base = Transform3D(Basis(), _edit.center);
r.basis.rotate(plane.normal, angle);
t = base * r * base.inverse() * original;
@@ -1831,7 +1845,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
} else if ((m->get_button_mask() & MOUSE_BUTTON_MASK_RIGHT) || freelook_active) {
- if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) {
+ if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) {
nav_mode = NAVIGATION_ZOOM;
} else if (freelook_active) {
nav_mode = NAVIGATION_LOOK;
@@ -1924,7 +1938,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
} else if (nav_scheme == NAVIGATION_MAYA) {
- if (pan_gesture->get_alt()) {
+ if (pan_gesture->is_alt_pressed()) {
nav_mode = NAVIGATION_PAN;
}
}
@@ -1962,6 +1976,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
return;
}
+ if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_numpad")) {
+ const uint32_t code = k->get_keycode();
+ if (code >= KEY_0 && code <= KEY_9) {
+ k->set_keycode(code - KEY_0 + KEY_KP_0);
+ }
+ }
+
if (ED_IS_SHORTCUT("spatial_editor/snap", p_event)) {
if (_edit.mode != TRANSFORM_NONE) {
_edit.snap = !_edit.snap;
@@ -2053,11 +2074,11 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const
real_t pan_speed = 1 / 150.0;
int pan_speed_modifier = 10;
- if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift()) {
+ if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
pan_speed *= pan_speed_modifier;
}
- Transform camera_transform;
+ Transform3D camera_transform;
camera_transform.translate(cursor.pos);
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
@@ -2078,7 +2099,7 @@ void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const
real_t zoom_speed = 1 / 80.0;
int zoom_speed_modifier = 10;
- if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift()) {
+ if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
zoom_speed *= zoom_speed_modifier;
}
@@ -2145,7 +2166,7 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const
const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis");
// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".
- const Transform prev_camera_transform = to_camera_transform(cursor);
+ const Transform3D prev_camera_transform = to_camera_transform(cursor);
if (invert_y_axis) {
cursor.x_rot -= p_relative.y * radians_per_pixel;
@@ -2158,7 +2179,7 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const
cursor.y_rot += p_relative.x * radians_per_pixel;
// Look is like the opposite of Orbit: the focus point rotates around the camera
- Transform camera_transform = to_camera_transform(cursor);
+ Transform3D camera_transform = to_camera_transform(cursor);
Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
Vector3 diff = prev_pos - pos;
@@ -2444,7 +2465,7 @@ void Node3DEditorViewport::_notification(int p_what) {
continue;
}
- Transform t = sp->get_global_gizmo_transform();
+ Transform3D t = sp->get_global_gizmo_transform();
VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sp);
AABB new_aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp);
@@ -2499,25 +2520,21 @@ void Node3DEditorViewport::_notification(int p_what) {
}
if (show_info) {
+ const String viewport_size = vformat(String::utf8("%d × %d"), viewport->get_size().x, viewport->get_size().y);
String text;
- text += "X: " + rtos(current_camera->get_translation().x).pad_decimals(1) + "\n";
- text += "Y: " + rtos(current_camera->get_translation().y).pad_decimals(1) + "\n";
- text += "Z: " + rtos(current_camera->get_translation().z).pad_decimals(1) + "\n";
- text += TTR("Pitch") + ": " + itos(Math::round(current_camera->get_rotation_degrees().x)) + "\n";
- text += TTR("Yaw") + ": " + itos(Math::round(current_camera->get_rotation_degrees().y)) + "\n\n";
-
- text += TTR("Size") +
- vformat(
- ": %dx%d (%.1fMP)\n",
- viewport->get_size().x,
- viewport->get_size().y,
- viewport->get_size().x * viewport->get_size().y * 0.000'001);
- text += TTR("Objects Drawn") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_OBJECTS_IN_FRAME)) + "\n";
- text += TTR("Material Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_MATERIAL_CHANGES_IN_FRAME)) + "\n";
- text += TTR("Shader Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_SHADER_CHANGES_IN_FRAME)) + "\n";
- text += TTR("Surface Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_SURFACE_CHANGES_IN_FRAME)) + "\n";
- text += TTR("Draw Calls") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)) + "\n";
- text += TTR("Vertices") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_VERTICES_IN_FRAME));
+ text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1));
+ text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1));
+ text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1));
+ text += "\n";
+ text += vformat(
+ TTR("Size: %s (%.1fMP)\n"),
+ viewport_size,
+ viewport->get_size().x * viewport->get_size().y * 0.000001);
+
+ text += "\n";
+ text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME));
+ text += vformat(TTR("Primitives: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME));
+ text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME));
info_label->set_text(text);
}
@@ -2878,7 +2895,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
break;
}
- Transform camera_transform = camera->get_global_transform();
+ Transform3D camera_transform = camera->get_global_transform();
List<Node *> &selection = editor_selection->get_selected_node_list();
@@ -2895,7 +2912,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
continue;
}
- Transform xform;
+ Transform3D xform;
if (orthogonal) {
xform = sp->get_global_transform();
xform.basis.set_euler(camera_transform.basis.get_euler());
@@ -2915,7 +2932,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
break;
}
- Transform camera_transform = camera->get_global_transform();
+ Transform3D camera_transform = camera->get_global_transform();
List<Node *> &selection = editor_selection->get_selected_node_list();
@@ -3060,9 +3077,9 @@ void Node3DEditorViewport::_menu_option(int p_option) {
case VIEW_DISPLAY_NORMAL_BUFFER:
case VIEW_DISPLAY_DEBUG_SHADOW_ATLAS:
case VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS:
- case VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO:
- case VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING:
- case VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION:
+ case VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO:
+ case VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING:
+ case VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION:
case VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE:
case VIEW_DISPLAY_DEBUG_SSAO:
case VIEW_DISPLAY_DEBUG_PSSM_SPLITS:
@@ -3086,9 +3103,9 @@ void Node3DEditorViewport::_menu_option(int p_option) {
VIEW_DISPLAY_WIREFRAME,
VIEW_DISPLAY_DEBUG_SHADOW_ATLAS,
VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS,
- VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO,
- VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING,
- VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION,
+ VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO,
+ VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING,
+ VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION,
VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE,
VIEW_DISPLAY_DEBUG_SSAO,
VIEW_DISPLAY_DEBUG_GI_BUFFER,
@@ -3114,9 +3131,9 @@ void Node3DEditorViewport::_menu_option(int p_option) {
Viewport::DEBUG_DRAW_WIREFRAME,
Viewport::DEBUG_DRAW_SHADOW_ATLAS,
Viewport::DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS,
- Viewport::DEBUG_DRAW_GI_PROBE_ALBEDO,
- Viewport::DEBUG_DRAW_GI_PROBE_LIGHTING,
- Viewport::DEBUG_DRAW_GI_PROBE_EMISSION,
+ Viewport::DEBUG_DRAW_VOXEL_GI_ALBEDO,
+ Viewport::DEBUG_DRAW_VOXEL_GI_LIGHTING,
+ Viewport::DEBUG_DRAW_VOXEL_GI_EMISSION,
Viewport::DEBUG_DRAW_SCENE_LUMINANCE,
Viewport::DEBUG_DRAW_SSAO,
Viewport::DEBUG_DRAW_GI_BUFFER,
@@ -3249,14 +3266,12 @@ void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {
if (!preview) {
preview_camera->hide();
}
- view_menu->set_disabled(false);
surface->update();
} else {
previewing = preview;
previewing->connect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), preview->get_camera()); //replace
- view_menu->set_disabled(true);
surface->update();
}
}
@@ -3315,9 +3330,9 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
return;
}
- Transform xform = spatial_editor->get_gizmo_transform();
+ Transform3D xform = spatial_editor->get_gizmo_transform();
- Transform camera_xform = camera->get_transform();
+ Transform3D camera_xform = camera->get_transform();
if (xform.origin.distance_squared_to(camera_xform.origin) < 0.01) {
for (int i = 0; i < 3; i++) {
@@ -3503,7 +3518,6 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
previewing = Object::cast_to<Camera3D>(pv);
previewing->connect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), previewing->get_camera()); //replace
- view_menu->set_disabled(true);
surface->update();
preview_camera->set_pressed(true);
preview_camera->show();
@@ -3551,8 +3565,8 @@ Dictionary Node3DEditorViewport::get_state() const {
void Node3DEditorViewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("update_transform_gizmo_view"), &Node3DEditorViewport::update_transform_gizmo_view); // Used by call_deferred.
- ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Node3DEditorViewport::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw"), &Node3DEditorViewport::drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Node3DEditorViewport::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Node3DEditorViewport::drop_data_fw);
ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport")));
ADD_SIGNAL(MethodInfo("clicked", PropertyInfo(Variant::OBJECT, "viewport")));
@@ -3711,7 +3725,7 @@ void Node3DEditorViewport::_create_preview(const Vector<String> &files) const {
preview_node->add_child(mesh_instance);
} else {
if (scene.is_valid()) {
- Node *instance = scene->instance();
+ Node *instance = scene->instantiate();
if (instance) {
preview_node->add_child(instance);
}
@@ -3756,51 +3770,51 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
- Node *instanced_scene = nullptr;
+ Node *instantiated_scene = nullptr;
if (mesh != nullptr || scene != nullptr) {
if (mesh != nullptr) {
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
mesh_instance->set_mesh(mesh);
mesh_instance->set_name(path.get_file().get_basename());
- instanced_scene = mesh_instance;
+ instantiated_scene = mesh_instance;
} else {
if (!scene.is_valid()) { // invalid scene
return false;
} else {
- instanced_scene = scene->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ instantiated_scene = scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
}
}
}
- if (instanced_scene == nullptr) {
+ if (instantiated_scene == nullptr) {
return false;
}
if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing
- if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instanced_scene)) {
- memdelete(instanced_scene);
+ if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) {
+ memdelete(instantiated_scene);
return false;
}
}
if (scene != nullptr) {
- instanced_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path));
+ instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path));
}
- editor_data->get_undo_redo().add_do_method(parent, "add_child", instanced_scene);
- editor_data->get_undo_redo().add_do_method(instanced_scene, "set_owner", editor->get_edited_scene());
- editor_data->get_undo_redo().add_do_reference(instanced_scene);
- editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene);
+ editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene);
+ editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene());
+ editor_data->get_undo_redo().add_do_reference(instantiated_scene);
+ editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instantiated_scene);
- String new_name = parent->validate_child_name(instanced_scene);
+ String new_name = parent->validate_child_name(instantiated_scene);
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name);
editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
- Node3D *node3d = Object::cast_to<Node3D>(instanced_scene);
+ Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene);
if (node3d) {
- Transform global_transform;
+ Transform3D global_transform;
Node3D *parent_node3d = Object::cast_to<Node3D>(parent);
if (parent_node3d) {
global_transform = parent_node3d->get_global_gizmo_transform();
@@ -3809,7 +3823,7 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
global_transform.origin = spatial_editor->snap_point(_get_instance_position(p_point));
global_transform.basis *= node3d->get_transform().basis;
- editor_data->get_undo_redo().add_do_method(instanced_scene, "set_global_transform", global_transform);
+ editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_global_transform", global_transform);
}
return true;
@@ -3852,7 +3866,7 @@ void Node3DEditorViewport::_perform_drop_data() {
}
bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
- bool can_instance = false;
+ bool can_instantiate = false;
if (!preview_node->is_inside_tree()) {
Dictionary d = p_data;
@@ -3874,11 +3888,11 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
String type = res->get_class();
if (type == "PackedScene") {
Ref<PackedScene> sdata = ResourceLoader::load(files[i]);
- Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
- if (!instanced_scene) {
+ Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ if (!instantiated_scene) {
continue;
}
- memdelete(instanced_scene);
+ memdelete(instantiated_scene);
} else if (type == "Mesh" || type == "ArrayMesh" || type == "PrimitiveMesh") {
Ref<Mesh> mesh = ResourceLoader::load(files[i]);
if (!mesh.is_valid()) {
@@ -3887,24 +3901,24 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
} else {
continue;
}
- can_instance = true;
+ can_instantiate = true;
break;
}
}
- if (can_instance) {
+ if (can_instantiate) {
_create_preview(files);
}
}
} else {
- can_instance = true;
+ can_instantiate = true;
}
- if (can_instance) {
- Transform global_transform = Transform(Basis(), _get_instance_position(p_point));
+ if (can_instantiate) {
+ Transform3D global_transform = Transform3D(Basis(), _get_instance_position(p_point));
preview_node->set_global_transform(global_transform);
}
- return can_instance;
+ return can_instantiate;
}
void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
@@ -4036,9 +4050,9 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
display_submenu->add_separator();
display_submenu->add_radio_check_item(TTR("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS);
display_submenu->add_separator();
- display_submenu->add_radio_check_item(TTR("GIProbe Lighting"), VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING);
- display_submenu->add_radio_check_item(TTR("GIProbe Albedo"), VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO);
- display_submenu->add_radio_check_item(TTR("GIProbe Emission"), VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION);
+ display_submenu->add_radio_check_item(TTR("VoxelGI Lighting"), VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING);
+ display_submenu->add_radio_check_item(TTR("VoxelGI Albedo"), VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO);
+ display_submenu->add_radio_check_item(TTR("VoxelGI Emission"), VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION);
display_submenu->add_separator();
display_submenu->add_radio_check_item(TTR("SDFGI Cascades"), VIEW_DISPLAY_DEBUG_SDFGI);
display_submenu->add_radio_check_item(TTR("SDFGI Probes"), VIEW_DISPLAY_DEBUG_SDFGI_PROBES);
@@ -4574,7 +4588,7 @@ void Node3DEditor::update_transform_gizmo() {
continue;
}
- Transform xf = se->sp->get_global_gizmo_transform();
+ Transform3D xf = se->sp->get_global_gizmo_transform();
if (first) {
center.position = xf.origin;
@@ -4955,7 +4969,7 @@ void Node3DEditor::_snap_update() {
}
void Node3DEditor::_xform_dialog_action() {
- Transform t;
+ Transform3D t;
//translation
Vector3 scale;
Vector3 rotate;
@@ -4988,7 +5002,7 @@ void Node3DEditor::_xform_dialog_action() {
bool post = xform_type->get_selected() > 0;
- Transform tr = sp->get_global_gizmo_transform();
+ Transform3D tr = sp->get_global_gizmo_transform();
if (post) {
tr = tr * t;
} else {
@@ -5056,11 +5070,11 @@ void Node3DEditor::_update_camera_override_button(bool p_game_running) {
if (p_game_running) {
button->set_disabled(false);
- button->set_tooltip(TTR("Game Camera Override\nNo game instance running."));
+ button->set_tooltip(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera."));
} else {
button->set_disabled(true);
button->set_pressed(false);
- button->set_tooltip(TTR("Game Camera Override\nOverrides game camera with editor viewport camera."));
+ button->set_tooltip(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature."));
}
}
@@ -5314,7 +5328,7 @@ void Node3DEditor::_init_indicators() {
origin_enabled = true;
grid_enabled = true;
- indicator_mat.instance();
+ indicator_mat.instantiate();
indicator_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
@@ -5394,7 +5408,7 @@ void Node3DEditor::_init_indicators() {
"}");
for (int i = 0; i < 3; i++) {
- grid_mat[i].instance();
+ grid_mat[i].instantiate();
grid_mat[i]->set_shader(grid_shader);
}
@@ -5834,7 +5848,7 @@ void Node3DEditor::_init_grid() {
return;
}
Camera3D *camera = get_editor_viewport(0)->camera;
- Vector3 camera_position = camera->get_translation();
+ Vector3 camera_position = camera->get_position();
if (camera_position == Vector3()) {
return; // Camera3D is invalid, don't draw the grid.
}
@@ -6180,7 +6194,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() {
if (ss->intersect_ray(from, to, result, excluded)) {
Vector3 position_offset = d["position_offset"];
- Transform new_transform = sp->get_global_transform();
+ Transform3D new_transform = sp->get_global_transform();
new_transform.origin.y = result.position.y;
new_transform.origin = new_transform.origin - position_offset;
@@ -6204,7 +6218,7 @@ void Node3DEditor::_unhandled_key_input(Ref<InputEvent> p_event) {
return;
}
- snap_key_enabled = Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+ snap_key_enabled = Input::get_singleton()->is_key_pressed(KEY_CTRL);
}
void Node3DEditor::_sun_environ_settings_pressed() {
@@ -6213,31 +6227,47 @@ void Node3DEditor::_sun_environ_settings_pressed() {
sun_environ_popup->popup();
}
-void Node3DEditor::_add_sun_to_scene() {
+void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) {
sun_environ_popup->hide();
+ if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
+ // Prevent infinite feedback loop between the sun and environment methods.
+ _add_environment_to_scene(true);
+ }
+
Node *base = get_tree()->get_edited_scene_root();
if (!base) {
- EditorNode::get_singleton()->show_warning(TTR("A root node is needed for this operation"));
- return;
+ // Create a root node so we can add child nodes to it.
+ EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D));
+ base = get_tree()->get_edited_scene_root();
}
ERR_FAIL_COND(!base);
Node *new_sun = preview_sun->duplicate();
undo_redo->create_action("Add Preview Sun to Scene");
undo_redo->add_do_method(base, "add_child", new_sun);
+ // Move to the beginning of the scene tree since more "global" nodes
+ // generally look better when placed at the top.
+ undo_redo->add_do_method(base, "move_child", new_sun, 0);
undo_redo->add_do_method(new_sun, "set_owner", base);
undo_redo->add_undo_method(base, "remove_child", new_sun);
undo_redo->add_do_reference(new_sun);
undo_redo->commit_action();
}
-void Node3DEditor::_add_environment_to_scene() {
+
+void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) {
sun_environ_popup->hide();
+ if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
+ // Prevent infinite feedback loop between the sun and environment methods.
+ _add_sun_to_scene(true);
+ }
+
Node *base = get_tree()->get_edited_scene_root();
if (!base) {
- EditorNode::get_singleton()->show_warning(TTR("A root node is needed for this operation"));
- return;
+ // Create a root node so we can add child nodes to it.
+ EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D));
+ base = get_tree()->get_edited_scene_root();
}
ERR_FAIL_COND(!base);
@@ -6246,6 +6276,9 @@ void Node3DEditor::_add_environment_to_scene() {
undo_redo->create_action("Add Preview Environment to Scene");
undo_redo->add_do_method(base, "add_child", new_env);
+ // Move to the beginning of the scene tree since more "global" nodes
+ // generally look better when placed at the top.
+ undo_redo->add_do_method(base, "move_child", new_env, 0);
undo_redo->add_do_method(new_env, "set_owner", base);
undo_redo->add_undo_method(base, "remove_child", new_env);
undo_redo->add_do_reference(new_env);
@@ -6368,7 +6401,7 @@ void Node3DEditor::_request_gizmo(Object *p_obj) {
if (!sp) {
return;
}
- if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_a_parent_of(sp)))) {
+ if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) {
Ref<EditorNode3DGizmo> seg;
for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {
@@ -6442,7 +6475,7 @@ void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {
}
void Node3DEditor::_node_added(Node *p_node) {
- if (EditorNode::get_singleton()->get_scene_root()->is_a_parent_of(p_node)) {
+ if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
if (Object::cast_to<WorldEnvironment>(p_node)) {
world_env_count++;
if (world_env_count == 1) {
@@ -6458,7 +6491,7 @@ void Node3DEditor::_node_added(Node *p_node) {
}
void Node3DEditor::_node_removed(Node *p_node) {
- if (EditorNode::get_singleton()->get_scene_root()->is_a_parent_of(p_node)) {
+ if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
if (Object::cast_to<WorldEnvironment>(p_node)) {
world_env_count--;
if (world_env_count == 0) {
@@ -6490,14 +6523,14 @@ void Node3DEditor::_register_all_gizmos() {
add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));
add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin)));
add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin)));
- add_gizmo_plugin(Ref<VisibilityNotifier3DGizmoPlugin>(memnew(VisibilityNotifier3DGizmoPlugin)));
+ add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin)));
add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin)));
add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin)));
add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin)));
add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));
add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin)));
- add_gizmo_plugin(Ref<GIProbeGizmoPlugin>(memnew(GIProbeGizmoPlugin)));
- add_gizmo_plugin(Ref<BakedLightmapGizmoPlugin>(memnew(BakedLightmapGizmoPlugin)));
+ add_gizmo_plugin(Ref<VoxelGIGizmoPlugin>(memnew(VoxelGIGizmoPlugin)));
+ add_gizmo_plugin(Ref<LightmapGIGizmoPlugin>(memnew(LightmapGIGizmoPlugin)));
add_gizmo_plugin(Ref<LightmapProbeGizmoPlugin>(memnew(LightmapProbeGizmoPlugin)));
add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin)));
add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin)));
@@ -6545,9 +6578,11 @@ void Node3DEditor::clear() {
void Node3DEditor::_sun_direction_draw() {
sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1));
- sun_direction_material->set_shader_param("sun_direction", -preview_sun->get_transform().basis.get_axis(Vector3::AXIS_Z));
- float nrg = sun_energy->get_value();
- sun_direction_material->set_shader_param("sun_color", Vector3(sun_color->get_pick_color().r * nrg, sun_color->get_pick_color().g * nrg, sun_color->get_pick_color().b * nrg));
+ Vector3 z_axis = preview_sun->get_transform().basis.get_axis(Vector3::AXIS_Z);
+ z_axis = get_editor_viewport(0)->camera->get_camera_transform().basis.xform_inv(z_axis);
+ sun_direction_material->set_shader_param("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z));
+ Color color = sun_color->get_pick_color() * sun_energy->get_value();
+ sun_direction_material->set_shader_param("sun_color", Vector3(color.r, color.g, color.b));
}
void Node3DEditor::_preview_settings_changed() {
@@ -6556,8 +6591,8 @@ void Node3DEditor::_preview_settings_changed() {
}
{ // preview sun
- Transform t;
- t.basis = sun_rotation;
+ Transform3D t;
+ t.basis = Basis(Vector3(sun_rotation.x, sun_rotation.y, 0));
preview_sun->set_transform(t);
sun_direction->update();
preview_sun->set_param(Light3D::PARAM_ENERGY, sun_energy->get_value());
@@ -6579,11 +6614,20 @@ void Node3DEditor::_preview_settings_changed() {
environment->set_tonemapper(environ_tonemap_button->is_pressed() ? Environment::TONE_MAPPER_FILMIC : Environment::TONE_MAPPER_LINEAR);
}
}
+
void Node3DEditor::_load_default_preview_settings() {
sun_environ_updating = true;
- sun_rotation = Basis(Vector3(0, 1, 0), Math_PI * 3.0 / 4) * Basis(Vector3(1, 0, 0), -Math_PI / 4);
+ // These default rotations place the preview sun at an angular altitude
+ // of 60 degrees (must be negative) and an azimuth of 30 degrees clockwise
+ // from north (or 150 CCW from south), from north east, facing south west.
+ // On any not-tidally-locked planet, a sun would have an angular altitude
+ // of 60 degrees as the average of all points on the sphere at noon.
+ // The azimuth choice is arbitrary, but ideally shouldn't be on an axis.
+ sun_rotation = Vector2(-Math::deg2rad(60.0), Math::deg2rad(150.0));
+ sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x));
+ sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y));
sun_direction->update();
environ_sky_color->set_pick_color(Color::hex(0x91b2ceff));
environ_ground_color->set_pick_color(Color::hex(0x1f1f21ff));
@@ -6626,6 +6670,9 @@ void Node3DEditor::_update_preview_environment() {
}
}
+ sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x));
+ sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y));
+
bool disable_env = world_env_count > 0 || environ_button->is_pressed();
environ_button->set_disabled(world_env_count > 0);
@@ -6654,17 +6701,21 @@ void Node3DEditor::_update_preview_environment() {
void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
- float x = -mm->get_relative().y * 0.02 * EDSCALE;
- float y = mm->get_relative().x * 0.02 * EDSCALE;
-
- Basis rot = Basis(Vector3(0, 1, 0), y) * Basis(Vector3(1, 0, 0), x);
-
- sun_rotation = rot * sun_rotation;
- sun_rotation.orthonormalize();
+ sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE);
+ sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE);
+ sun_rotation.x = CLAMP(sun_rotation.x, -Math_TAU / 4, Math_TAU / 4);
+ sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x));
+ sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y));
_preview_settings_changed();
}
}
+void Node3DEditor::_sun_direction_angle_set() {
+ sun_rotation.x = Math::deg2rad(-sun_angle_altitude->get_value());
+ sun_rotation.y = Math::deg2rad(180.0 - sun_angle_azimuth->get_value());
+ _preview_settings_changed();
+}
+
Node3DEditor::Node3DEditor(EditorNode *p_editor) {
gizmo.visible = true;
gizmo.scale = 1.0;
@@ -6692,6 +6743,13 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
button_binds.resize(1);
String sct;
+ // Add some margin to the left for better aesthetics.
+ // This prevents the first button's hover/pressed effect from "touching" the panel's border,
+ // which looks ugly.
+ Control *margin_left = memnew(Control);
+ hbc_menu->add_child(margin_left);
+ margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE);
+
tool_button[TOOL_MODE_SELECT] = memnew(Button);
hbc_menu->add_child(tool_button[TOOL_MODE_SELECT]);
tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true);
@@ -6748,6 +6806,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
button_binds.write[0] = MENU_LOCK_SELECTED;
tool_button[TOOL_LOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds);
tool_button[TOOL_LOCK_SELECTED]->set_tooltip(TTR("Lock the selected object in place (can't be moved)."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KEY_MASK_CMD | KEY_L));
tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button);
hbc_menu->add_child(tool_button[TOOL_UNLOCK_SELECTED]);
@@ -6755,6 +6815,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
button_binds.write[0] = MENU_UNLOCK_SELECTED;
tool_button[TOOL_UNLOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds);
tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip(TTR("Unlock the selected object (can be moved)."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_L));
tool_button[TOOL_GROUP_SELECTED] = memnew(Button);
hbc_menu->add_child(tool_button[TOOL_GROUP_SELECTED]);
@@ -6762,6 +6824,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
button_binds.write[0] = MENU_GROUP_SELECTED;
tool_button[TOOL_GROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds);
tool_button[TOOL_GROUP_SELECTED]->set_tooltip(TTR("Makes sure the object's children are not selectable."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KEY_MASK_CMD | KEY_G));
tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button);
hbc_menu->add_child(tool_button[TOOL_UNGROUP_SELECTED]);
@@ -6769,6 +6833,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
button_binds.write[0] = MENU_UNGROUP_SELECTED;
tool_button[TOOL_UNGROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds);
tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip(TTR("Restores the object's children's ability to be selected."));
+ // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
+ tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G));
hbc_menu->add_child(memnew(VSeparator));
@@ -6887,7 +6953,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid")), MENU_VIEW_GRID);
+ p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), KEY_NUMBERSIGN), MENU_VIEW_GRID);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS);
@@ -7043,8 +7109,6 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
xform_dialog->connect("confirmed", callable_mp(this, &Node3DEditor::_xform_dialog_action));
- scenario_debug = RenderingServer::SCENARIO_DEBUG_DISABLED;
-
selected = nullptr;
set_process_unhandled_key_input(true);
@@ -7087,14 +7151,43 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
sun_direction->set_default_cursor_shape(CURSOR_MOVE);
String sun_dir_shader_code = "shader_type canvas_item; uniform vec3 sun_direction; uniform vec3 sun_color; void fragment() { vec3 n; n.xy = UV * 2.0 - 1.0; n.z = sqrt(max(0.0, 1.0 - dot(n.xy, n.xy))); COLOR.rgb = dot(n,sun_direction) * sun_color; COLOR.a = 1.0 - smoothstep(0.99,1.0,length(n.xy)); }";
- sun_direction_shader.instance();
+ sun_direction_shader.instantiate();
sun_direction_shader->set_code(sun_dir_shader_code);
- sun_direction_material.instance();
+ sun_direction_material.instantiate();
sun_direction_material->set_shader(sun_direction_shader);
sun_direction_material->set_shader_param("sun_direction", Vector3(0, 0, 1));
sun_direction_material->set_shader_param("sun_color", Vector3(1, 1, 1));
sun_direction->set_material(sun_direction_material);
+ HBoxContainer *sun_angle_hbox = memnew(HBoxContainer);
+ VBoxContainer *sun_angle_altitude_vbox = memnew(VBoxContainer);
+ Label *sun_angle_altitude_label = memnew(Label);
+ sun_angle_altitude_label->set_text(TTR("Angular Altitude"));
+ sun_angle_altitude_vbox->add_child(sun_angle_altitude_label);
+ sun_angle_altitude = memnew(EditorSpinSlider);
+ sun_angle_altitude->set_max(90);
+ sun_angle_altitude->set_min(-90);
+ sun_angle_altitude->set_step(0.1);
+ sun_angle_altitude->connect("value_changed", callable_mp(this, &Node3DEditor::_sun_direction_angle_set).unbind(1));
+ sun_angle_altitude_vbox->add_child(sun_angle_altitude);
+ sun_angle_hbox->add_child(sun_angle_altitude_vbox);
+ VBoxContainer *sun_angle_azimuth_vbox = memnew(VBoxContainer);
+ sun_angle_azimuth_vbox->set_custom_minimum_size(Vector2(100, 0));
+ Label *sun_angle_azimuth_label = memnew(Label);
+ sun_angle_azimuth_label->set_text(TTR("Azimuth"));
+ sun_angle_azimuth_vbox->add_child(sun_angle_azimuth_label);
+ sun_angle_azimuth = memnew(EditorSpinSlider);
+ sun_angle_azimuth->set_max(180);
+ sun_angle_azimuth->set_min(-180);
+ sun_angle_azimuth->set_step(0.1);
+ sun_angle_azimuth->set_allow_greater(true);
+ sun_angle_azimuth->set_allow_lesser(true);
+ sun_angle_azimuth->connect("value_changed", callable_mp(this, &Node3DEditor::_sun_direction_angle_set).unbind(1));
+ sun_angle_azimuth_vbox->add_child(sun_angle_azimuth);
+ sun_angle_hbox->add_child(sun_angle_azimuth_vbox);
+ sun_angle_hbox->add_theme_constant_override("separation", 10);
+ sun_vb->add_child(sun_angle_hbox);
+
sun_color = memnew(ColorPickerButton);
sun_color->set_edit_alpha(false);
sun_vb->add_margin_child(TTR("Sun Color"), sun_color);
@@ -7113,7 +7206,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
sun_add_to_scene = memnew(Button);
sun_add_to_scene->set_text(TTR("Add Sun to Scene"));
- sun_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_sun_to_scene));
+ sun_add_to_scene->set_tooltip(TTR("Adds a DirectionalLight3D node matching the preview sun settings to the current scene.\nHold Shift while clicking to also add the preview environment to the current scene."));
+ sun_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_sun_to_scene), varray(false));
sun_vb->add_spacer();
sun_vb->add_child(sun_add_to_scene);
@@ -7177,7 +7271,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
environ_add_to_scene = memnew(Button);
environ_add_to_scene->set_text(TTR("Add Environment to Scene"));
- environ_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_environment_to_scene));
+ environ_add_to_scene->set_tooltip(TTR("Adds a WorldEnvironment node matching the preview environment settings to the current scene.\nHold Shift while clicking to also add the preview sun to the current scene."));
+ environ_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_environment_to_scene), varray(false));
environ_vb->add_spacer();
environ_vb->add_child(environ_add_to_scene);
@@ -7191,11 +7286,11 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
preview_sun->set_shadow(true);
preview_sun->set_shadow_mode(DirectionalLight3D::SHADOW_PARALLEL_4_SPLITS);
preview_environment = memnew(WorldEnvironment);
- environment.instance();
+ environment.instantiate();
preview_environment->set_environment(environment);
Ref<Sky> sky;
- sky.instance();
- sky_material.instance();
+ sky.instantiate();
+ sky_material.instantiate();
sky->set_material(sky_material);
environment->set_sky(sky);
environment->set_background(Environment::BG_SKY);
@@ -7236,10 +7331,6 @@ void Node3DEditorPlugin::set_state(const Dictionary &p_state) {
spatial_editor->set_state(p_state);
}
-void Node3DEditor::snap_cursor_to_plane(const Plane &p_plane) {
- //cursor.pos=p_plane.project(cursor.pos);
-}
-
Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {
if (is_snap_enabled()) {
p_target.x = Math::snap_scalar(0.0, get_translate_snap(), p_target.x);
@@ -7282,14 +7373,6 @@ float Node3DEditor::get_scale_snap() const {
return snap_value;
}
-void Node3DEditorPlugin::_bind_methods() {
- ClassDB::bind_method("snap_cursor_to_plane", &Node3DEditorPlugin::snap_cursor_to_plane);
-}
-
-void Node3DEditorPlugin::snap_cursor_to_plane(const Plane &p_plane) {
- spatial_editor->snap_cursor_to_plane(p_plane);
-}
-
struct _GizmoPluginPriorityComparator {
bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {
if (p_a->get_priority() == p_b->get_priority()) {
@@ -7338,17 +7421,17 @@ Node3DEditorPlugin::~Node3DEditorPlugin() {
}
void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) {
- Color instanced_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instanced", Color(0.7, 0.7, 0.7, 0.6));
+ Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
Vector<Ref<StandardMaterial3D>> mats;
for (int i = 0; i < 4; i++) {
bool selected = i % 2 == 1;
- bool instanced = i < 2;
+ bool instantiated = i < 2;
Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
- Color color = instanced ? instanced_color : p_color;
+ Color color = instantiated ? instantiated_color : p_color;
if (!selected) {
color.a *= 0.3;
@@ -7380,17 +7463,17 @@ void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color
}
void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) {
- Color instanced_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instanced", Color(0.7, 0.7, 0.7, 0.6));
+ Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
Vector<Ref<StandardMaterial3D>> icons;
for (int i = 0; i < 4; i++) {
bool selected = i % 2 == 1;
- bool instanced = i < 2;
+ bool instantiated = i < 2;
Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
- Color color = instanced ? instanced_color : p_albedo;
+ Color color = instantiated ? instantiated_color : p_albedo;
if (!selected) {
color.a *= 0.85;
@@ -7468,15 +7551,15 @@ Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_na
}
String EditorNode3DGizmoPlugin::get_gizmo_name() const {
- if (get_script_instance() && get_script_instance()->has_method("get_gizmo_name")) {
- return get_script_instance()->call("get_gizmo_name");
+ if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) {
+ return get_script_instance()->call("_get_gizmo_name");
}
return TTR("Nameless gizmo");
}
int EditorNode3DGizmoPlugin::get_priority() const {
- if (get_script_instance() && get_script_instance()->has_method("get_priority")) {
- return get_script_instance()->call("get_priority");
+ if (get_script_instance() && get_script_instance()->has_method("_get_priority")) {
+ return get_script_instance()->call("_get_priority");
}
return 0;
}
@@ -7503,8 +7586,8 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) {
void EditorNode3DGizmoPlugin::_bind_methods() {
#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo")
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
- BIND_VMETHOD(MethodInfo(GIZMO_REF, "create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
+ BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1)));
@@ -7513,97 +7596,97 @@ void EditorNode3DGizmoPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref<EditorNode3DGizmo>()));
- BIND_VMETHOD(MethodInfo(Variant::STRING, "get_gizmo_name"));
- BIND_VMETHOD(MethodInfo(Variant::INT, "get_priority"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_be_hidden"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "is_selectable_when_hidden"));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name"));
+ BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority"));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden"));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden"));
- BIND_VMETHOD(MethodInfo("redraw", GIZMO_REF));
- BIND_VMETHOD(MethodInfo(Variant::STRING, "get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
+ BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
- MethodInfo hvget(Variant::NIL, "get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "index"));
+ MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "index"));
hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
BIND_VMETHOD(hvget);
- BIND_VMETHOD(MethodInfo("set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
- MethodInfo cm = MethodInfo("commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
cm.default_arguments.push_back(false);
BIND_VMETHOD(cm);
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
#undef GIZMO_REF
}
bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
- if (get_script_instance() && get_script_instance()->has_method("has_gizmo")) {
- return get_script_instance()->call("has_gizmo", p_spatial);
+ if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) {
+ return get_script_instance()->call("_has_gizmo", p_spatial);
}
return false;
}
Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
- if (get_script_instance() && get_script_instance()->has_method("create_gizmo")) {
- return get_script_instance()->call("create_gizmo", p_spatial);
+ if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) {
+ return get_script_instance()->call("_create_gizmo", p_spatial);
}
Ref<EditorNode3DGizmo> ref;
if (has_gizmo(p_spatial)) {
- ref.instance();
+ ref.instantiate();
}
return ref;
}
bool EditorNode3DGizmoPlugin::can_be_hidden() const {
- if (get_script_instance() && get_script_instance()->has_method("can_be_hidden")) {
- return get_script_instance()->call("can_be_hidden");
+ if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) {
+ return get_script_instance()->call("_can_be_hidden");
}
return true;
}
bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const {
- if (get_script_instance() && get_script_instance()->has_method("is_selectable_when_hidden")) {
- return get_script_instance()->call("is_selectable_when_hidden");
+ if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) {
+ return get_script_instance()->call("_is_selectable_when_hidden");
}
return false;
}
void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
- if (get_script_instance() && get_script_instance()->has_method("redraw")) {
+ if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
Ref<EditorNode3DGizmo> ref(p_gizmo);
- get_script_instance()->call("redraw", ref);
+ get_script_instance()->call("_redraw", ref);
}
}
String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("get_handle_name")) {
- return get_script_instance()->call("get_handle_name", p_gizmo, p_idx);
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
+ return get_script_instance()->call("_get_handle_name", p_gizmo, p_idx);
}
return "";
}
Variant EditorNode3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("get_handle_value")) {
- return get_script_instance()->call("get_handle_value", p_gizmo, p_idx);
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
+ return get_script_instance()->call("_get_handle_value", p_gizmo, p_idx);
}
return Variant();
}
void EditorNode3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) {
- if (get_script_instance() && get_script_instance()->has_method("set_handle")) {
- get_script_instance()->call("set_handle", p_gizmo, p_idx, p_camera, p_point);
+ if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
+ get_script_instance()->call("_set_handle", p_gizmo, p_idx, p_camera, p_point);
}
}
void EditorNode3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) {
- if (get_script_instance() && get_script_instance()->has_method("commit_handle")) {
- get_script_instance()->call("commit_handle", p_gizmo, p_idx, p_restore, p_cancel);
+ if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
+ get_script_instance()->call("_commit_handle", p_gizmo, p_idx, p_restore, p_cancel);
}
}
bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("is_handle_highlighted")) {
- return get_script_instance()->call("is_handle_highlighted", p_gizmo, p_idx);
+ if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
+ return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_idx);
}
return false;
}
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 33f4c32471..a195a0eee0 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -34,7 +34,6 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "editor/editor_scale.h"
-#include "scene/3d/immediate_geometry_3d.h"
#include "scene/3d/light_3d.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/3d/world_environment.h"
@@ -52,7 +51,7 @@ class EditorNode3DGizmo : public Node3DGizmo {
GDCLASS(EditorNode3DGizmo, Node3DGizmo);
bool selected;
- bool instanced;
+ bool instantiated;
public:
void set_selected(bool p_selected) { selected = p_selected; }
@@ -206,9 +205,9 @@ class Node3DEditorViewport : public Control {
VIEW_DISPLAY_NORMAL_BUFFER,
VIEW_DISPLAY_DEBUG_SHADOW_ATLAS,
VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS,
- VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO,
- VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING,
- VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION,
+ VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO,
+ VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING,
+ VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION,
VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE,
VIEW_DISPLAY_DEBUG_SSAO,
VIEW_DISPLAY_DEBUG_PSSM_SPLITS,
@@ -322,7 +321,7 @@ private:
Vector3 _get_ray_pos(const Vector2 &p_pos) const;
Vector3 _get_ray(const Vector2 &p_pos) const;
Point2 _point_to_screen(const Vector3 &p_point);
- Transform _get_camera_transform() const;
+ Transform3D _get_camera_transform() const;
int get_selected_count() const;
Vector3 _get_camera_position() const;
@@ -380,13 +379,14 @@ private:
struct EditData {
TransformMode mode;
TransformPlane plane;
- Transform original;
+ Transform3D original;
Vector3 click_ray;
Vector3 click_ray_pos;
Vector3 center;
Vector3 orig_gizmo_pos;
int edited_gizmo = 0;
Point2 mouse_pos;
+ Point2 original_mouse_pos;
bool snap = false;
Ref<EditorNode3DGizmo> gizmo;
int gizmo_handle = 0;
@@ -432,7 +432,7 @@ private:
//
void _update_camera(float p_interp_delta);
- Transform to_camera_transform(const Cursor &p_cursor) const;
+ Transform3D to_camera_transform(const Cursor &p_cursor) const;
void _draw();
void _surface_mouse_enter();
@@ -505,9 +505,9 @@ class Node3DEditorSelectedItem : public Object {
public:
AABB aabb;
- Transform original; // original location when moving
- Transform original_local;
- Transform last_xform; // last transform
+ Transform3D original; // original location when moving
+ Transform3D original_local;
+ Transform3D last_xform; // last transform
bool last_xform_dirty;
Node3D *sp;
RID sbox_instance;
@@ -600,8 +600,6 @@ private:
ToolMode tool_mode;
- RenderingServer::ScenarioDebugMode scenario_debug;
-
RID origin;
RID origin_instance;
bool origin_enabled;
@@ -641,7 +639,7 @@ private:
struct Gizmo {
bool visible = false;
float scale = 0;
- Transform transform;
+ Transform3D transform;
} gizmo;
enum MenuOption {
@@ -763,6 +761,8 @@ private:
VBoxContainer *sun_vb;
Popup *sun_environ_popup;
Control *sun_direction;
+ EditorSpinSlider *sun_angle_altitude;
+ EditorSpinSlider *sun_angle_azimuth;
ColorPickerButton *sun_color;
EditorSpinSlider *sun_energy;
EditorSpinSlider *sun_max_distance;
@@ -770,8 +770,9 @@ private:
void _sun_direction_draw();
void _sun_direction_input(const Ref<InputEvent> &p_event);
+ void _sun_direction_angle_set();
- Basis sun_rotation;
+ Vector2 sun_rotation;
Ref<Shader> sun_direction_shader;
Ref<ShaderMaterial> sun_direction_material;
@@ -804,8 +805,8 @@ private:
void _preview_settings_changed();
void _sun_environ_settings_pressed();
- void _add_sun_to_scene();
- void _add_environment_to_scene();
+ void _add_sun_to_scene(bool p_already_added_environment = false);
+ void _add_environment_to_scene(bool p_already_added_sun = false);
protected:
void _notification(int p_what);
@@ -816,7 +817,6 @@ protected:
public:
static Node3DEditor *get_singleton() { return singleton; }
- void snap_cursor_to_plane(const Plane &p_plane);
Vector3 snap_point(Vector3 p_target, Vector3 p_start = Vector3(0, 0, 0)) const;
@@ -824,7 +824,7 @@ public:
float get_zfar() const { return settings_zfar->get_value(); }
float get_fov() const { return settings_fov->get_value(); }
- Transform get_gizmo_transform() const { return gizmo.transform; }
+ Transform3D get_gizmo_transform() const { return gizmo.transform; }
bool is_gizmo_visible() const { return gizmo.visible; }
ToolMode get_tool_mode() const { return tool_mode; }
@@ -889,12 +889,7 @@ class Node3DEditorPlugin : public EditorPlugin {
Node3DEditor *spatial_editor;
EditorNode *editor;
-protected:
- static void _bind_methods();
-
public:
- void snap_cursor_to_plane(const Plane &p_plane);
-
Node3DEditor *get_spatial_editor() { return spatial_editor; }
virtual String get_name() const override { return "3D"; }
bool has_main_screen() const override { return true; }
diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp
index 0821f140b3..b0cafd83be 100644
--- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp
@@ -56,7 +56,7 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) {
} break;
case OccluderInstance3D::BAKE_ERROR_NO_MESHES: {
- EditorNode::get_singleton()->show_warning(TTR("No meshes to bake."));
+ EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.\nMake sure there is at least one MeshInstance3D node in the scene whose visual layers are part of the OccluderInstance3D's Bake Mask property."));
break;
}
default: {
diff --git a/editor/plugins/ot_features_plugin.cpp b/editor/plugins/ot_features_plugin.cpp
index ebfdf2c7cd..2ac90762e3 100644
--- a/editor/plugins/ot_features_plugin.cpp
+++ b/editor/plugins/ot_features_plugin.cpp
@@ -191,7 +191,7 @@ void EditorInspectorPluginOpenTypeFeatures::parse_begin(Object *p_object) {
void EditorInspectorPluginOpenTypeFeatures::parse_category(Object *p_object, const String &p_parse_category) {
}
-bool EditorInspectorPluginOpenTypeFeatures::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) {
+bool EditorInspectorPluginOpenTypeFeatures::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 == "opentype_features/_new") {
OpenTypeFeaturesAdd *editor = memnew(OpenTypeFeaturesAdd);
add_property_editor(p_path, editor);
@@ -208,6 +208,6 @@ bool EditorInspectorPluginOpenTypeFeatures::parse_property(Object *p_object, Var
OpenTypeFeaturesEditorPlugin::OpenTypeFeaturesEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginOpenTypeFeatures> ftr_plugin;
- ftr_plugin.instance();
+ ftr_plugin.instantiate();
EditorInspector::add_inspector_plugin(ftr_plugin);
}
diff --git a/editor/plugins/ot_features_plugin.h b/editor/plugins/ot_features_plugin.h
index 9559a6c0c3..dbafa3bbf6 100644
--- a/editor/plugins/ot_features_plugin.h
+++ b/editor/plugins/ot_features_plugin.h
@@ -88,7 +88,7 @@ public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
virtual void parse_category(Object *p_object, const String &p_parse_category) override;
- virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) override;
+ virtual bool 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 = false) override;
};
/*************************************************************************/
diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp
index 84b4516452..838e67b5e7 100644
--- a/editor/plugins/path_2d_editor_plugin.cpp
+++ b/editor/plugins/path_2d_editor_plugin.cpp
@@ -31,7 +31,7 @@
#include "path_2d_editor_plugin.h"
#include "canvas_item_editor_plugin.h"
-#include "core/os/file_access.h"
+#include "core/io/file_access.h"
#include "core/os/keyboard.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
@@ -89,7 +89,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
// Check for point movement start (for point + in/out controls).
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (mode == MODE_EDIT && !mb->get_shift() && dist_to_p < grab_threshold) {
+ if (mode == MODE_EDIT && !mb->is_shift_pressed() && dist_to_p < grab_threshold) {
// Points can only be moved in edit mode.
action = ACTION_MOVING_POINT;
@@ -149,7 +149,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
}
// Check for point creation.
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && ((mb->get_command() && mode == MODE_EDIT) || mode == MODE_CREATE)) {
+ if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && ((mb->is_command_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) {
Ref<Curve2D> curve = node->get_curve();
undo_redo->create_action(TTR("Add Point to Curve"));
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 47bd1114d2..82b51f8a06 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -94,8 +94,8 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin
return;
}
- Transform gt = path->get_global_transform();
- Transform gi = gt.affine_inverse();
+ Transform3D gt = path->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
@@ -302,8 +302,8 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
if (c.is_null()) {
return false;
}
- Transform gt = path->get_global_transform();
- Transform it = gt.affine_inverse();
+ Transform3D gt = path->get_global_transform();
+ Transform3D it = gt.affine_inverse();
static const int click_dist = 10; //should make global
@@ -316,7 +316,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
set_handle_clicked(false);
}
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->get_control()))) {
+ if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_ctrl_pressed()))) {
//click into curve, break it down
Vector<Vector3> v3a = c->tessellate();
int idx = 0;
@@ -554,7 +554,7 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) {
mirror_handle_length = true;
Ref<Path3DGizmoPlugin> gizmo_plugin;
- gizmo_plugin.instance();
+ gizmo_plugin.instantiate();
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
sep = memnew(VSeparator);
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index 470d897dcc..1a13a028c8 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -32,8 +32,8 @@
#include "canvas_item_editor_plugin.h"
#include "core/input/input.h"
+#include "core/io/file_access.h"
#include "core/math/geometry_2d.h"
-#include "core/os/file_access.h"
#include "core/os/keyboard.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
@@ -162,7 +162,7 @@ void Polygon2DEditor::_update_bone_list() {
}
Ref<ButtonGroup> bg;
- bg.instance();
+ bg.instantiate();
for (int i = 0; i < node->get_bone_count(); i++) {
CheckBox *cb = memnew(CheckBox);
NodePath np = node->get_bone_path(i);
@@ -613,11 +613,11 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
}
if (uv_move_current == UV_MODE_EDIT_POINT) {
- if (mb->get_shift() && mb->get_command()) {
+ if (mb->is_shift_pressed() && mb->is_command_pressed()) {
uv_move_current = UV_MODE_SCALE;
- } else if (mb->get_shift()) {
+ } else if (mb->is_shift_pressed()) {
uv_move_current = UV_MODE_MOVE;
- } else if (mb->get_command()) {
+ } else if (mb->is_command_pressed()) {
uv_move_current = UV_MODE_ROTATE;
}
}
@@ -1144,7 +1144,7 @@ void Polygon2DEditor::_uv_draw() {
if (!found_child) {
//draw normally
Transform2D bone_xform = node->get_global_transform().affine_inverse() * (skeleton->get_global_transform() * bone->get_skeleton_rest());
- Transform2D endpoint_xform = bone_xform * Transform2D(0, Vector2(bone->get_default_length(), 0));
+ Transform2D endpoint_xform = bone_xform * Transform2D(0, Vector2(bone->get_length(), 0));
Color color = current ? Color(1, 1, 1) : Color(0.5, 0.5, 0.5);
uv_edit_draw->draw_line(mtx.xform(bone_xform.get_origin()), mtx.xform(endpoint_xform.get_origin()), Color(0, 0, 0), Math::round((current ? 5 : 4) * EDSCALE));
@@ -1231,7 +1231,7 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) :
uv_edit->add_child(uv_main_vb);
HBoxContainer *uv_mode_hb = memnew(HBoxContainer);
- uv_edit_group.instance();
+ uv_edit_group.instantiate();
uv_edit_mode[0] = memnew(Button);
uv_mode_hb->add_child(uv_edit_mode[0]);
diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp
index b4b8e82124..488aa8c861 100644
--- a/editor/plugins/resource_preloader_editor_plugin.cpp
+++ b/editor/plugins/resource_preloader_editor_plugin.cpp
@@ -339,9 +339,9 @@ void ResourcePreloaderEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_library"), &ResourcePreloaderEditor::_update_library);
ClassDB::bind_method(D_METHOD("_remove_resource", "to_remove"), &ResourcePreloaderEditor::_remove_resource);
- ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &ResourcePreloaderEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &ResourcePreloaderEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw"), &ResourcePreloaderEditor::drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &ResourcePreloaderEditor::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &ResourcePreloaderEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &ResourcePreloaderEditor::drop_data_fw);
}
ResourcePreloaderEditor::ResourcePreloaderEditor() {
@@ -367,8 +367,10 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() {
tree = memnew(Tree);
tree->connect("button_pressed", callable_mp(this, &ResourcePreloaderEditor::_cell_button_pressed));
tree->set_columns(2);
- tree->set_column_min_width(0, 2);
- tree->set_column_min_width(1, 3);
+ tree->set_column_expand_ratio(0, 2);
+ tree->set_column_clip_content(0, true);
+ tree->set_column_expand_ratio(1, 3);
+ tree->set_column_clip_content(1, true);
tree->set_column_expand(0, true);
tree->set_column_expand(1, true);
tree->set_v_size_flags(SIZE_EXPAND_FILL);
diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp
index 50f4d8493f..120b0bc0bb 100644
--- a/editor/plugins/root_motion_editor_plugin.cpp
+++ b/editor/plugins/root_motion_editor_plugin.cpp
@@ -205,7 +205,6 @@ void EditorPropertyRootMotion::update_property() {
assign->set_flat(false);
return;
}
- assign->set_flat(true);
Node *base_node = nullptr;
if (base_hint != NodePath()) {
@@ -247,14 +246,12 @@ EditorPropertyRootMotion::EditorPropertyRootMotion() {
HBoxContainer *hbc = memnew(HBoxContainer);
add_child(hbc);
assign = memnew(Button);
- assign->set_flat(true);
assign->set_h_size_flags(SIZE_EXPAND_FILL);
assign->set_clip_text(true);
assign->connect("pressed", callable_mp(this, &EditorPropertyRootMotion::_node_assign));
hbc->add_child(assign);
clear = memnew(Button);
- clear->set_flat(true);
clear->connect("pressed", callable_mp(this, &EditorPropertyRootMotion::_node_clear));
hbc->add_child(clear);
@@ -281,7 +278,7 @@ void EditorInspectorRootMotionPlugin::parse_begin(Object *p_object) {
//do none
}
-bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) {
+bool EditorInspectorRootMotionPlugin::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 == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) {
EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion);
if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) {
diff --git a/editor/plugins/root_motion_editor_plugin.h b/editor/plugins/root_motion_editor_plugin.h
index c70fff7db7..1484af62e8 100644
--- a/editor/plugins/root_motion_editor_plugin.h
+++ b/editor/plugins/root_motion_editor_plugin.h
@@ -65,7 +65,7 @@ class EditorInspectorRootMotionPlugin : public EditorInspectorPlugin {
public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
- virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false) override;
+ virtual bool 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 = false) override;
virtual void parse_end() override;
};
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 58e6717a3d..498d5b0711 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -32,8 +32,8 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
+#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
-#include "core/os/file_access.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "editor/debugger/editor_debugger_node.h"
@@ -71,7 +71,7 @@ Array EditorSyntaxHighlighter::_get_supported_languages() const {
Ref<EditorSyntaxHighlighter> EditorSyntaxHighlighter::_create() const {
Ref<EditorSyntaxHighlighter> syntax_highlighter;
- syntax_highlighter.instance();
+ syntax_highlighter.instantiate();
if (get_script_instance()) {
syntax_highlighter->set_script(get_script_instance()->get_script());
}
@@ -140,10 +140,15 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
/* Reserved words. */
const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
+ const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color");
List<String> keywords;
script->get_language()->get_reserved_words(&keywords);
for (List<String>::Element *E = keywords.front(); E; E = E->next()) {
- highlighter->add_keyword_color(E->get(), keyword_color);
+ if (script->get_language()->is_control_flow_keyword(E->get())) {
+ highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ } else {
+ highlighter->add_keyword_color(E->get(), keyword_color);
+ }
}
/* Member types. */
@@ -196,7 +201,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
Ref<EditorSyntaxHighlighter> EditorStandardSyntaxHighlighter::_create() const {
Ref<EditorStandardSyntaxHighlighter> syntax_highlighter;
- syntax_highlighter.instance();
+ syntax_highlighter.instantiate();
return syntax_highlighter;
}
@@ -204,7 +209,7 @@ Ref<EditorSyntaxHighlighter> EditorStandardSyntaxHighlighter::_create() const {
Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const {
Ref<EditorPlainTextSyntaxHighlighter> syntax_highlighter;
- syntax_highlighter.instance();
+ syntax_highlighter.instantiate();
return syntax_highlighter;
}
@@ -213,6 +218,8 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const {
/*** SCRIPT EDITOR ****/
void ScriptEditorBase::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor);
+
ADD_SIGNAL(MethodInfo("name_changed"));
ADD_SIGNAL(MethodInfo("edited_script_changed"));
ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic")));
@@ -223,7 +230,7 @@ void ScriptEditorBase::_bind_methods() {
ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));
ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text")));
- BIND_VMETHOD(MethodInfo("add_syntax_highlighter", PropertyInfo(Variant::OBJECT, "highlighter")));
+ BIND_VMETHOD(MethodInfo("_add_syntax_highlighter", PropertyInfo(Variant::OBJECT, "highlighter")));
}
static bool _is_built_in_script(Script *p_script) {
@@ -332,13 +339,13 @@ void ScriptEditorQuickOpen::_update_search() {
if ((search_box->get_text() == "" || file.findn(search_box->get_text()) != -1)) {
TreeItem *ti = search_options->create_item(root);
ti->set_text(0, file);
- if (root->get_children() == ti) {
+ if (root->get_first_child() == ti) {
ti->select(0);
}
}
}
- get_ok_button()->set_disabled(root->get_children() == nullptr);
+ get_ok_button()->set_disabled(root->get_first_child() == nullptr);
}
void ScriptEditorQuickOpen::_confirmed() {
@@ -698,7 +705,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
// Do not try to save internal scripts, but prompt to save in-memory
// scripts which are not saved to disk yet (have empty path).
if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) {
- _menu_option(FILE_SAVE);
+ save_current_script();
}
}
if (script.is_valid()) {
@@ -753,6 +760,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
_update_members_overview_visibility();
_update_help_overview_visibility();
_save_layout();
+ _update_find_replace_bar();
}
void ScriptEditor::_close_current_tab(bool p_save) {
@@ -822,6 +830,7 @@ void ScriptEditor::_close_all_tabs() {
_close_current_tab(false);
}
+ _update_find_replace_bar();
}
void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) {
@@ -1224,55 +1233,7 @@ void ScriptEditor::_menu_option(int p_option) {
if (current) {
switch (p_option) {
case FILE_SAVE: {
- if (_test_script_times_on_disk()) {
- return;
- }
-
- if (trim_trailing_whitespace_on_save) {
- current->trim_trailing_whitespace();
- }
-
- current->insert_final_newline();
-
- if (convert_indent_on_save) {
- if (use_space_indentation) {
- current->convert_indent_to_spaces();
- } else {
- current->convert_indent_to_tabs();
- }
- }
-
- RES resource = current->get_edited_resource();
- Ref<TextFile> text_file = resource;
- Ref<Script> script = resource;
-
- if (text_file != nullptr) {
- current->apply_code();
- _save_text_file(text_file, text_file->get_path());
- break;
- }
-
- if (script != nullptr) {
- const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
- EditorHelp::get_doc_data()->remove_doc(doc.name);
- }
- }
- }
-
- editor->save_resource(resource);
-
- if (script != nullptr) {
- const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- EditorHelp::get_doc_data()->add_doc(doc);
- update_doc(doc.name);
- }
- }
-
+ save_current_script();
} break;
case FILE_SAVE_AS: {
if (trim_trailing_whitespace_on_save) {
@@ -1544,6 +1505,11 @@ void ScriptEditor::_notification(int p_what) {
filename->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("normal", "LineEdit"));
recent_scripts->set_as_minsize();
+
+ if (is_inside_tree()) {
+ _update_script_colors();
+ _update_script_names();
+ }
} break;
case NOTIFICATION_READY: {
@@ -1665,7 +1631,7 @@ void ScriptEditor::_help_overview_selected(int p_idx) {
}
void ScriptEditor::_script_selected(int p_idx) {
- grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(1); //amazing hack, simply amazing
+ grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT); //amazing hack, simply amazing
_go_to_tab(script_list->get_item_metadata(p_idx));
grab_focus_block = false;
@@ -1682,6 +1648,7 @@ void ScriptEditor::ensure_select_current() {
}
}
}
+ _update_find_replace_bar();
_update_selected_editor_menu();
}
@@ -1718,7 +1685,7 @@ struct _ScriptEditorItemData {
if (sort_key == id.sort_key) {
return index < id.index;
} else {
- return sort_key < id.sort_key;
+ return sort_key.naturalnocasecmp_to(id.sort_key) < 0;
}
} else {
return category < id.category;
@@ -1827,7 +1794,6 @@ void ScriptEditor::_update_help_overview() {
void ScriptEditor::_update_script_colors() {
bool script_temperature_enabled = EditorSettings::get_singleton()->get("text_editor/script_list/script_temperature_enabled");
- bool highlight_current = EditorSettings::get_singleton()->get("text_editor/script_list/highlight_current_script");
int hist_size = EditorSettings::get_singleton()->get("text_editor/script_list/script_temperature_history_size");
Color hot_color = get_theme_color("accent_color", "Editor");
@@ -1842,11 +1808,7 @@ void ScriptEditor::_update_script_colors() {
script_list->set_item_custom_bg_color(i, Color(0, 0, 0, 0));
- bool current = tab_container->get_current_tab() == c;
- if (current && highlight_current) {
- script_list->set_item_custom_bg_color(i, EditorSettings::get_singleton()->get("text_editor/script_list/current_script_background_color"));
-
- } else if (script_temperature_enabled) {
+ if (script_temperature_enabled) {
if (!n->has_meta("__editor_pass")) {
continue;
}
@@ -2325,6 +2287,58 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
return true;
}
+void ScriptEditor::save_current_script() {
+ ScriptEditorBase *current = _get_current_editor();
+ if (!current || _test_script_times_on_disk()) {
+ return;
+ }
+
+ if (trim_trailing_whitespace_on_save) {
+ current->trim_trailing_whitespace();
+ }
+
+ current->insert_final_newline();
+
+ if (convert_indent_on_save) {
+ if (use_space_indentation) {
+ current->convert_indent_to_spaces();
+ } else {
+ current->convert_indent_to_tabs();
+ }
+ }
+
+ RES resource = current->get_edited_resource();
+ Ref<TextFile> text_file = resource;
+ Ref<Script> script = resource;
+
+ if (text_file != nullptr) {
+ current->apply_code();
+ _save_text_file(text_file, text_file->get_path());
+ return;
+ }
+
+ if (script != nullptr) {
+ const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
+ for (int j = 0; j < documentations.size(); j++) {
+ const DocData::ClassDoc &doc = documentations.get(j);
+ if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
+ EditorHelp::get_doc_data()->remove_doc(doc.name);
+ }
+ }
+ }
+
+ editor->save_resource(resource);
+
+ if (script != nullptr) {
+ const Vector<DocData::ClassDoc> &documentations = script->get_documentation();
+ for (int j = 0; j < documentations.size(); j++) {
+ const DocData::ClassDoc &doc = documentations.get(j);
+ EditorHelp::get_doc_data()->add_doc(doc);
+ update_doc(doc.name);
+ }
+ }
+}
+
void ScriptEditor::save_all_scripts() {
for (int i = 0; i < tab_container->get_child_count(); i++) {
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
@@ -2439,6 +2453,11 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const
script_list->select(script_list->find_metadata(i));
+ // Save the current script so the changes can be picked up by an external editor.
+ if (!_is_built_in_script(script.ptr())) { // But only if it's not built-in script.
+ save_current_script();
+ }
+
break;
}
}
@@ -2501,6 +2520,16 @@ void ScriptEditor::_file_removed(const String &p_removed_file) {
}
}
+void ScriptEditor::_update_find_replace_bar() {
+ ScriptEditorBase *se = _get_current_editor();
+ if (se) {
+ se->set_find_replace_bar(find_replace_bar);
+ } else {
+ find_replace_bar->set_text_edit(nullptr);
+ find_replace_bar->hide();
+ }
+}
+
void ScriptEditor::_autosave_scripts() {
save_all_scripts();
}
@@ -2752,6 +2781,8 @@ void ScriptEditor::_script_list_gui_input(const Ref<InputEvent> &ev) {
case MOUSE_BUTTON_RIGHT: {
_make_script_list_context_menu();
} break;
+ default:
+ break;
}
}
}
@@ -3174,7 +3205,7 @@ void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_numb
if (ResourceLoader::exists(fpath)) {
RES res = ResourceLoader::load(fpath);
- if (fpath.get_extension() == "shader") {
+ if (fpath.get_extension() == "gdshader") {
ShaderEditorPlugin *shader_editor = Object::cast_to<ShaderEditorPlugin>(EditorNode::get_singleton()->get_editor_data().get_editor("Shader"));
shader_editor->edit(res.ptr());
shader_editor->make_visible(true);
@@ -3257,9 +3288,9 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter);
ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter);
- ClassDB::bind_method(D_METHOD("get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw", "point", "data", "from"), &ScriptEditor::drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw", "point", "data", "from"), &ScriptEditor::drop_data_fw);
ClassDB::bind_method(D_METHOD("goto_line", "line_number"), &ScriptEditor::_goto_script_line2);
ClassDB::bind_method(D_METHOD("get_current_script"), &ScriptEditor::_get_current_script);
@@ -3363,11 +3394,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
help_overview->set_custom_minimum_size(Size2(0, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing
help_overview->set_v_size_flags(SIZE_EXPAND_FILL);
+ VBoxContainer *code_editor_container = memnew(VBoxContainer);
+ script_split->add_child(code_editor_container);
+
tab_container = memnew(TabContainer);
tab_container->set_tabs_visible(false);
tab_container->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
- script_split->add_child(tab_container);
+ code_editor_container->add_child(tab_container);
tab_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ tab_container->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ find_replace_bar = memnew(FindReplaceBar);
+ code_editor_container->add_child(find_replace_bar);
+ find_replace_bar->hide();
ED_SHORTCUT("script_editor/window_sort", TTR("Sort"));
ED_SHORTCUT("script_editor/window_move_up", TTR("Move Up"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_UP);
@@ -3672,9 +3711,7 @@ ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) {
EDITOR_DEF("text_editor/external/use_external_editor", false);
EDITOR_DEF("text_editor/external/exec_path", "");
EDITOR_DEF("text_editor/script_list/script_temperature_enabled", true);
- EDITOR_DEF("text_editor/script_list/highlight_current_script", true);
EDITOR_DEF("text_editor/script_list/script_temperature_history_size", 15);
- EDITOR_DEF("text_editor/script_list/current_script_background_color", Color(1, 1, 1, 0.3));
EDITOR_DEF("text_editor/script_list/group_help_pages", true);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "text_editor/script_list/sort_scripts_by", PROPERTY_HINT_ENUM, "Name,Path,None"));
EDITOR_DEF("text_editor/script_list/sort_scripts_by", 0);
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index b2172e7f10..72a649ffbf 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -80,7 +80,7 @@ public:
virtual Ref<EditorSyntaxHighlighter> _create() const override;
- EditorStandardSyntaxHighlighter() { highlighter.instance(); }
+ EditorStandardSyntaxHighlighter() { highlighter.instantiate(); }
};
class EditorPlainTextSyntaxHighlighter : public EditorSyntaxHighlighter {
@@ -162,6 +162,9 @@ public:
virtual void set_tooltip_request_func(String p_method, Object *p_obj) = 0;
virtual Control *get_edit_menu() = 0;
virtual void clear_edit_menu() = 0;
+ virtual void set_find_replace_bar(FindReplaceBar *p_bar) = 0;
+
+ virtual Control *get_base_editor() const = 0;
virtual void validate() = 0;
@@ -268,6 +271,7 @@ class ScriptEditor : public PanelContainer {
ConfirmationDialog *erase_tab_confirm;
ScriptCreateDialog *script_create_dialog;
Button *scripts_visible;
+ FindReplaceBar *find_replace_bar;
String current_theme;
@@ -324,6 +328,7 @@ class ScriptEditor : public PanelContainer {
void _show_error_dialog(String p_path);
void _close_tab(int p_idx, bool p_save = true, bool p_history_back = true);
+ void _update_find_replace_bar();
void _close_current_tab(bool p_save = true);
void _close_discard_current_tab(const String &p_str);
@@ -463,6 +468,7 @@ public:
void get_breakpoints(List<String> *p_breakpoints);
+ void save_current_script();
void save_all_scripts();
void set_window_layout(Ref<ConfigFile> p_layout);
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index c982207224..cc0fbcc634 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -109,13 +109,11 @@ ConnectionInfoDialog::ConnectionInfoDialog() {
////////////////////////////////////////////////////////////////////////////////
Vector<String> ScriptTextEditor::get_functions() {
- String errortxt;
- int line = -1, col;
CodeEdit *te = code_editor->get_text_editor();
String text = te->get_text();
List<String> fnc;
- if (script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc)) {
+ if (script->get_language()->validate(text, script->get_path(), &fnc)) {
//if valid rewrite functions to latest
functions.clear();
for (List<String>::Element *E = fnc.front(); E; E = E->next()) {
@@ -168,68 +166,26 @@ void ScriptTextEditor::enable_editor() {
void ScriptTextEditor::_load_theme_settings() {
CodeEdit *text_edit = code_editor->get_text_editor();
- text_edit->clear_keywords();
+ Color updated_marked_line_color = EDITOR_GET("text_editor/highlighting/mark_color");
Color updated_safe_line_number_color = EDITOR_GET("text_editor/highlighting/safe_line_number_color");
- if (updated_safe_line_number_color != safe_line_number_color) {
+
+ bool safe_line_number_color_updated = updated_safe_line_number_color != safe_line_number_color;
+ bool marked_line_color_updated = updated_marked_line_color != marked_line_color;
+ if (safe_line_number_color_updated || marked_line_color_updated) {
safe_line_number_color = updated_safe_line_number_color;
for (int i = 0; i < text_edit->get_line_count(); i++) {
- if (text_edit->get_line_gutter_item_color(i, line_number_gutter) != default_line_number_color) {
+ if (marked_line_color_updated && text_edit->get_line_background_color(i) == marked_line_color) {
+ text_edit->set_line_background_color(i, updated_marked_line_color);
+ }
+
+ if (safe_line_number_color_updated && text_edit->get_line_gutter_item_color(i, line_number_gutter) != default_line_number_color) {
text_edit->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);
}
}
+ marked_line_color = updated_marked_line_color;
}
- Color background_color = EDITOR_GET("text_editor/highlighting/background_color");
- Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color");
- Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color");
- Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color");
- Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color");
- Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color");
- Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
- Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color");
- Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color");
- Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color");
- Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color");
- Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color");
- Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color");
- Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color");
- Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color");
- Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color");
- Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color");
- Color bookmark_color = EDITOR_GET("text_editor/highlighting/bookmark_color");
- Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color");
- Color executing_line_color = EDITOR_GET("text_editor/highlighting/executing_line_color");
- Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color");
- Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color");
- Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color");
-
- text_edit->add_theme_color_override("background_color", background_color);
- text_edit->add_theme_color_override("completion_background_color", completion_background_color);
- text_edit->add_theme_color_override("completion_selected_color", completion_selected_color);
- text_edit->add_theme_color_override("completion_existing_color", completion_existing_color);
- text_edit->add_theme_color_override("completion_scroll_color", completion_scroll_color);
- text_edit->add_theme_color_override("completion_font_color", completion_font_color);
- text_edit->add_theme_color_override("font_color", text_color);
- text_edit->add_theme_color_override("line_number_color", line_number_color);
- text_edit->add_theme_color_override("caret_color", caret_color);
- text_edit->add_theme_color_override("caret_background_color", caret_background_color);
- text_edit->add_theme_color_override("font_selected_color", text_selected_color);
- text_edit->add_theme_color_override("selection_color", selection_color);
- text_edit->add_theme_color_override("brace_mismatch_color", brace_mismatch_color);
- text_edit->add_theme_color_override("current_line_color", current_line_color);
- text_edit->add_theme_color_override("line_length_guideline_color", line_length_guideline_color);
- text_edit->add_theme_color_override("word_highlighted_color", word_highlighted_color);
- text_edit->add_theme_color_override("bookmark_color", bookmark_color);
- text_edit->add_theme_color_override("breakpoint_color", breakpoint_color);
- text_edit->add_theme_color_override("executing_line_color", executing_line_color);
- text_edit->add_theme_color_override("mark_color", mark_color);
- text_edit->add_theme_color_override("code_folding_color", code_folding_color);
- text_edit->add_theme_color_override("search_result_color", search_result_color);
- text_edit->add_theme_color_override("search_result_border_color", search_result_border_color);
-
- text_edit->add_theme_constant_override("line_spacing", EDITOR_DEF("text_editor/theme/line_spacing", 6));
-
theme_loaded = true;
if (!script.is_null()) {
_set_theme_for_script();
@@ -244,46 +200,29 @@ void ScriptTextEditor::_set_theme_for_script() {
CodeEdit *text_edit = code_editor->get_text_editor();
text_edit->get_syntax_highlighter()->update_cache();
- /* add keywords for auto completion */
- // singleton autoloads (as types, just as engine singletons are)
- 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();
- if (info.is_singleton) {
- text_edit->add_keyword(info.name);
- }
- }
-
- // engine types
- List<StringName> types;
- ClassDB::get_class_list(&types);
- for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
- String n = E->get();
- if (n.begins_with("_")) {
- n = n.substr(1, n.length());
- }
- text_edit->add_keyword(n);
- }
-
- // user types
- List<StringName> global_classes;
- ScriptServer::get_global_class_list(&global_classes);
- for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) {
- text_edit->add_keyword(E->get());
+ List<String> strings;
+ script->get_language()->get_string_delimiters(&strings);
+ text_edit->clear_string_delimiters();
+ for (List<String>::Element *E = strings.front(); E; E = E->next()) {
+ String string = E->get();
+ String beg = string.get_slice(" ", 0);
+ String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String();
+ text_edit->add_string_delimiter(beg, end, end == "");
}
- List<String> keywords;
- script->get_language()->get_reserved_words(&keywords);
- for (List<String>::Element *E = keywords.front(); E; E = E->next()) {
- text_edit->add_keyword(E->get());
+ List<String> comments;
+ script->get_language()->get_comment_delimiters(&comments);
+ text_edit->clear_comment_delimiters();
+ for (List<String>::Element *E = comments.front(); E; E = E->next()) {
+ String comment = E->get();
+ String beg = comment.get_slice(" ", 0);
+ String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String();
+ text_edit->add_comment_delimiter(beg, end, end == "");
}
+}
- // core types
- List<String> core_types;
- script->get_language()->get_core_type_words(&core_types);
- for (List<String>::Element *E = core_types.front(); E; E = E->next()) {
- text_edit->add_keyword(E->get());
- }
+void ScriptTextEditor::_show_errors_panel(bool p_show) {
+ errors_panel->set_visible(p_show);
}
void ScriptTextEditor::_show_warnings_panel(bool p_show) {
@@ -292,7 +231,7 @@ void ScriptTextEditor::_show_warnings_panel(bool p_show) {
void ScriptTextEditor::_warning_clicked(Variant p_line) {
if (p_line.get_type() == Variant::INT) {
- code_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t());
+ goto_line_centered(p_line.operator int64_t());
} else if (p_line.get_type() == Variant::DICTIONARY) {
Dictionary meta = p_line.operator Dictionary();
code_editor->get_text_editor()->insert_at("# warning-ignore:" + meta["code"].operator String(), meta["line"].operator int64_t() - 1);
@@ -300,6 +239,12 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) {
}
}
+void ScriptTextEditor::_error_clicked(Variant p_line) {
+ if (p_line.get_type() == Variant::INT) {
+ code_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t());
+ }
+}
+
void ScriptTextEditor::reload_text() {
ERR_FAIL_COND(script.is_null());
@@ -450,23 +395,21 @@ Ref<Texture2D> ScriptTextEditor::get_theme_icon() {
}
void ScriptTextEditor::_validate_script() {
- String errortxt;
- int line = -1, col;
CodeEdit *te = code_editor->get_text_editor();
String text = te->get_text();
List<String> fnc;
Set<int> safe_lines;
List<ScriptLanguage::Warning> warnings;
+ List<ScriptLanguage::ScriptError> errors;
- if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &warnings, &safe_lines)) {
- String error_text = "error(" + itos(line) + "," + itos(col) + "): " + errortxt;
+ if (!script->get_language()->validate(text, script->get_path(), &fnc, &errors, &warnings, &safe_lines)) {
+ String error_text = TTR("Error at ") + "(" + itos(errors[0].line) + "," + itos(errors[0].column) + "): " + errors[0].message;
code_editor->set_error(error_text);
- code_editor->set_error_pos(line - 1, col - 1);
+ code_editor->set_error_pos(errors[0].line - 1, errors[0].column - 1);
script_is_valid = false;
} else {
code_editor->set_error("");
- line = -1;
if (!script->is_tool()) {
script->set_source_code(text);
script->update_exports();
@@ -508,7 +451,8 @@ void ScriptTextEditor::_validate_script() {
}
}
- code_editor->set_warning_nb(warning_nb);
+ code_editor->set_error_count(errors.size());
+ code_editor->set_warning_count(warning_nb);
// Add script warnings.
warnings_panel->push_table(3);
@@ -542,23 +486,52 @@ void ScriptTextEditor::_validate_script() {
}
warnings_panel->pop(); // Table.
- line--;
+ errors_panel->clear();
+ errors_panel->push_table(2);
+ for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) {
+ ScriptLanguage::ScriptError err = E->get();
+
+ errors_panel->push_cell();
+ errors_panel->push_meta(err.line - 1);
+ errors_panel->push_color(warnings_panel->get_theme_color("error_color", "Editor"));
+ errors_panel->add_text(TTR("Line") + " " + itos(err.line) + ":");
+ errors_panel->pop(); // Color.
+ errors_panel->pop(); // Meta goto.
+ errors_panel->pop(); // Cell.
+
+ errors_panel->push_cell();
+ errors_panel->add_text(err.message);
+ errors_panel->pop(); // Cell.
+ }
+ errors_panel->pop(); // Table
+
bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true);
bool last_is_safe = false;
for (int i = 0; i < te->get_line_count(); i++) {
- te->set_line_as_marked(i, line == i);
+ if (errors.is_empty()) {
+ te->set_line_background_color(i, Color(0, 0, 0, 0));
+ } else {
+ for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) {
+ bool error_line = i == E->get().line - 1;
+ te->set_line_background_color(i, error_line ? marked_line_color : Color(0, 0, 0, 0));
+ if (error_line) {
+ break;
+ }
+ }
+ }
+
if (highlight_safe) {
if (safe_lines.has(i + 1)) {
te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);
last_is_safe = true;
- } else if (last_is_safe && (te->is_line_comment(i) || te->get_line(i).strip_edges().is_empty())) {
+ } else if (last_is_safe && (te->is_in_comment(i) != -1 || te->get_line(i).strip_edges().is_empty())) {
te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color);
} else {
te->set_line_gutter_item_color(i, line_number_gutter, default_line_number_color);
last_is_safe = false;
}
} else {
- te->set_line_gutter_item_color(line, 1, default_line_number_color);
+ te->set_line_gutter_item_color(i, 1, default_line_number_color);
}
}
@@ -1065,7 +1038,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
return;
}
- tx->indent_selected_lines_left();
+ tx->unindent_lines();
} break;
case EDIT_INDENT_RIGHT: {
Ref<Script> scr = script;
@@ -1073,16 +1046,16 @@ void ScriptTextEditor::_edit_option(int p_op) {
return;
}
- tx->indent_selected_lines_right();
+ tx->indent_lines();
} break;
case EDIT_DELETE_LINE: {
code_editor->delete_lines();
} break;
- case EDIT_CLONE_DOWN: {
- code_editor->clone_lines_down();
+ case EDIT_DUPLICATE_SELECTION: {
+ code_editor->duplicate_selection();
} break;
case EDIT_TOGGLE_FOLD_LINE: {
- tx->toggle_fold_line(tx->cursor_get_line());
+ tx->toggle_foldable_line(tx->cursor_get_line());
tx->update();
} break;
case EDIT_FOLD_ALL_LINES: {
@@ -1097,7 +1070,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
_edit_option_toggle_inline_comment();
} break;
case EDIT_COMPLETE: {
- tx->query_code_comple();
+ tx->request_code_completion(true);
} break;
case EDIT_AUTO_INDENT: {
String text = tx->get_text();
@@ -1364,9 +1337,9 @@ void ScriptTextEditor::_notification(int p_what) {
void ScriptTextEditor::_bind_methods() {
ClassDB::bind_method("_update_connected_methods", &ScriptTextEditor::_update_connected_methods);
- ClassDB::bind_method("get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw);
- ClassDB::bind_method("can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw);
- ClassDB::bind_method("drop_data_fw", &ScriptTextEditor::drop_data_fw);
+ ClassDB::bind_method("_get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw);
+ ClassDB::bind_method("_can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw);
+ ClassDB::bind_method("_drop_data_fw", &ScriptTextEditor::drop_data_fw);
ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptTextEditor::add_syntax_highlighter);
}
@@ -1379,6 +1352,10 @@ void ScriptTextEditor::clear_edit_menu() {
memdelete(edit_hb);
}
+void ScriptTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) {
+ code_editor->set_find_replace_bar(p_bar);
+}
+
void ScriptTextEditor::reload(bool p_soft) {
CodeEdit *te = code_editor->get_text_editor();
Ref<Script> scr = script;
@@ -1402,6 +1379,10 @@ void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj)
void ScriptTextEditor::set_debugger_active(bool p_active) {
}
+Control *ScriptTextEditor::get_base_editor() const {
+ return code_editor->get_text_editor();
+}
+
Variant ScriptTextEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
return Variant();
}
@@ -1463,14 +1444,21 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
}
if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) {
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
Array files = d["files"];
String text_to_drop;
+ bool preload = Input::get_singleton()->is_key_pressed(KEY_CTRL);
for (int i = 0; i < files.size(); i++) {
if (i > 0) {
- text_to_drop += ",";
+ text_to_drop += ", ";
+ }
+
+ if (preload) {
+ text_to_drop += "preload(" + String(files[i]).c_escape().quote(quote_style) + ")";
+ } else {
+ text_to_drop += String(files[i]).c_escape().quote(quote_style);
}
- text_to_drop += "\"" + String(files[i]).c_escape() + "\"";
}
te->cursor_set_line(row);
@@ -1556,7 +1544,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
}
bool has_color = (word_at_pos == "Color");
- bool foldable = tx->can_fold(row) || tx->is_folded(row);
+ bool foldable = tx->can_fold_line(row) || tx->is_line_folded(row);
bool open_docs = false;
bool goto_definition = false;
@@ -1682,6 +1670,7 @@ void ScriptTextEditor::_enable_code_editor() {
editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
editor_box->add_child(code_editor);
+ code_editor->connect("show_errors_panel", callable_mp(this, &ScriptTextEditor::_show_errors_panel));
code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));
code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script));
code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings));
@@ -1702,6 +1691,13 @@ void ScriptTextEditor::_enable_code_editor() {
"normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts"));
warnings_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_warning_clicked));
+ editor_box->add_child(errors_panel);
+ errors_panel->add_theme_font_override(
+ "normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
+ errors_panel->add_theme_font_size_override(
+ "normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts"));
+ errors_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_error_clicked));
+
add_child(context_menu);
context_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
@@ -1764,7 +1760,7 @@ void ScriptTextEditor::_enable_code_editor() {
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES);
edit_menu->get_popup()->add_separator();
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE);
@@ -1833,11 +1829,17 @@ ScriptTextEditor::ScriptTextEditor() {
warnings_panel->set_focus_mode(FOCUS_CLICK);
warnings_panel->hide();
+ errors_panel = memnew(RichTextLabel);
+ errors_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
+ errors_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ errors_panel->set_meta_underline(true);
+ errors_panel->set_selection_enabled(true);
+ errors_panel->set_focus_mode(FOCUS_CLICK);
+ errors_panel->hide();
+
update_settings();
- code_editor->get_text_editor()->set_callhint_settings(
- EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
- EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset"));
+ code_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"));
code_editor->get_text_editor()->set_select_identifiers_on_hover(true);
code_editor->get_text_editor()->set_context_menu_enabled(false);
@@ -1860,11 +1862,11 @@ ScriptTextEditor::ScriptTextEditor() {
highlighter_menu->set_name("highlighter_menu");
Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter;
- plain_highlighter.instance();
+ plain_highlighter.instantiate();
add_syntax_highlighter(plain_highlighter);
Ref<EditorStandardSyntaxHighlighter> highlighter;
- highlighter.instance();
+ highlighter.instantiate();
add_syntax_highlighter(highlighter);
set_syntax_highlighter(highlighter);
@@ -1895,6 +1897,7 @@ ScriptTextEditor::~ScriptTextEditor() {
if (!editor_enabled) {
memdelete(code_editor);
memdelete(warnings_panel);
+ memdelete(errors_panel);
memdelete(context_menu);
memdelete(color_panel);
memdelete(edit_hb);
@@ -1931,9 +1934,9 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), 0);
ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), 0);
#ifdef OSX_ENABLED
- ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_C);
+ ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_C);
#else
- ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD | KEY_D);
+ ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_D);
#endif
ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_E);
ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CMD | KEY_MASK_ALT | KEY_T);
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 17abfcf4cc..e4a13951e4 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -55,6 +55,7 @@ class ScriptTextEditor : public ScriptEditorBase {
CodeTextEditor *code_editor = nullptr;
RichTextLabel *warnings_panel = nullptr;
+ RichTextLabel *errors_panel = nullptr;
Ref<Script> script;
bool script_is_valid = false;
@@ -89,6 +90,8 @@ class ScriptTextEditor : public ScriptEditorBase {
Color default_line_number_color = Color(1, 1, 1);
Color safe_line_number_color = Color(1, 1, 1);
+ Color marked_line_color = Color(1, 1, 1);
+
PopupPanel *color_panel = nullptr;
ColorPicker *color_picker = nullptr;
Vector2 color_position;
@@ -114,7 +117,7 @@ class ScriptTextEditor : public ScriptEditorBase {
EDIT_INDENT_RIGHT,
EDIT_INDENT_LEFT,
EDIT_DELETE_LINE,
- EDIT_CLONE_DOWN,
+ EDIT_DUPLICATE_SELECTION,
EDIT_PICK_COLOR,
EDIT_TO_UPPERCASE,
EDIT_TO_LOWERCASE,
@@ -159,7 +162,9 @@ protected:
void _load_theme_settings();
void _set_theme_for_script();
+ void _show_errors_panel(bool p_show);
void _show_warnings_panel(bool p_show);
+ void _error_clicked(Variant p_line);
void _warning_clicked(Variant p_line);
void _notification(int p_what);
@@ -231,8 +236,12 @@ public:
Control *get_edit_menu() override;
virtual void clear_edit_menu() override;
+ virtual void set_find_replace_bar(FindReplaceBar *p_bar) override;
+
static void register_editor();
+ virtual Control *get_base_editor() const override;
+
virtual void validate() override;
ScriptTextEditor();
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index ed3b746678..c1216a9732 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -37,12 +37,18 @@
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/project_settings_editor.h"
#include "editor/property_editor.h"
#include "servers/display_server.h"
#include "servers/rendering/shader_types.h"
/*** SHADER SCRIPT EDITOR ****/
+static bool saved_warnings_enabled = false;
+static bool saved_treat_warning_as_errors = false;
+static Map<ShaderWarning::Code, bool> saved_warnings;
+static uint32_t saved_warning_flags = 0U;
+
Ref<Shader> ShaderTextEditor::get_edited_shader() const {
return shader;
}
@@ -57,6 +63,8 @@ void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) {
get_text_editor()->set_text(p_shader->get_code());
get_text_editor()->clear_undo_history();
+ get_text_editor()->call_deferred("set_h_scroll", 0);
+ get_text_editor()->call_deferred("set_v_scroll", 0);
_validate_script();
_line_col_changed();
@@ -82,54 +90,21 @@ void ShaderTextEditor::reload_text() {
update_line_and_column();
}
+void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) {
+ warnings_panel = p_warnings_panel;
+}
+
void ShaderTextEditor::_load_theme_settings() {
- Color background_color = EDITOR_GET("text_editor/highlighting/background_color");
- Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color");
- Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color");
- Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color");
- Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color");
- Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color");
- Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
- Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color");
- Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color");
- Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color");
- Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color");
- Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color");
- Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color");
- Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color");
- Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color");
- Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color");
- Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color");
- Color bookmark_color = EDITOR_GET("text_editor/highlighting/bookmark_color");
- Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color");
- Color executing_line_color = EDITOR_GET("text_editor/highlighting/executing_line_color");
- Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color");
- Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color");
- Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color");
-
- get_text_editor()->add_theme_color_override("background_color", background_color);
- get_text_editor()->add_theme_color_override("completion_background_color", completion_background_color);
- get_text_editor()->add_theme_color_override("completion_selected_color", completion_selected_color);
- get_text_editor()->add_theme_color_override("completion_existing_color", completion_existing_color);
- get_text_editor()->add_theme_color_override("completion_scroll_color", completion_scroll_color);
- get_text_editor()->add_theme_color_override("completion_font_color", completion_font_color);
- get_text_editor()->add_theme_color_override("font_color", text_color);
- get_text_editor()->add_theme_color_override("line_number_color", line_number_color);
- get_text_editor()->add_theme_color_override("caret_color", caret_color);
- get_text_editor()->add_theme_color_override("caret_background_color", caret_background_color);
- get_text_editor()->add_theme_color_override("font_selected_color", text_selected_color);
- get_text_editor()->add_theme_color_override("selection_color", selection_color);
- get_text_editor()->add_theme_color_override("brace_mismatch_color", brace_mismatch_color);
- get_text_editor()->add_theme_color_override("current_line_color", current_line_color);
- get_text_editor()->add_theme_color_override("line_length_guideline_color", line_length_guideline_color);
- get_text_editor()->add_theme_color_override("word_highlighted_color", word_highlighted_color);
- get_text_editor()->add_theme_color_override("mark_color", mark_color);
- get_text_editor()->add_theme_color_override("bookmark_color", bookmark_color);
- get_text_editor()->add_theme_color_override("breakpoint_color", breakpoint_color);
- get_text_editor()->add_theme_color_override("executing_line_color", executing_line_color);
- get_text_editor()->add_theme_color_override("code_folding_color", code_folding_color);
- get_text_editor()->add_theme_color_override("search_result_color", search_result_color);
- get_text_editor()->add_theme_color_override("search_result_border_color", search_result_border_color);
+ CodeEdit *text_editor = get_text_editor();
+ Color updated_marked_line_color = EDITOR_GET("text_editor/highlighting/mark_color");
+ if (updated_marked_line_color != marked_line_color) {
+ for (int i = 0; i < text_editor->get_line_count(); i++) {
+ if (text_editor->get_line_background_color(i) == marked_line_color) {
+ text_editor->set_line_background_color(i, updated_marked_line_color);
+ }
+ }
+ marked_line_color = updated_marked_line_color;
+ }
syntax_highlighter->set_number_color(EDITOR_GET("text_editor/highlighting/number_color"));
syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/highlighting/symbol_color"));
@@ -141,9 +116,14 @@ void ShaderTextEditor::_load_theme_settings() {
List<String> keywords;
ShaderLanguage::get_keyword_list(&keywords);
const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
+ const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color");
for (List<String>::Element *E = keywords.front(); E; E = E->next()) {
- syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ if (ShaderLanguage::is_control_flow_keyword(E->get())) {
+ syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ } else {
+ syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ }
}
// Colorize built-ins like `COLOR` differently to make them easier
@@ -173,6 +153,16 @@ void ShaderTextEditor::_load_theme_settings() {
syntax_highlighter->clear_color_regions();
syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
syntax_highlighter->add_color_region("//", "", comment_color, true);
+
+ text_editor->clear_comment_delimiters();
+ text_editor->add_comment_delimiter("/*", "*/", false);
+ text_editor->add_comment_delimiter("//", "", true);
+
+ if (warnings_panel) {
+ // Warnings panel
+ warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
+ warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts"));
+ }
}
void ShaderTextEditor::_check_shader_mode() {
@@ -219,6 +209,9 @@ void ShaderTextEditor::_validate_script() {
ShaderLanguage sl;
+ sl.enable_warning_checking(saved_warnings_enabled);
+ sl.set_warning_flags(saved_warning_flags);
+
Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type);
if (err != OK) {
@@ -226,25 +219,75 @@ void ShaderTextEditor::_validate_script() {
set_error(error_text);
set_error_pos(sl.get_error_line() - 1, 0);
for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
- get_text_editor()->set_line_as_marked(i, false);
+ get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
}
- get_text_editor()->set_line_as_marked(sl.get_error_line() - 1, true);
-
+ get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color);
} else {
for (int i = 0; i < get_text_editor()->get_line_count(); i++) {
- get_text_editor()->set_line_as_marked(i, false);
+ get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0));
}
set_error("");
}
+ if (warnings.size() > 0 || err != OK) {
+ warnings_panel->clear();
+ }
+ warnings.clear();
+ for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) {
+ warnings.push_back(E->get());
+ }
+ if (warnings.size() > 0 && err == OK) {
+ warnings.sort_custom<WarningsComparator>();
+ _update_warning_panel();
+ } else {
+ set_warning_count(0);
+ }
emit_signal("script_changed");
}
+void ShaderTextEditor::_update_warning_panel() {
+ int warning_count = 0;
+
+ warnings_panel->push_table(2);
+ for (int i = 0; i < warnings.size(); i++) {
+ ShaderWarning &w = warnings[i];
+
+ if (warning_count == 0) {
+ if (saved_treat_warning_as_errors) {
+ String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors.");
+ set_error_pos(w.get_line() - 1, 0);
+ set_error(error_text);
+ get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color);
+ }
+ }
+
+ warning_count++;
+
+ // First cell.
+ warnings_panel->push_cell();
+ warnings_panel->push_meta(w.get_line() - 1);
+ warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor"));
+ warnings_panel->add_text(TTR("Line") + " " + itos(w.get_line()));
+ warnings_panel->add_text(" (" + w.get_name() + "):");
+ warnings_panel->pop(); // Color.
+ warnings_panel->pop(); // Meta goto.
+ warnings_panel->pop(); // Cell.
+
+ // Second cell.
+ warnings_panel->push_cell();
+ warnings_panel->add_text(w.get_message());
+ warnings_panel->pop(); // Cell.
+ }
+ warnings_panel->pop(); // Table.
+
+ set_warning_count(warning_count);
+}
+
void ShaderTextEditor::_bind_methods() {
}
ShaderTextEditor::ShaderTextEditor() {
- syntax_highlighter.instance();
+ syntax_highlighter.instantiate();
get_text_editor()->set_syntax_highlighter(syntax_highlighter);
}
@@ -280,25 +323,19 @@ void ShaderEditor::_menu_option(int p_option) {
if (shader.is_null()) {
return;
}
-
- CodeEdit *tx = shader_editor->get_text_editor();
- tx->indent_selected_lines_left();
-
+ shader_editor->get_text_editor()->unindent_lines();
} break;
case EDIT_INDENT_RIGHT: {
if (shader.is_null()) {
return;
}
-
- CodeEdit *tx = shader_editor->get_text_editor();
- tx->indent_selected_lines_right();
-
+ shader_editor->get_text_editor()->indent_lines();
} break;
case EDIT_DELETE_LINE: {
shader_editor->delete_lines();
} break;
- case EDIT_CLONE_DOWN: {
- shader_editor->clone_lines_down();
+ case EDIT_DUPLICATE_SELECTION: {
+ shader_editor->duplicate_selection();
} break;
case EDIT_TOGGLE_COMMENT: {
if (shader.is_null()) {
@@ -309,7 +346,7 @@ void ShaderEditor::_menu_option(int p_option) {
} break;
case EDIT_COMPLETE: {
- shader_editor->get_text_editor()->query_code_comple();
+ shader_editor->get_text_editor()->request_code_completion();
} break;
case SEARCH_FIND: {
shader_editor->get_find_replace_bar()->popup_search();
@@ -353,10 +390,6 @@ void ShaderEditor::_notification(int p_what) {
}
}
-void ShaderEditor::_params_changed() {
- shader_editor->_validate_script();
-}
-
void ShaderEditor::_editor_settings_changed() {
shader_editor->update_editor_settings();
@@ -365,8 +398,19 @@ void ShaderEditor::_editor_settings_changed() {
shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false);
}
+void ShaderEditor::_show_warnings_panel(bool p_show) {
+ warnings_panel->set_visible(p_show);
+}
+
+void ShaderEditor::_warning_clicked(Variant p_line) {
+ if (p_line.get_type() == Variant::INT) {
+ shader_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t());
+ }
+}
+
void ShaderEditor::_bind_methods() {
- ClassDB::bind_method("_params_changed", &ShaderEditor::_params_changed);
+ ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel);
+ ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked);
}
void ShaderEditor::ensure_select_current() {
@@ -384,6 +428,47 @@ void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
shader_editor->goto_line_selection(p_line, p_begin, p_end);
}
+void ShaderEditor::_project_settings_changed() {
+ _update_warnings(true);
+}
+
+void ShaderEditor::_update_warnings(bool p_validate) {
+ bool changed = false;
+
+ bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize();
+ if (warnings_enabled != saved_warnings_enabled) {
+ saved_warnings_enabled = warnings_enabled;
+ changed = true;
+ }
+
+ bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize();
+ if (treat_warning_as_errors != saved_treat_warning_as_errors) {
+ saved_treat_warning_as_errors = treat_warning_as_errors;
+ changed = true;
+ }
+
+ bool update_flags = false;
+
+ for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) {
+ ShaderWarning::Code code = (ShaderWarning::Code)i;
+ bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower());
+
+ if (saved_warnings[code] != value) {
+ saved_warnings[code] = value;
+ update_flags = true;
+ changed = true;
+ }
+ }
+
+ if (update_flags) {
+ saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings);
+ }
+
+ if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) {
+ shader_editor->validate_script();
+ }
+}
+
void ShaderEditor::_check_for_external_edit() {
if (shader.is_null() || !shader.is_valid()) {
return;
@@ -555,17 +640,24 @@ void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) {
}
ShaderEditor::ShaderEditor(EditorNode *p_node) {
+ GLOBAL_DEF("debug/shader_language/warnings/enable", true);
+ GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false);
+ for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) {
+ GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true);
+ }
+ _update_warnings(false);
+
shader_editor = memnew(ShaderTextEditor);
shader_editor->set_v_size_flags(SIZE_EXPAND_FILL);
shader_editor->add_theme_constant_override("separation", 0);
shader_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel));
shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders));
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ShaderEditor::_editor_settings_changed));
+ ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &ShaderEditor::_project_settings_changed));
- shader_editor->get_text_editor()->set_callhint_settings(
- EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
- EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset"));
+ shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"));
shader_editor->get_text_editor()->set_select_identifiers_on_hover(true);
shader_editor->get_text_editor()->set_context_menu_enabled(false);
@@ -600,7 +692,7 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) {
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE);
edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option));
@@ -646,7 +738,28 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) {
hbc->add_child(goto_menu);
hbc->add_child(help_menu);
hbc->add_theme_style_override("panel", p_node->get_gui_base()->get_theme_stylebox("ScriptEditorPanel", "EditorStyles"));
- main_container->add_child(shader_editor);
+
+ VSplitContainer *editor_box = memnew(VSplitContainer);
+ main_container->add_child(editor_box);
+ editor_box->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
+ editor_box->add_child(shader_editor);
+
+ FindReplaceBar *bar = memnew(FindReplaceBar);
+ main_container->add_child(bar);
+ bar->hide();
+ shader_editor->set_find_replace_bar(bar);
+
+ warnings_panel = memnew(RichTextLabel);
+ warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
+ warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ warnings_panel->set_meta_underline(true);
+ warnings_panel->set_selection_enabled(true);
+ warnings_panel->set_focus_mode(FOCUS_CLICK);
+ warnings_panel->hide();
+ warnings_panel->connect("meta_clicked", callable_mp(this, &ShaderEditor::_warning_clicked));
+ editor_box->add_child(warnings_panel);
+ shader_editor->set_warnings_panel(warnings_panel);
goto_line_dialog = memnew(GotoLineDialog);
add_child(goto_line_dialog);
diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h
index 731c0a5b7e..77579754d3 100644
--- a/editor/plugins/shader_editor_plugin.h
+++ b/editor/plugins/shader_editor_plugin.h
@@ -35,6 +35,7 @@
#include "editor/editor_plugin.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel_container.h"
+#include "scene/gui/rich_text_label.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/text_edit.h"
#include "scene/main/timer.h"
@@ -44,10 +45,19 @@
class ShaderTextEditor : public CodeTextEditor {
GDCLASS(ShaderTextEditor, CodeTextEditor);
+ Color marked_line_color = Color(1, 1, 1);
+
+ struct WarningsComparator {
+ _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); }
+ };
+
Ref<CodeHighlighter> syntax_highlighter;
+ RichTextLabel *warnings_panel = nullptr;
Ref<Shader> shader;
+ List<ShaderWarning> warnings;
void _check_shader_mode();
+ void _update_warning_panel();
protected:
static void _bind_methods();
@@ -59,6 +69,7 @@ public:
virtual void _validate_script() override;
void reload_text();
+ void set_warnings_panel(RichTextLabel *p_warnings_panel);
Ref<Shader> get_edited_shader() const;
void set_edited_shader(const Ref<Shader> &p_shader);
@@ -80,7 +91,7 @@ class ShaderEditor : public PanelContainer {
EDIT_INDENT_LEFT,
EDIT_INDENT_RIGHT,
EDIT_DELETE_LINE,
- EDIT_CLONE_DOWN,
+ EDIT_DUPLICATE_SELECTION,
EDIT_TOGGLE_COMMENT,
EDIT_COMPLETE,
SEARCH_FIND,
@@ -100,6 +111,7 @@ class ShaderEditor : public PanelContainer {
PopupMenu *bookmarks_menu;
MenuButton *help_menu;
PopupMenu *context_menu;
+ RichTextLabel *warnings_panel = nullptr;
uint64_t idle;
GotoLineDialog *goto_line_dialog;
@@ -109,13 +121,16 @@ class ShaderEditor : public PanelContainer {
ShaderTextEditor *shader_editor;
void _menu_option(int p_option);
- void _params_changed();
mutable Ref<Shader> shader;
void _editor_settings_changed();
+ void _project_settings_changed();
void _check_for_external_edit();
void _reload_shader_from_disk();
+ void _show_warnings_panel(bool p_show);
+ void _warning_clicked(Variant p_line);
+ void _update_warnings(bool p_validate);
protected:
void _notification(int p_what);
diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp
index 47d7f8204b..85ccc5b798 100644
--- a/editor/plugins/shader_file_editor_plugin.cpp
+++ b/editor/plugins/shader_file_editor_plugin.cpp
@@ -272,7 +272,7 @@ ShaderFileEditor::ShaderFileEditor(EditorNode *p_node) {
main_vb->add_child(stage_hb);
Ref<ButtonGroup> bg;
- bg.instance();
+ bg.instantiate();
for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) {
Button *button = memnew(Button(stage_str[i]));
button->set_toggle_mode(true);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 404ef62eca..0b04c2e50e 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -95,7 +95,7 @@ void BoneTransformEditor::create_editors() {
section->get_vbox()->add_child(transform_section);
// Transform/Matrix property
- transform_property = memnew(EditorPropertyTransform());
+ transform_property = memnew(EditorPropertyTransform3D());
transform_property->setup(-10000, 10000, 0.001f, true);
transform_property->set_label("Transform");
transform_property->set_use_folding(true);
@@ -171,7 +171,7 @@ void BoneTransformEditor::_value_changed(const double p_value) {
return;
}
- Transform tform = compute_transform_from_vector3s();
+ Transform3D tform = compute_transform_from_vector3s();
_change_transform(tform);
}
@@ -179,30 +179,30 @@ void BoneTransformEditor::_value_changed_vector3(const String p_property_name, c
if (updating) {
return;
}
- Transform tform = compute_transform_from_vector3s();
+ Transform3D tform = compute_transform_from_vector3s();
_change_transform(tform);
}
-Transform BoneTransformEditor::compute_transform_from_vector3s() const {
+Transform3D BoneTransformEditor::compute_transform_from_vector3s() const {
// Convert rotation from degrees to radians.
Vector3 prop_rotation = rotation_property->get_vector();
prop_rotation.x = Math::deg2rad(prop_rotation.x);
prop_rotation.y = Math::deg2rad(prop_rotation.y);
prop_rotation.z = Math::deg2rad(prop_rotation.z);
- return Transform(
+ return Transform3D(
Basis(prop_rotation, scale_property->get_vector()),
translation_property->get_vector());
}
-void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform p_transform, const StringName p_edited_property_name, const bool p_boolean) {
+void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean) {
if (updating) {
return;
}
_change_transform(p_transform);
}
-void BoneTransformEditor::_change_transform(Transform p_new_transform) {
+void BoneTransformEditor::_change_transform(Transform3D p_new_transform) {
if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") {
undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS);
undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int()));
@@ -235,7 +235,7 @@ void BoneTransformEditor::_update_properties() {
updating = true;
- Transform tform = skeleton->get(property);
+ Transform3D tform = skeleton->get(property);
_update_transform_properties(tform);
}
@@ -250,11 +250,11 @@ void BoneTransformEditor::_update_custom_pose_properties() {
updating = true;
- Transform tform = skeleton->get_bone_custom_pose(property.to_int());
+ Transform3D tform = skeleton->get_bone_custom_pose(property.to_int());
_update_transform_properties(tform);
}
-void BoneTransformEditor::_update_transform_properties(Transform tform) {
+void BoneTransformEditor::_update_transform_properties(Transform3D tform) {
Basis rotation_basis = tform.get_basis();
Vector3 rotation_radians = rotation_basis.get_rotation_euler();
Vector3 rotation_degrees = Vector3(Math::rad2deg(rotation_radians.x), Math::rad2deg(rotation_radians.y), Math::rad2deg(rotation_radians.z));
@@ -306,7 +306,7 @@ void BoneTransformEditor::_key_button_pressed() {
}
// Need to normalize the basis before you key it
- Transform tform = compute_transform_from_vector3s();
+ Transform3D tform = compute_transform_from_vector3s();
tform.orthonormalize();
AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform);
}
@@ -380,7 +380,7 @@ void Skeleton3DEditor::create_physical_skeleton() {
}
PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) {
- const Transform child_rest = skeleton->get_bone_rest(bone_child_id);
+ const Transform3D child_rest = skeleton->get_bone_rest(bone_child_id);
const real_t half_height(child_rest.origin.length() * 0.5);
const real_t radius(half_height * 0.2);
@@ -392,15 +392,15 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi
CollisionShape3D *bone_shape = memnew(CollisionShape3D);
bone_shape->set_shape(bone_shape_capsule);
- Transform capsule_transform;
+ Transform3D capsule_transform;
capsule_transform.basis = Basis(Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(0, -1, 0));
bone_shape->set_transform(capsule_transform);
- Transform body_transform;
+ Transform3D body_transform;
body_transform.set_look_at(Vector3(0, 0, 0), child_rest.origin);
body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height));
- Transform joint_transform;
+ Transform3D joint_transform;
joint_transform.origin = Vector3(0, 0, half_height);
PhysicalBone3D *physical_bone = memnew(PhysicalBone3D);
@@ -666,9 +666,9 @@ void Skeleton3DEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties);
ClassDB::bind_method(D_METHOD("_on_click_option"), &Skeleton3DEditor::_on_click_option);
- ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Skeleton3DEditor::drop_data_fw);
ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone);
}
@@ -700,7 +700,7 @@ Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) {
editor = p_node;
Ref<EditorInspectorPluginSkeleton> skeleton_plugin;
- skeleton_plugin.instance();
+ skeleton_plugin.instantiate();
skeleton_plugin->editor = editor;
EditorInspector::add_inspector_plugin(skeleton_plugin);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 14c213f7b2..9de52c6fa8 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -41,7 +41,7 @@ class PhysicalBone3D;
class Skeleton3DEditorPlugin;
class Button;
class CheckBox;
-class EditorPropertyTransform;
+class EditorPropertyTransform3D;
class EditorPropertyVector3;
class BoneTransformEditor : public VBoxContainer {
@@ -53,7 +53,7 @@ class BoneTransformEditor : public VBoxContainer {
EditorPropertyVector3 *rotation_property = nullptr;
EditorPropertyVector3 *scale_property = nullptr;
EditorInspectorSection *transform_section = nullptr;
- EditorPropertyTransform *transform_property = nullptr;
+ EditorPropertyTransform3D *transform_property = nullptr;
Rect2 background_rects[5];
@@ -78,11 +78,11 @@ class BoneTransformEditor : public VBoxContainer {
// Called when the one of the EditorPropertyVector3 are updated.
void _value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean);
// Called when the transform_property is updated.
- void _value_changed_transform(const String p_property_name, const Transform p_transform, const StringName p_edited_property_name, const bool p_boolean);
+ void _value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean);
// Changes the transform to the given transform and updates the UI accordingly.
- void _change_transform(Transform p_new_transform);
+ void _change_transform(Transform3D p_new_transform);
// Creates a Transform using the EditorPropertyVector3 properties.
- Transform compute_transform_from_vector3s() const;
+ Transform3D compute_transform_from_vector3s() const;
void update_enabled_checkbox();
@@ -98,7 +98,7 @@ public:
void _update_properties();
void _update_custom_pose_properties();
- void _update_transform_properties(Transform p_transform);
+ void _update_transform_properties(Transform3D p_transform);
// Can/cannot modify the spinner values for the Transform
void set_read_only(const bool p_read_only);
@@ -127,7 +127,7 @@ class Skeleton3DEditor : public VBoxContainer {
struct BoneInfo {
PhysicalBone3D *physical_bone = nullptr;
- Transform relative_rest; // Relative to skeleton node
+ Transform3D relative_rest; // Relative to skeleton node
};
EditorNode *editor;
diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp
index 4a7f6c0f7e..ef328bcfe2 100644
--- a/editor/plugins/sprite_2d_editor_plugin.cpp
+++ b/editor/plugins/sprite_2d_editor_plugin.cpp
@@ -186,7 +186,7 @@ void Sprite2DEditor::_update_mesh_data() {
}
Ref<BitMap> bm;
- bm.instance();
+ bm.instantiate();
bm->create_from_image_alpha(image);
int shrink = shrink_pixels->get_value();
@@ -322,7 +322,7 @@ void Sprite2DEditor::_convert_to_mesh_2d_node() {
}
Ref<ArrayMesh> mesh;
- mesh.instance();
+ mesh.instantiate();
Array a;
a.resize(Mesh::ARRAY_MAX);
@@ -435,7 +435,7 @@ void Sprite2DEditor::_create_light_occluder_2d_node() {
Vector<Vector2> outline = computed_outline_lines[i];
Ref<OccluderPolygon2D> polygon;
- polygon.instance();
+ polygon.instantiate();
PackedVector2Array a;
a.resize(outline.size());
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index bd6dac7490..70c7b3072b 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -103,19 +103,18 @@ void SpriteFramesEditor::_sheet_preview_draw() {
}
void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
- Ref<InputEventMouseButton> mb = p_event;
-
+ const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- Size2i size = split_sheet_preview->get_size();
- int h = split_sheet_h->get_value();
- int v = split_sheet_v->get_value();
+ const Size2i size = split_sheet_preview->get_size();
+ const int h = split_sheet_h->get_value();
+ const int v = split_sheet_v->get_value();
- int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1);
- int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1);
+ const int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1);
+ const int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1);
- int idx = h * y + x;
+ const int idx = h * y + x;
- if (mb->get_shift() && last_frame_selected >= 0) {
+ if (mb->is_shift_pressed() && last_frame_selected >= 0) {
//select multiple
int from = idx;
int to = last_frame_selected;
@@ -124,13 +123,19 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
}
for (int i = from; i <= to; i++) {
- if (mb->get_control()) {
+ // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
+ frames_toggled_by_mouse_hover.insert(idx);
+
+ if (mb->is_ctrl_pressed()) {
frames_selected.erase(i);
} else {
frames_selected.insert(i);
}
}
} else {
+ // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
+ frames_toggled_by_mouse_hover.insert(idx);
+
if (frames_selected.has(idx)) {
frames_selected.erase(idx);
} else {
@@ -141,6 +146,39 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
last_frame_selected = idx;
split_sheet_preview->update();
}
+
+ if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ frames_toggled_by_mouse_hover.clear();
+ }
+
+ const Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
+ // Select by holding down the mouse button on frames.
+ const Size2i size = split_sheet_preview->get_size();
+ const int h = split_sheet_h->get_value();
+ const int v = split_sheet_v->get_value();
+
+ const int x = CLAMP(int(mm->get_position().x) * h / size.width, 0, h - 1);
+ const int y = CLAMP(int(mm->get_position().y) * v / size.height, 0, v - 1);
+
+ const int idx = h * y + x;
+
+ if (!frames_toggled_by_mouse_hover.has(idx)) {
+ // Only allow toggling each tile once per mouse hold.
+ // Otherwise, the selection would constantly "flicker" in and out when moving the mouse cursor.
+ // The mouse button must be released before it can be toggled again.
+ frames_toggled_by_mouse_hover.insert(idx);
+
+ if (frames_selected.has(idx)) {
+ frames_selected.erase(idx);
+ } else {
+ frames_selected.insert(idx);
+ }
+
+ last_frame_selected = idx;
+ split_sheet_preview->update();
+ }
+ }
}
void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {
@@ -150,11 +188,11 @@ void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {
// Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer
// to allow performing this action anywhere, even if the cursor isn't
// hovering the texture in the workspace.
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {
_sheet_zoom_in();
// Don't scroll up after zooming in.
accept_event();
- } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) {
+ } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {
_sheet_zoom_out();
// Don't scroll down after zooming out.
accept_event();
@@ -189,7 +227,7 @@ void SpriteFramesEditor::_sheet_add_frames() {
int y = (yp * height) + region_rect.position.y;
Ref<AtlasTexture> at;
- at.instance();
+ at.instantiate();
at->set_atlas(split_sheet_preview->get_texture());
at->set_region(Rect2(x, y, width, height));
@@ -220,7 +258,7 @@ void SpriteFramesEditor::_sheet_zoom_out() {
void SpriteFramesEditor::_sheet_zoom_reset() {
// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.
- sheet_zoom = MAX(1.0, EDSCALE);
+ sheet_zoom = MAX(1.0f, EDSCALE);
Size2 texture_size = split_sheet_preview->get_texture()->get_size();
split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);
}
@@ -252,10 +290,10 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
EditorNode::get_singleton()->show_warning(TTR("Unable to load images"));
ERR_FAIL_COND(!texture.is_valid());
}
- bool new_texture = texture != split_sheet_preview->get_texture();
frames_selected.clear();
last_frame_selected = -1;
+ bool new_texture = texture != split_sheet_preview->get_texture();
split_sheet_preview->set_texture(texture);
if (new_texture) {
//different texture, reset to 4x4
@@ -280,17 +318,17 @@ void SpriteFramesEditor::_notification(int p_what) {
move_down->set_icon(get_theme_icon("MoveRight", "EditorIcons"));
_delete->set_icon(get_theme_icon("Remove", "EditorIcons"));
zoom_out->set_icon(get_theme_icon("ZoomLess", "EditorIcons"));
- zoom_1->set_icon(get_theme_icon("ZoomReset", "EditorIcons"));
+ zoom_reset->set_icon(get_theme_icon("ZoomReset", "EditorIcons"));
zoom_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons"));
new_anim->set_icon(get_theme_icon("New", "EditorIcons"));
remove_anim->set_icon(get_theme_icon("Remove", "EditorIcons"));
split_sheet_zoom_out->set_icon(get_theme_icon("ZoomLess", "EditorIcons"));
- split_sheet_zoom_1->set_icon(get_theme_icon("ZoomReset", "EditorIcons"));
+ split_sheet_zoom_reset->set_icon(get_theme_icon("ZoomReset", "EditorIcons"));
split_sheet_zoom_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons"));
[[fallthrough]];
}
case NOTIFICATION_THEME_CHANGED: {
- splite_sheet_scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree"));
+ split_sheet_scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree"));
} break;
case NOTIFICATION_READY: {
add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up.
@@ -513,7 +551,7 @@ void SpriteFramesEditor::_animation_select() {
if (frames->has_animation(edited_anim)) {
double value = anim_speed->get_line_edit()->get_text().to_float();
- if (!Math::is_equal_approx(value, frames->get_animation_speed(edited_anim))) {
+ if (!Math::is_equal_approx(value, (double)frames->get_animation_speed(edited_anim))) {
_animation_fps_changed(value);
}
}
@@ -694,11 +732,11 @@ void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) {
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {
_zoom_in();
// Don't scroll up after zooming in.
accept_event();
- } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) {
+ } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {
_zoom_out();
// Don't scroll down after zooming out.
accept_event();
@@ -733,7 +771,7 @@ void SpriteFramesEditor::_zoom_out() {
}
void SpriteFramesEditor::_zoom_reset() {
- thumbnail_zoom = MAX(1.0, EDSCALE);
+ thumbnail_zoom = MAX(1.0f, EDSCALE);
tree->set_fixed_column_width(thumbnail_default_size * 3 / 2);
tree->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size));
}
@@ -954,7 +992,7 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
if (String(d["type"]) == "files") {
Vector<String> files = d["files"];
- if (Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+ if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
_prepare_sprite_sheet(files[0]);
} else {
_file_load_request(files, at_pos);
@@ -964,9 +1002,9 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
void SpriteFramesEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false));
- ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &SpriteFramesEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &SpriteFramesEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw"), &SpriteFramesEditor::drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &SpriteFramesEditor::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &SpriteFramesEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &SpriteFramesEditor::drop_data_fw);
}
SpriteFramesEditor::SpriteFramesEditor() {
@@ -1086,11 +1124,13 @@ SpriteFramesEditor::SpriteFramesEditor() {
zoom_out->set_flat(true);
zoom_out->set_tooltip(TTR("Zoom Out"));
hbc->add_child(zoom_out);
- zoom_1 = memnew(Button);
- zoom_1->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_reset));
- zoom_1->set_flat(true);
- zoom_1->set_tooltip(TTR("Zoom Reset"));
- hbc->add_child(zoom_1);
+
+ zoom_reset = memnew(Button);
+ zoom_reset->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_reset));
+ zoom_reset->set_flat(true);
+ zoom_reset->set_tooltip(TTR("Zoom Reset"));
+ hbc->add_child(zoom_reset);
+
zoom_in = memnew(Button);
zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_in));
zoom_in->set_flat(true);
@@ -1183,16 +1223,16 @@ SpriteFramesEditor::SpriteFramesEditor() {
split_sheet_preview->connect("draw", callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw));
split_sheet_preview->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_preview_input));
- splite_sheet_scroll = memnew(ScrollContainer);
- splite_sheet_scroll->set_enable_h_scroll(true);
- splite_sheet_scroll->set_enable_v_scroll(true);
- splite_sheet_scroll->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input));
- split_sheet_panel->add_child(splite_sheet_scroll);
+ split_sheet_scroll = memnew(ScrollContainer);
+ split_sheet_scroll->set_enable_h_scroll(true);
+ split_sheet_scroll->set_enable_v_scroll(true);
+ split_sheet_scroll->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input));
+ split_sheet_panel->add_child(split_sheet_scroll);
CenterContainer *cc = memnew(CenterContainer);
cc->add_child(split_sheet_preview);
cc->set_h_size_flags(SIZE_EXPAND_FILL);
cc->set_v_size_flags(SIZE_EXPAND_FILL);
- splite_sheet_scroll->add_child(cc);
+ split_sheet_scroll->add_child(cc);
MarginContainer *split_sheet_zoom_margin = memnew(MarginContainer);
split_sheet_panel->add_child(split_sheet_zoom_margin);
@@ -1209,12 +1249,14 @@ SpriteFramesEditor::SpriteFramesEditor() {
split_sheet_zoom_out->set_tooltip(TTR("Zoom Out"));
split_sheet_zoom_out->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out));
split_sheet_zoom_hb->add_child(split_sheet_zoom_out);
- split_sheet_zoom_1 = memnew(Button);
- split_sheet_zoom_1->set_flat(true);
- split_sheet_zoom_1->set_focus_mode(FOCUS_NONE);
- split_sheet_zoom_1->set_tooltip(TTR("Zoom Reset"));
- split_sheet_zoom_1->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset));
- split_sheet_zoom_hb->add_child(split_sheet_zoom_1);
+
+ split_sheet_zoom_reset = memnew(Button);
+ split_sheet_zoom_reset->set_flat(true);
+ split_sheet_zoom_reset->set_focus_mode(FOCUS_NONE);
+ split_sheet_zoom_reset->set_tooltip(TTR("Zoom Reset"));
+ split_sheet_zoom_reset->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset));
+ split_sheet_zoom_hb->add_child(split_sheet_zoom_reset);
+
split_sheet_zoom_in = memnew(Button);
split_sheet_zoom_in->set_flat(true);
split_sheet_zoom_in->set_focus_mode(FOCUS_NONE);
@@ -1230,14 +1272,14 @@ SpriteFramesEditor::SpriteFramesEditor() {
// Config scale.
scale_ratio = 1.2f;
- thumbnail_default_size = 96 * MAX(1.0, EDSCALE);
- thumbnail_zoom = MAX(1.0, EDSCALE);
- max_thumbnail_zoom = 8.0f * MAX(1.0, EDSCALE);
- min_thumbnail_zoom = 0.1f * MAX(1.0, EDSCALE);
+ thumbnail_default_size = 96 * MAX(1, EDSCALE);
+ thumbnail_zoom = MAX(1.0f, EDSCALE);
+ max_thumbnail_zoom = 8.0f * MAX(1.0f, EDSCALE);
+ min_thumbnail_zoom = 0.1f * MAX(1.0f, EDSCALE);
// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.
- sheet_zoom = MAX(1.0, EDSCALE);
- max_sheet_zoom = 16.0f * MAX(1.0, EDSCALE);
- min_sheet_zoom = 0.01f * MAX(1.0, EDSCALE);
+ sheet_zoom = MAX(1.0f, EDSCALE);
+ max_sheet_zoom = 16.0f * MAX(1.0f, EDSCALE);
+ min_sheet_zoom = 0.01f * MAX(1.0f, EDSCALE);
_zoom_reset();
}
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index bbc26ca726..e6c59e3533 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -54,13 +54,12 @@ class SpriteFramesEditor : public HSplitContainer {
Button *move_up;
Button *move_down;
Button *zoom_out;
- Button *zoom_1;
+ Button *zoom_reset;
Button *zoom_in;
ItemList *tree;
bool loading_scene;
int sel;
- HSplitContainer *split;
Button *new_anim;
Button *remove_anim;
@@ -79,15 +78,16 @@ class SpriteFramesEditor : public HSplitContainer {
ConfirmationDialog *delete_dialog;
ConfirmationDialog *split_sheet_dialog;
- ScrollContainer *splite_sheet_scroll;
+ ScrollContainer *split_sheet_scroll;
TextureRect *split_sheet_preview;
SpinBox *split_sheet_h;
SpinBox *split_sheet_v;
Button *split_sheet_zoom_out;
- Button *split_sheet_zoom_1;
+ Button *split_sheet_zoom_reset;
Button *split_sheet_zoom_in;
EditorFileDialog *file_split_sheet;
Set<int> frames_selected;
+ Set<int> frames_toggled_by_mouse_hover;
int last_frame_selected;
float scale_ratio;
diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp
index 64df982d5d..91c5e96f08 100644
--- a/editor/plugins/style_box_editor_plugin.cpp
+++ b/editor/plugins/style_box_editor_plugin.cpp
@@ -44,7 +44,7 @@ void EditorInspectorPluginStyleBox::parse_begin(Object *p_object) {
add_custom_control(preview);
}
-bool EditorInspectorPluginStyleBox::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) {
+bool EditorInspectorPluginStyleBox::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, bool p_wide) {
return false; //do not want
}
@@ -93,6 +93,6 @@ StyleBoxPreview::StyleBoxPreview() {
StyleBoxEditorPlugin::StyleBoxEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginStyleBox> inspector_plugin;
- inspector_plugin.instance();
+ inspector_plugin.instantiate();
add_inspector_plugin(inspector_plugin);
}
diff --git a/editor/plugins/style_box_editor_plugin.h b/editor/plugins/style_box_editor_plugin.h
index d4a235cd10..8ca348bd80 100644
--- a/editor/plugins/style_box_editor_plugin.h
+++ b/editor/plugins/style_box_editor_plugin.h
@@ -61,7 +61,7 @@ class EditorInspectorPluginStyleBox : public EditorInspectorPlugin {
public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
- virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false) override;
+ virtual bool 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 = false) override;
virtual void parse_end() override;
};
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index 2b8bfe067d..5766646f7d 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -59,58 +59,7 @@ void TextEditor::_change_syntax_highlighter(int p_idx) {
}
void TextEditor::_load_theme_settings() {
- CodeEdit *text_edit = code_editor->get_text_editor();
- text_edit->get_syntax_highlighter()->update_cache();
-
- Color background_color = EDITOR_GET("text_editor/highlighting/background_color");
- Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color");
- Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color");
- Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color");
- Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color");
- Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color");
- Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
- Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color");
- Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color");
- Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color");
- Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color");
- Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color");
- Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color");
- Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color");
- Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color");
- Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color");
- Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color");
- Color bookmark_color = EDITOR_GET("text_editor/highlighting/bookmark_color");
- Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color");
- Color executing_line_color = EDITOR_GET("text_editor/highlighting/executing_line_color");
- Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color");
- Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color");
- Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color");
-
- text_edit->add_theme_color_override("background_color", background_color);
- text_edit->add_theme_color_override("completion_background_color", completion_background_color);
- text_edit->add_theme_color_override("completion_selected_color", completion_selected_color);
- text_edit->add_theme_color_override("completion_existing_color", completion_existing_color);
- text_edit->add_theme_color_override("completion_scroll_color", completion_scroll_color);
- text_edit->add_theme_color_override("completion_font_color", completion_font_color);
- text_edit->add_theme_color_override("font_color", text_color);
- text_edit->add_theme_color_override("line_number_color", line_number_color);
- text_edit->add_theme_color_override("caret_color", caret_color);
- text_edit->add_theme_color_override("caret_background_color", caret_background_color);
- text_edit->add_theme_color_override("font_selected_color", text_selected_color);
- text_edit->add_theme_color_override("selection_color", selection_color);
- text_edit->add_theme_color_override("brace_mismatch_color", brace_mismatch_color);
- text_edit->add_theme_color_override("current_line_color", current_line_color);
- text_edit->add_theme_color_override("line_length_guideline_color", line_length_guideline_color);
- text_edit->add_theme_color_override("word_highlighted_color", word_highlighted_color);
- text_edit->add_theme_color_override("breakpoint_color", breakpoint_color);
- text_edit->add_theme_color_override("executing_line_color", executing_line_color);
- text_edit->add_theme_color_override("mark_color", mark_color);
- text_edit->add_theme_color_override("bookmark_color", bookmark_color);
- text_edit->add_theme_color_override("code_folding_color", code_folding_color);
- text_edit->add_theme_color_override("search_result_color", search_result_color);
- text_edit->add_theme_color_override("search_result_border_color", search_result_border_color);
-
- text_edit->add_theme_constant_override("line_spacing", EDITOR_DEF("text_editor/theme/line_spacing", 6));
+ code_editor->get_text_editor()->get_syntax_highlighter()->update_cache();
}
String TextEditor::get_name() {
@@ -171,6 +120,10 @@ void TextEditor::add_callback(const String &p_function, PackedStringArray p_args
void TextEditor::set_debugger_active(bool p_active) {
}
+Control *TextEditor::get_base_editor() const {
+ return code_editor->get_text_editor();
+}
+
Array TextEditor::get_breakpoints() {
return Array();
}
@@ -328,6 +281,10 @@ void TextEditor::clear_edit_menu() {
memdelete(edit_hb);
}
+void TextEditor::set_find_replace_bar(FindReplaceBar *p_bar) {
+ code_editor->set_find_replace_bar(p_bar);
+}
+
void TextEditor::_edit_option(int p_op) {
CodeEdit *tx = code_editor->get_text_editor();
@@ -363,19 +320,19 @@ void TextEditor::_edit_option(int p_op) {
code_editor->move_lines_down();
} break;
case EDIT_INDENT_LEFT: {
- tx->indent_selected_lines_left();
+ tx->unindent_lines();
} break;
case EDIT_INDENT_RIGHT: {
- tx->indent_selected_lines_right();
+ tx->indent_lines();
} break;
case EDIT_DELETE_LINE: {
code_editor->delete_lines();
} break;
- case EDIT_CLONE_DOWN: {
- code_editor->clone_lines_down();
+ case EDIT_DUPLICATE_SELECTION: {
+ code_editor->duplicate_selection();
} break;
case EDIT_TOGGLE_FOLD_LINE: {
- tx->toggle_fold_line(tx->cursor_get_line());
+ tx->toggle_foldable_line(tx->cursor_get_line());
tx->update();
} break;
case EDIT_FOLD_ALL_LINES: {
@@ -475,8 +432,8 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
tx->_get_mouse_pos(mb->get_global_position() - tx->get_global_position(), row, col);
tx->set_right_click_moves_caret(EditorSettings::get_singleton()->get("text_editor/cursor/right_click_moves_caret"));
- bool can_fold = tx->can_fold(row);
- bool is_folded = tx->is_folded(row);
+ bool can_fold = tx->can_fold_line(row);
+ bool is_folded = tx->is_line_folded(row);
if (tx->is_right_click_moving_caret()) {
if (tx->is_selection_active()) {
@@ -506,7 +463,7 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_MENU) {
CodeEdit *tx = code_editor->get_text_editor();
int line = tx->cursor_get_line();
- _make_context_menu(tx->is_selection_active(), tx->can_fold(line), tx->is_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos()));
+ _make_context_menu(tx->is_selection_active(), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos()));
context_menu->grab_focus();
}
}
@@ -585,7 +542,7 @@ TextEditor::TextEditor() {
edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option));
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("un_redo"), EDIT_REDO);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
edit_menu->get_popup()->add_separator();
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
@@ -602,7 +559,7 @@ TextEditor::TextEditor() {
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES);
edit_menu->get_popup()->add_separator();
- edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN);
+ edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS);
@@ -624,11 +581,11 @@ TextEditor::TextEditor() {
highlighter_menu->connect("id_pressed", callable_mp(this, &TextEditor::_change_syntax_highlighter));
Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter;
- plain_highlighter.instance();
+ plain_highlighter.instantiate();
add_syntax_highlighter(plain_highlighter);
Ref<EditorStandardSyntaxHighlighter> highlighter;
- highlighter.instance();
+ highlighter.instantiate();
add_syntax_highlighter(highlighter);
set_syntax_highlighter(plain_highlighter);
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index c066d51b18..86a4910ac0 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -66,7 +66,7 @@ private:
EDIT_INDENT_RIGHT,
EDIT_INDENT_LEFT,
EDIT_DELETE_LINE,
- EDIT_CLONE_DOWN,
+ EDIT_DUPLICATE_SELECTION,
EDIT_TO_UPPERCASE,
EDIT_TO_LOWERCASE,
EDIT_CAPITALIZE,
@@ -139,9 +139,12 @@ public:
virtual Control *get_edit_menu() override;
virtual void clear_edit_menu() override;
+ virtual void set_find_replace_bar(FindReplaceBar *p_bar) override;
virtual void validate() override;
+ virtual Control *get_base_editor() const override;
+
static void register_editor();
TextEditor();
diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp
index 36297c8a4a..696aa88e23 100644
--- a/editor/plugins/texture_3d_editor_plugin.cpp
+++ b/editor/plugins/texture_3d_editor_plugin.cpp
@@ -85,9 +85,9 @@ void Texture3DEditor::_make_shaders() {
" COLOR = textureLod(tex,vec3(UV,layer),0.0);\n"
"}";
- shader.instance();
+ shader.instantiate();
shader->set_code(shader_3d);
- material.instance();
+ material.instantiate();
material->set_shader(shader);
}
@@ -207,6 +207,6 @@ void EditorInspectorPlugin3DTexture::parse_begin(Object *p_object) {
Texture3DEditorPlugin::Texture3DEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPlugin3DTexture> plugin;
- plugin.instance();
+ plugin.instantiate();
add_inspector_plugin(plugin);
}
diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp
index ecf7370834..10b942d9ee 100644
--- a/editor/plugins/texture_editor_plugin.cpp
+++ b/editor/plugins/texture_editor_plugin.cpp
@@ -160,6 +160,6 @@ void EditorInspectorPluginTexture::parse_begin(Object *p_object) {
TextureEditorPlugin::TextureEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginTexture> plugin;
- plugin.instance();
+ plugin.instantiate();
add_inspector_plugin(plugin);
}
diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp
index 89ed98d53e..3f46cd64a2 100644
--- a/editor/plugins/texture_layered_editor_plugin.cpp
+++ b/editor/plugins/texture_layered_editor_plugin.cpp
@@ -112,7 +112,7 @@ void TextureLayeredEditor::_make_shaders() {
" COLOR = textureLod(tex,vec3(UV,layer),0.0);\n"
"}";
- shaders[0].instance();
+ shaders[0].instantiate();
shaders[0]->set_code(shader_2d_array);
String shader_cube = ""
@@ -125,7 +125,7 @@ void TextureLayeredEditor::_make_shaders() {
" COLOR = textureLod(tex,n,0.0);\n"
"}";
- shaders[1].instance();
+ shaders[1].instantiate();
shaders[1]->set_code(shader_cube);
String shader_cube_array = ""
@@ -139,11 +139,11 @@ void TextureLayeredEditor::_make_shaders() {
" COLOR = textureLod(tex,vec4(n,layer),0.0);\n"
"}";
- shaders[2].instance();
+ shaders[2].instantiate();
shaders[2]->set_code(shader_cube_array);
for (int i = 0; i < 3; i++) {
- materials[i].instance();
+ materials[i].instantiate();
materials[i]->set_shader(shaders[i]);
}
}
@@ -271,6 +271,6 @@ void EditorInspectorPluginLayeredTexture::parse_begin(Object *p_object) {
TextureLayeredEditorPlugin::TextureLayeredEditorPlugin(EditorNode *p_node) {
Ref<EditorInspectorPluginLayeredTexture> plugin;
- plugin.instance();
+ plugin.instantiate();
add_inspector_plugin(plugin);
}
diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp
index 7b927ad98b..d0ba68138b 100644
--- a/editor/plugins/texture_region_editor_plugin.cpp
+++ b/editor/plugins/texture_region_editor_plugin.cpp
@@ -331,7 +331,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
for (List<Rect2>::Element *E = autoslice_cache.front(); E; E = E->next()) {
if (E->get().has_point(point)) {
rect = E->get();
- if (Input::get_singleton()->is_key_pressed(KEY_CONTROL) && !(Input::get_singleton()->is_key_pressed(KEY_SHIFT | KEY_ALT))) {
+ if (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !(Input::get_singleton()->is_key_pressed(KEY_SHIFT | KEY_ALT))) {
Rect2 r;
if (node_sprite) {
r = node_sprite->get_region_rect();
@@ -863,13 +863,13 @@ Sprite2D *TextureRegionEditor::get_sprite() {
void TextureRegionEditor::edit(Object *p_obj) {
if (node_sprite) {
- node_sprite->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
+ node_sprite->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
}
if (node_sprite_3d) {
- node_sprite_3d->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
+ node_sprite_3d->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
}
if (node_ninepatch) {
- node_ninepatch->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
+ node_ninepatch->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
}
if (obj_styleBox.is_valid()) {
obj_styleBox->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
@@ -881,13 +881,22 @@ void TextureRegionEditor::edit(Object *p_obj) {
node_sprite = Object::cast_to<Sprite2D>(p_obj);
node_sprite_3d = Object::cast_to<Sprite3D>(p_obj);
node_ninepatch = Object::cast_to<NinePatchRect>(p_obj);
+
+ bool is_resource = false;
if (Object::cast_to<StyleBoxTexture>(p_obj)) {
obj_styleBox = Ref<StyleBoxTexture>(Object::cast_to<StyleBoxTexture>(p_obj));
+ is_resource = true;
}
if (Object::cast_to<AtlasTexture>(p_obj)) {
atlas_tex = Ref<AtlasTexture>(Object::cast_to<AtlasTexture>(p_obj));
+ is_resource = true;
+ }
+
+ if (is_resource) {
+ p_obj->connect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
+ } else {
+ p_obj->connect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
}
- p_obj->connect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed));
_edit_region();
} else {
node_sprite = nullptr;
@@ -937,7 +946,6 @@ void TextureRegionEditor::_edit_region() {
if (cache_map.has(texture->get_rid())) {
autoslice_cache = cache_map[texture->get_rid()];
autoslice_is_dirty = false;
- return;
} else {
if (is_visible() && snap_mode == SNAP_AUTOSLICE) {
_update_autoslice();
@@ -1034,7 +1042,7 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) {
hb_grid->add_child(sb_step_y);
hb_grid->add_child(memnew(VSeparator));
- hb_grid->add_child(memnew(Label(TTR("Sep.:"))));
+ hb_grid->add_child(memnew(Label(TTR("Separation:"))));
sb_sep_x = memnew(SpinBox);
sb_sep_x->set_min(0);
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index c765aa0319..be1aeb309f 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -30,16 +30,1200 @@
#include "theme_editor_plugin.h"
-#include "core/os/file_access.h"
#include "core/os/keyboard.h"
-#include "core/version.h"
+#include "editor/editor_resource_picker.h"
#include "editor/editor_scale.h"
-#include "scene/gui/progress_bar.h"
+#include "editor/progress_dialog.h"
+
+void ThemeItemImportTree::_update_items_tree() {
+ import_items_tree->clear();
+ TreeItem *root = import_items_tree->create_item();
+
+ if (base_theme.is_null()) {
+ return;
+ }
+
+ String filter_text = import_items_filter->get_text();
+
+ List<StringName> types;
+ List<StringName> names;
+ List<StringName> filtered_names;
+ base_theme->get_type_list(&types);
+ types.sort_custom<StringName::AlphCompare>();
+
+ int color_amount = 0;
+ int constant_amount = 0;
+ int font_amount = 0;
+ int font_size_amount = 0;
+ int icon_amount = 0;
+ int stylebox_amount = 0;
+
+ tree_color_items.clear();
+ tree_constant_items.clear();
+ tree_font_items.clear();
+ tree_font_size_items.clear();
+ tree_icon_items.clear();
+ tree_stylebox_items.clear();
+
+ for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ String type_name = (String)E->get();
+
+ TreeItem *type_node = import_items_tree->create_item(root);
+ type_node->set_meta("_can_be_imported", false);
+ type_node->set_collapsed(true);
+ type_node->set_text(0, type_name);
+ type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
+ type_node->set_checked(IMPORT_ITEM, false);
+ type_node->set_editable(IMPORT_ITEM, true);
+ type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
+ type_node->set_checked(IMPORT_ITEM_DATA, false);
+ type_node->set_editable(IMPORT_ITEM_DATA, true);
+
+ bool is_matching_filter = (filter_text.is_empty() || type_name.findn(filter_text) > -1);
+ bool has_filtered_items = false;
+ bool any_checked = false;
+ bool any_checked_with_data = false;
+
+ for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) {
+ Theme::DataType dt = (Theme::DataType)i;
+
+ names.clear();
+ filtered_names.clear();
+ base_theme->get_theme_item_list(dt, E->get(), &names);
+
+ bool data_type_has_filtered_items = false;
+
+ for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
+ String item_name = (String)F->get();
+ bool is_item_matching_filter = (item_name.findn(filter_text) > -1);
+ if (!filter_text.is_empty() && !is_matching_filter && !is_item_matching_filter) {
+ continue;
+ }
+
+ // Only mark this if actual items match the filter and not just the type group.
+ if (!filter_text.is_empty() && is_item_matching_filter) {
+ has_filtered_items = true;
+ data_type_has_filtered_items = true;
+ }
+ filtered_names.push_back(F->get());
+ }
+
+ if (filtered_names.size() == 0) {
+ continue;
+ }
+
+ TreeItem *data_type_node = import_items_tree->create_item(type_node);
+ data_type_node->set_meta("_can_be_imported", false);
+ data_type_node->set_metadata(0, i);
+ data_type_node->set_collapsed(!data_type_has_filtered_items);
+ data_type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
+ data_type_node->set_checked(IMPORT_ITEM, false);
+ data_type_node->set_editable(IMPORT_ITEM, true);
+ data_type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
+ data_type_node->set_checked(IMPORT_ITEM_DATA, false);
+ data_type_node->set_editable(IMPORT_ITEM_DATA, true);
+
+ List<TreeItem *> *item_list;
+
+ switch (dt) {
+ case Theme::DATA_TYPE_COLOR:
+ data_type_node->set_icon(0, get_theme_icon("Color", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Colors"));
+
+ item_list = &tree_color_items;
+ color_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ data_type_node->set_icon(0, get_theme_icon("MemberConstant", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Constants"));
+
+ item_list = &tree_constant_items;
+ constant_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ data_type_node->set_icon(0, get_theme_icon("Font", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Fonts"));
+
+ item_list = &tree_font_items;
+ font_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ data_type_node->set_icon(0, get_theme_icon("FontSize", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Font Sizes"));
+
+ item_list = &tree_font_size_items;
+ font_size_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ data_type_node->set_icon(0, get_theme_icon("ImageTexture", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Icons"));
+
+ item_list = &tree_icon_items;
+ icon_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ data_type_node->set_icon(0, get_theme_icon("StyleBoxFlat", "EditorIcons"));
+ data_type_node->set_text(0, TTR("Styleboxes"));
+
+ item_list = &tree_stylebox_items;
+ stylebox_amount += filtered_names.size();
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ break; // Can't happen, but silences warning.
+ }
+
+ bool data_type_any_checked = false;
+ bool data_type_any_checked_with_data = false;
+
+ filtered_names.sort_custom<StringName::AlphCompare>();
+ for (List<StringName>::Element *F = filtered_names.front(); F; F = F->next()) {
+ TreeItem *item_node = import_items_tree->create_item(data_type_node);
+ item_node->set_meta("_can_be_imported", true);
+ item_node->set_text(0, F->get());
+ item_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
+ item_node->set_checked(IMPORT_ITEM, false);
+ item_node->set_editable(IMPORT_ITEM, true);
+ item_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK);
+ item_node->set_checked(IMPORT_ITEM_DATA, false);
+ item_node->set_editable(IMPORT_ITEM_DATA, true);
+
+ _restore_selected_item(item_node);
+ if (item_node->is_checked(IMPORT_ITEM)) {
+ data_type_any_checked = true;
+ any_checked = true;
+ }
+ if (item_node->is_checked(IMPORT_ITEM_DATA)) {
+ data_type_any_checked_with_data = true;
+ any_checked_with_data = true;
+ }
+
+ item_list->push_back(item_node);
+ }
+
+ data_type_node->set_checked(IMPORT_ITEM, data_type_any_checked);
+ data_type_node->set_checked(IMPORT_ITEM_DATA, data_type_any_checked && data_type_any_checked_with_data);
+ }
+
+ // Remove the item if it doesn't match the filter in any way.
+ if (!is_matching_filter && !has_filtered_items) {
+ root->remove_child(type_node);
+ memdelete(type_node);
+ continue;
+ }
+
+ // Show one level inside of a type group if there are matches in items.
+ if (!filter_text.is_empty() && has_filtered_items) {
+ type_node->set_collapsed(false);
+ }
+
+ type_node->set_checked(IMPORT_ITEM, any_checked);
+ type_node->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data);
+ }
+
+ if (color_amount > 0) {
+ Array arr;
+ arr.push_back(color_amount);
+ select_colors_label->set_text(TTRN("One color", "{num} colors", color_amount).format(arr, "{num}"));
+ select_all_colors_button->set_visible(true);
+ select_full_colors_button->set_visible(true);
+ deselect_all_colors_button->set_visible(true);
+ } else {
+ select_colors_label->set_text(TTR("No colors found."));
+ select_all_colors_button->set_visible(false);
+ select_full_colors_button->set_visible(false);
+ deselect_all_colors_button->set_visible(false);
+ }
+
+ if (constant_amount > 0) {
+ Array arr;
+ arr.push_back(constant_amount);
+ select_constants_label->set_text(TTRN("One constant", "{num} constants", constant_amount).format(arr, "{num}"));
+ select_all_constants_button->set_visible(true);
+ select_full_constants_button->set_visible(true);
+ deselect_all_constants_button->set_visible(true);
+ } else {
+ select_constants_label->set_text(TTR("No constants found."));
+ select_all_constants_button->set_visible(false);
+ select_full_constants_button->set_visible(false);
+ deselect_all_constants_button->set_visible(false);
+ }
+
+ if (font_amount > 0) {
+ Array arr;
+ arr.push_back(font_amount);
+ select_fonts_label->set_text(TTRN("One font", "{num} fonts", font_amount).format(arr, "{num}"));
+ select_all_fonts_button->set_visible(true);
+ select_full_fonts_button->set_visible(true);
+ deselect_all_fonts_button->set_visible(true);
+ } else {
+ select_fonts_label->set_text(TTR("No fonts found."));
+ select_all_fonts_button->set_visible(false);
+ select_full_fonts_button->set_visible(false);
+ deselect_all_fonts_button->set_visible(false);
+ }
+
+ if (font_size_amount > 0) {
+ Array arr;
+ arr.push_back(font_size_amount);
+ select_font_sizes_label->set_text(TTRN("One font size", "{num} font sizes", font_size_amount).format(arr, "{num}"));
+ select_all_font_sizes_button->set_visible(true);
+ select_full_font_sizes_button->set_visible(true);
+ deselect_all_font_sizes_button->set_visible(true);
+ } else {
+ select_font_sizes_label->set_text(TTR("No font sizes found."));
+ select_all_font_sizes_button->set_visible(false);
+ select_full_font_sizes_button->set_visible(false);
+ deselect_all_font_sizes_button->set_visible(false);
+ }
+
+ if (icon_amount > 0) {
+ Array arr;
+ arr.push_back(icon_amount);
+ select_icons_label->set_text(TTRN("One icon", "{num} icons", icon_amount).format(arr, "{num}"));
+ select_all_icons_button->set_visible(true);
+ select_full_icons_button->set_visible(true);
+ deselect_all_icons_button->set_visible(true);
+ select_icons_warning_hb->set_visible(true);
+ } else {
+ select_icons_label->set_text(TTR("No icons found."));
+ select_all_icons_button->set_visible(false);
+ select_full_icons_button->set_visible(false);
+ deselect_all_icons_button->set_visible(false);
+ select_icons_warning_hb->set_visible(false);
+ }
+
+ if (stylebox_amount > 0) {
+ Array arr;
+ arr.push_back(stylebox_amount);
+ select_styleboxes_label->set_text(TTRN("One stylebox", "{num} styleboxes", stylebox_amount).format(arr, "{num}"));
+ select_all_styleboxes_button->set_visible(true);
+ select_full_styleboxes_button->set_visible(true);
+ deselect_all_styleboxes_button->set_visible(true);
+ } else {
+ select_styleboxes_label->set_text(TTR("No styleboxes found."));
+ select_all_styleboxes_button->set_visible(false);
+ select_full_styleboxes_button->set_visible(false);
+ deselect_all_styleboxes_button->set_visible(false);
+ }
+}
+
+void ThemeItemImportTree::_toggle_type_items(bool p_collapse) {
+ TreeItem *root = import_items_tree->get_root();
+ if (!root) {
+ return;
+ }
+
+ TreeItem *type_node = root->get_first_child();
+ while (type_node) {
+ type_node->set_collapsed(p_collapse);
+ type_node = type_node->get_next();
+ }
+}
+
+void ThemeItemImportTree::_filter_text_changed(const String &p_value) {
+ _update_items_tree();
+}
+
+void ThemeItemImportTree::_store_selected_item(TreeItem *p_tree_item) {
+ if (!p_tree_item->get_meta("_can_be_imported")) {
+ return;
+ }
+
+ TreeItem *data_type_node = p_tree_item->get_parent();
+ if (!data_type_node || data_type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ TreeItem *type_node = data_type_node->get_parent();
+ if (!type_node || type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ ThemeItem ti;
+ ti.item_name = p_tree_item->get_text(0);
+ ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0);
+ ti.type_name = type_node->get_text(0);
+
+ bool import = p_tree_item->is_checked(IMPORT_ITEM);
+ bool with_data = p_tree_item->is_checked(IMPORT_ITEM_DATA);
+
+ if (import && with_data) {
+ selected_items[ti] = SELECT_IMPORT_FULL;
+ } else if (import) {
+ selected_items[ti] = SELECT_IMPORT_DEFINITION;
+ } else {
+ selected_items.erase(ti);
+ }
+
+ _update_total_selected(ti.data_type);
+}
+
+void ThemeItemImportTree::_restore_selected_item(TreeItem *p_tree_item) {
+ if (!p_tree_item->get_meta("_can_be_imported")) {
+ return;
+ }
+
+ TreeItem *data_type_node = p_tree_item->get_parent();
+ if (!data_type_node || data_type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ TreeItem *type_node = data_type_node->get_parent();
+ if (!type_node || type_node == import_items_tree->get_root()) {
+ return;
+ }
+
+ ThemeItem ti;
+ ti.item_name = p_tree_item->get_text(0);
+ ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0);
+ ti.type_name = type_node->get_text(0);
+
+ if (!selected_items.has(ti)) {
+ p_tree_item->set_checked(IMPORT_ITEM, false);
+ p_tree_item->set_checked(IMPORT_ITEM_DATA, false);
+ return;
+ }
+
+ if (selected_items[ti] == SELECT_IMPORT_FULL) {
+ p_tree_item->set_checked(IMPORT_ITEM, true);
+ p_tree_item->set_checked(IMPORT_ITEM_DATA, true);
+ } else if (selected_items[ti] == SELECT_IMPORT_DEFINITION) {
+ p_tree_item->set_checked(IMPORT_ITEM, true);
+ p_tree_item->set_checked(IMPORT_ITEM_DATA, false);
+ }
+}
+
+void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ Label *total_selected_items_label;
+ switch (p_data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ total_selected_items_label = total_selected_colors_label;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ total_selected_items_label = total_selected_constants_label;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ total_selected_items_label = total_selected_fonts_label;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ total_selected_items_label = total_selected_font_sizes_label;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ total_selected_items_label = total_selected_icons_label;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ total_selected_items_label = total_selected_styleboxes_label;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ if (!total_selected_items_label) {
+ return;
+ }
+
+ int count = 0;
+ for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) {
+ ThemeItem ti = E->key();
+ if (ti.data_type == p_data_type) {
+ count++;
+ }
+ }
+
+ if (count == 0) {
+ total_selected_items_label->hide();
+ } else {
+ Array arr;
+ arr.push_back(count);
+ total_selected_items_label->set_text(TTRN("{num} currently selected", "{num} currently selected", count).format(arr, "{num}"));
+ total_selected_items_label->show();
+ }
+}
+
+void ThemeItemImportTree::_tree_item_edited() {
+ if (updating_tree) {
+ return;
+ }
+
+ TreeItem *edited_item = import_items_tree->get_edited();
+ if (!edited_item) {
+ return;
+ }
+
+ updating_tree = true;
+
+ int edited_column = import_items_tree->get_edited_column();
+ bool is_checked = edited_item->is_checked(edited_column);
+ if (is_checked) {
+ if (edited_column == IMPORT_ITEM_DATA) {
+ edited_item->set_checked(IMPORT_ITEM, true);
+ }
+
+ _select_all_subitems(edited_item, (edited_column == IMPORT_ITEM_DATA));
+ } else {
+ if (edited_column == IMPORT_ITEM) {
+ edited_item->set_checked(IMPORT_ITEM_DATA, false);
+ }
+
+ _deselect_all_subitems(edited_item, (edited_column == IMPORT_ITEM));
+ }
+
+ _update_parent_items(edited_item);
+ _store_selected_item(edited_item);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_all_subitems(TreeItem *p_root_item, bool p_select_with_data) {
+ TreeItem *child_item = p_root_item->get_first_child();
+ while (child_item) {
+ child_item->set_checked(IMPORT_ITEM, true);
+ if (p_select_with_data) {
+ child_item->set_checked(IMPORT_ITEM_DATA, true);
+ }
+ _store_selected_item(child_item);
+
+ _select_all_subitems(child_item, p_select_with_data);
+ child_item = child_item->get_next();
+ }
+}
+
+void ThemeItemImportTree::_deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely) {
+ TreeItem *child_item = p_root_item->get_first_child();
+ while (child_item) {
+ child_item->set_checked(IMPORT_ITEM_DATA, false);
+ if (p_deselect_completely) {
+ child_item->set_checked(IMPORT_ITEM, false);
+ }
+ _store_selected_item(child_item);
+
+ _deselect_all_subitems(child_item, p_deselect_completely);
+ child_item = child_item->get_next();
+ }
+}
+
+void ThemeItemImportTree::_update_parent_items(TreeItem *p_root_item) {
+ TreeItem *parent_item = p_root_item->get_parent();
+ if (!parent_item) {
+ return;
+ }
+
+ bool any_checked = false;
+ bool any_checked_with_data = false;
+
+ TreeItem *child_item = parent_item->get_first_child();
+ while (child_item) {
+ if (child_item->is_checked(IMPORT_ITEM)) {
+ any_checked = true;
+ }
+ if (child_item->is_checked(IMPORT_ITEM_DATA)) {
+ any_checked_with_data = true;
+ }
+
+ child_item = child_item->get_next();
+ }
+
+ parent_item->set_checked(IMPORT_ITEM, any_checked);
+ parent_item->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data);
+ _update_parent_items(parent_item);
+}
+
+void ThemeItemImportTree::_select_all_items_pressed() {
+ if (updating_tree) {
+ return;
+ }
+
+ updating_tree = true;
+
+ TreeItem *root = import_items_tree->get_root();
+ _select_all_subitems(root, false);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_full_items_pressed() {
+ if (updating_tree) {
+ return;
+ }
+
+ updating_tree = true;
+
+ TreeItem *root = import_items_tree->get_root();
+ _select_all_subitems(root, true);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_deselect_all_items_pressed() {
+ if (updating_tree) {
+ return;
+ }
+
+ updating_tree = true;
+
+ TreeItem *root = import_items_tree->get_root();
+ _deselect_all_subitems(root, true);
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ if (updating_tree) {
+ return;
+ }
+
+ Theme::DataType data_type = (Theme::DataType)p_data_type;
+ List<TreeItem *> *item_list;
+
+ switch (data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_list = &tree_color_items;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_list = &tree_constant_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_list = &tree_font_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_list = &tree_font_size_items;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_list = &tree_icon_items;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_list = &tree_stylebox_items;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ updating_tree = true;
+
+ for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
+ TreeItem *child_item = E->get();
+ if (!child_item) {
+ continue;
+ }
+
+ child_item->set_checked(IMPORT_ITEM, true);
+ _update_parent_items(child_item);
+ _store_selected_item(child_item);
+ }
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ if (updating_tree) {
+ return;
+ }
+
+ Theme::DataType data_type = (Theme::DataType)p_data_type;
+ List<TreeItem *> *item_list;
+
+ switch (data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_list = &tree_color_items;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_list = &tree_constant_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_list = &tree_font_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_list = &tree_font_size_items;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_list = &tree_icon_items;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_list = &tree_stylebox_items;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ updating_tree = true;
+
+ for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
+ TreeItem *child_item = E->get();
+ if (!child_item) {
+ continue;
+ }
+
+ child_item->set_checked(IMPORT_ITEM, true);
+ child_item->set_checked(IMPORT_ITEM_DATA, true);
+ _update_parent_items(child_item);
+ _store_selected_item(child_item);
+ }
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) {
+ ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds.");
+
+ if (updating_tree) {
+ return;
+ }
+
+ Theme::DataType data_type = (Theme::DataType)p_data_type;
+ List<TreeItem *> *item_list;
+
+ switch (data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_list = &tree_color_items;
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_list = &tree_constant_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_list = &tree_font_items;
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_list = &tree_font_size_items;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_list = &tree_icon_items;
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_list = &tree_stylebox_items;
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ return; // Can't happen, but silences warning.
+ }
+
+ updating_tree = true;
+
+ for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) {
+ TreeItem *child_item = E->get();
+ if (!child_item) {
+ continue;
+ }
+
+ child_item->set_checked(IMPORT_ITEM, false);
+ child_item->set_checked(IMPORT_ITEM_DATA, false);
+ _update_parent_items(child_item);
+ _store_selected_item(child_item);
+ }
+
+ updating_tree = false;
+}
+
+void ThemeItemImportTree::_import_selected() {
+ if (selected_items.size() == 0) {
+ EditorNode::get_singleton()->show_accept(TTR("Nothing was selected for the import."), TTR("OK"));
+ return;
+ }
+
+ // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ edited_theme->_freeze_change_propagation();
+ ProgressDialog::get_singleton()->add_task("import_theme_items", TTR("Importing Theme Items"), selected_items.size() + 2);
+
+ int idx = 0;
+ for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) {
+ // Arbitrary number of items to skip from reporting.
+ // Reduces the number of UI updates that this causes when copying large themes.
+ if (idx % 10 == 0) {
+ Array arr;
+ arr.push_back(idx + 1);
+ arr.push_back(selected_items.size());
+ ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Importing items {n}/{n}").format(arr, "{n}"), idx);
+ }
+
+ ItemCheckedState cs = E->get();
+ ThemeItem ti = E->key();
+
+ if (cs == SELECT_IMPORT_DEFINITION || cs == SELECT_IMPORT_FULL) {
+ Variant item_value = Variant();
+
+ if (cs == SELECT_IMPORT_FULL) {
+ item_value = base_theme->get_theme_item(ti.data_type, ti.item_name, ti.type_name);
+ } else {
+ switch (ti.data_type) {
+ case Theme::DATA_TYPE_COLOR:
+ item_value = Color();
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ item_value = 0;
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ item_value = Ref<Font>();
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ item_value = -1;
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ item_value = Ref<Texture2D>();
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ item_value = Ref<StyleBox>();
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ break; // Can't happen, but silences warning.
+ }
+ }
+
+ edited_theme->set_theme_item(ti.data_type, ti.item_name, ti.type_name, item_value);
+ }
+
+ idx++;
+ }
+
+ // Allow changes to be reported now that the operation is finished.
+ ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Updating the editor"), idx++);
+ edited_theme->_unfreeze_and_propagate_changes();
+ // Make sure the task is not ended before the editor freezes to update the Inspector.
+ ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Finalizing"), idx++);
+
+ ProgressDialog::get_singleton()->end_task("import_theme_items");
+ emit_signal("items_imported");
+}
+
+void ThemeItemImportTree::set_edited_theme(const Ref<Theme> &p_theme) {
+ edited_theme = p_theme;
+}
+
+void ThemeItemImportTree::set_base_theme(const Ref<Theme> &p_theme) {
+ base_theme = p_theme;
+}
+
+void ThemeItemImportTree::reset_item_tree() {
+ import_items_filter->clear();
+ selected_items.clear();
+
+ total_selected_colors_label->hide();
+ total_selected_constants_label->hide();
+ total_selected_fonts_label->hide();
+ total_selected_font_sizes_label->hide();
+ total_selected_icons_label->hide();
+ total_selected_styleboxes_label->hide();
+
+ _update_items_tree();
+}
+
+bool ThemeItemImportTree::has_selected_items() const {
+ return (selected_items.size() > 0);
+}
+
+void ThemeItemImportTree::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ select_icons_warning_icon->set_texture(get_theme_icon("StatusWarning", "EditorIcons"));
+ select_icons_warning->add_theme_color_override("font_color", get_theme_color("disabled_font_color", "Editor"));
+
+ // Bottom panel buttons.
+ import_collapse_types_button->set_icon(get_theme_icon("CollapseTree", "EditorIcons"));
+ import_expand_types_button->set_icon(get_theme_icon("ExpandTree", "EditorIcons"));
+
+ import_select_all_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ import_select_full_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+ import_deselect_all_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+
+ // Side panel buttons.
+ select_colors_icon->set_texture(get_theme_icon("Color", "EditorIcons"));
+ deselect_all_colors_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_colors_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_colors_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_constants_icon->set_texture(get_theme_icon("MemberConstant", "EditorIcons"));
+ deselect_all_constants_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_constants_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_constants_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_fonts_icon->set_texture(get_theme_icon("Font", "EditorIcons"));
+ deselect_all_fonts_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_fonts_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_fonts_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_font_sizes_icon->set_texture(get_theme_icon("FontSize", "EditorIcons"));
+ deselect_all_font_sizes_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_font_sizes_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_font_sizes_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_icons_icon->set_texture(get_theme_icon("ImageTexture", "EditorIcons"));
+ deselect_all_icons_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_icons_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_icons_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+
+ select_styleboxes_icon->set_texture(get_theme_icon("StyleBoxFlat", "EditorIcons"));
+ deselect_all_styleboxes_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons"));
+ select_all_styleboxes_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons"));
+ select_full_styleboxes_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons"));
+ } break;
+ }
+}
+
+void ThemeItemImportTree::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("items_imported"));
+}
+
+ThemeItemImportTree::ThemeItemImportTree() {
+ HBoxContainer *import_items_filter_hb = memnew(HBoxContainer);
+ add_child(import_items_filter_hb);
+ Label *import_items_filter_label = memnew(Label);
+ import_items_filter_label->set_text(TTR("Filter:"));
+ import_items_filter_hb->add_child(import_items_filter_label);
+ import_items_filter = memnew(LineEdit);
+ import_items_filter->set_clear_button_enabled(true);
+ import_items_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_items_filter_hb->add_child(import_items_filter);
+ import_items_filter->connect("text_changed", callable_mp(this, &ThemeItemImportTree::_filter_text_changed));
+
+ HBoxContainer *import_main_hb = memnew(HBoxContainer);
+ import_main_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ add_child(import_main_hb);
+
+ import_items_tree = memnew(Tree);
+ import_items_tree->set_hide_root(true);
+ import_items_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_main_hb->add_child(import_items_tree);
+ import_items_tree->connect("item_edited", callable_mp(this, &ThemeItemImportTree::_tree_item_edited));
+
+ import_items_tree->set_columns(3);
+ import_items_tree->set_column_titles_visible(true);
+ import_items_tree->set_column_title(IMPORT_ITEM, TTR("Import"));
+ import_items_tree->set_column_title(IMPORT_ITEM_DATA, TTR("With Data"));
+ import_items_tree->set_column_expand(0, true);
+ import_items_tree->set_column_clip_content(0, true);
+ import_items_tree->set_column_expand(IMPORT_ITEM, false);
+ import_items_tree->set_column_expand(IMPORT_ITEM_DATA, false);
+ import_items_tree->set_column_custom_minimum_width(0, 160 * EDSCALE);
+ import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM, 80 * EDSCALE);
+ import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM_DATA, 80 * EDSCALE);
+ import_items_tree->set_column_clip_content(1, true);
+ import_items_tree->set_column_clip_content(2, true);
+
+ ScrollContainer *import_bulk_sc = memnew(ScrollContainer);
+ import_bulk_sc->set_custom_minimum_size(Size2(260.0, 0.0) * EDSCALE);
+ import_bulk_sc->set_enable_h_scroll(false);
+ import_main_hb->add_child(import_bulk_sc);
+ VBoxContainer *import_bulk_vb = memnew(VBoxContainer);
+ import_bulk_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_bulk_sc->add_child(import_bulk_vb);
+
+ Label *import_bulk_label = memnew(Label);
+ import_bulk_label->set_text(TTR("Select by data type:"));
+ import_bulk_vb->add_child(import_bulk_label);
+
+ select_colors_icon = memnew(TextureRect);
+ select_colors_label = memnew(Label);
+ deselect_all_colors_button = memnew(Button);
+ select_all_colors_button = memnew(Button);
+ select_full_colors_button = memnew(Button);
+ total_selected_colors_label = memnew(Label);
+
+ select_constants_icon = memnew(TextureRect);
+ select_constants_label = memnew(Label);
+ deselect_all_constants_button = memnew(Button);
+ select_all_constants_button = memnew(Button);
+ select_full_constants_button = memnew(Button);
+ total_selected_constants_label = memnew(Label);
+
+ select_fonts_icon = memnew(TextureRect);
+ select_fonts_label = memnew(Label);
+ deselect_all_fonts_button = memnew(Button);
+ select_all_fonts_button = memnew(Button);
+ select_full_fonts_button = memnew(Button);
+ total_selected_fonts_label = memnew(Label);
+
+ select_font_sizes_icon = memnew(TextureRect);
+ select_font_sizes_label = memnew(Label);
+ deselect_all_font_sizes_button = memnew(Button);
+ select_all_font_sizes_button = memnew(Button);
+ select_full_font_sizes_button = memnew(Button);
+ total_selected_font_sizes_label = memnew(Label);
+
+ select_icons_icon = memnew(TextureRect);
+ select_icons_label = memnew(Label);
+ deselect_all_icons_button = memnew(Button);
+ select_all_icons_button = memnew(Button);
+ select_full_icons_button = memnew(Button);
+ total_selected_icons_label = memnew(Label);
+
+ select_styleboxes_icon = memnew(TextureRect);
+ select_styleboxes_label = memnew(Label);
+ deselect_all_styleboxes_button = memnew(Button);
+ select_all_styleboxes_button = memnew(Button);
+ select_full_styleboxes_button = memnew(Button);
+ total_selected_styleboxes_label = memnew(Label);
+
+ for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) {
+ Theme::DataType dt = (Theme::DataType)i;
+
+ TextureRect *select_items_icon;
+ Label *select_items_label;
+ Button *deselect_all_items_button;
+ Button *select_all_items_button;
+ Button *select_full_items_button;
+ Label *total_selected_items_label;
+
+ String items_title = "";
+ String select_all_items_tooltip = "";
+ String select_full_items_tooltip = "";
+ String deselect_all_items_tooltip = "";
+
+ switch (dt) {
+ case Theme::DATA_TYPE_COLOR:
+ select_items_icon = select_colors_icon;
+ select_items_label = select_colors_label;
+ deselect_all_items_button = deselect_all_colors_button;
+ select_all_items_button = select_all_colors_button;
+ select_full_items_button = select_full_colors_button;
+ total_selected_items_label = total_selected_colors_label;
+
+ items_title = TTR("Colors");
+ select_all_items_tooltip = TTR("Select all visible color items.");
+ select_full_items_tooltip = TTR("Select all visible color items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible color items.");
+ break;
+
+ case Theme::DATA_TYPE_CONSTANT:
+ select_items_icon = select_constants_icon;
+ select_items_label = select_constants_label;
+ deselect_all_items_button = deselect_all_constants_button;
+ select_all_items_button = select_all_constants_button;
+ select_full_items_button = select_full_constants_button;
+ total_selected_items_label = total_selected_constants_label;
+
+ items_title = TTR("Constants");
+ select_all_items_tooltip = TTR("Select all visible constant items.");
+ select_full_items_tooltip = TTR("Select all visible constant items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible constant items.");
+ break;
+
+ case Theme::DATA_TYPE_FONT:
+ select_items_icon = select_fonts_icon;
+ select_items_label = select_fonts_label;
+ deselect_all_items_button = deselect_all_fonts_button;
+ select_all_items_button = select_all_fonts_button;
+ select_full_items_button = select_full_fonts_button;
+ total_selected_items_label = total_selected_fonts_label;
+
+ items_title = TTR("Fonts");
+ select_all_items_tooltip = TTR("Select all visible font items.");
+ select_full_items_tooltip = TTR("Select all visible font items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible font items.");
+ break;
+
+ case Theme::DATA_TYPE_FONT_SIZE:
+ select_items_icon = select_font_sizes_icon;
+ select_items_label = select_font_sizes_label;
+ deselect_all_items_button = deselect_all_font_sizes_button;
+ select_all_items_button = select_all_font_sizes_button;
+ select_full_items_button = select_full_font_sizes_button;
+ total_selected_items_label = total_selected_font_sizes_label;
+
+ items_title = TTR("Font sizes");
+ select_all_items_tooltip = TTR("Select all visible font size items.");
+ select_full_items_tooltip = TTR("Select all visible font size items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible font size items.");
+ break;
+
+ case Theme::DATA_TYPE_ICON:
+ select_items_icon = select_icons_icon;
+ select_items_label = select_icons_label;
+ deselect_all_items_button = deselect_all_icons_button;
+ select_all_items_button = select_all_icons_button;
+ select_full_items_button = select_full_icons_button;
+ total_selected_items_label = total_selected_icons_label;
+
+ items_title = TTR("Icons");
+ select_all_items_tooltip = TTR("Select all visible icon items.");
+ select_full_items_tooltip = TTR("Select all visible icon items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible icon items.");
+ break;
+
+ case Theme::DATA_TYPE_STYLEBOX:
+ select_items_icon = select_styleboxes_icon;
+ select_items_label = select_styleboxes_label;
+ deselect_all_items_button = deselect_all_styleboxes_button;
+ select_all_items_button = select_all_styleboxes_button;
+ select_full_items_button = select_full_styleboxes_button;
+ total_selected_items_label = total_selected_styleboxes_label;
+
+ items_title = TTR("Styleboxes");
+ select_all_items_tooltip = TTR("Select all visible stylebox items.");
+ select_full_items_tooltip = TTR("Select all visible stylebox items and their data.");
+ deselect_all_items_tooltip = TTR("Deselect all visible stylebox items.");
+ break;
+
+ case Theme::DATA_TYPE_MAX:
+ continue; // Can't happen, but silences warning.
+ }
+
+ if (i > 0) {
+ import_bulk_vb->add_child(memnew(HSeparator));
+ }
+
+ HBoxContainer *all_set = memnew(HBoxContainer);
+ import_bulk_vb->add_child(all_set);
+
+ HBoxContainer *label_set = memnew(HBoxContainer);
+ label_set->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ all_set->add_child(label_set);
+ select_items_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ label_set->add_child(select_items_icon);
+ select_items_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ select_items_label->set_clip_text(true);
+ select_items_label->set_text(items_title);
+ label_set->add_child(select_items_label);
+
+ HBoxContainer *button_set = memnew(HBoxContainer);
+ button_set->set_alignment(BoxContainer::ALIGN_END);
+ all_set->add_child(button_set);
+ select_all_items_button->set_flat(true);
+ select_all_items_button->set_tooltip(select_all_items_tooltip);
+ button_set->add_child(select_all_items_button);
+ select_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_data_type_pressed), varray(i));
+ select_full_items_button->set_flat(true);
+ select_full_items_button->set_tooltip(select_full_items_tooltip);
+ button_set->add_child(select_full_items_button);
+ select_full_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_data_type_pressed), varray(i));
+ deselect_all_items_button->set_flat(true);
+ deselect_all_items_button->set_tooltip(deselect_all_items_tooltip);
+ button_set->add_child(deselect_all_items_button);
+ deselect_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_data_type_pressed), varray(i));
+
+ total_selected_items_label->set_align(Label::ALIGN_RIGHT);
+ total_selected_items_label->hide();
+ import_bulk_vb->add_child(total_selected_items_label);
+
+ if (dt == Theme::DATA_TYPE_ICON) {
+ select_icons_warning_hb = memnew(HBoxContainer);
+ import_bulk_vb->add_child(select_icons_warning_hb);
+
+ select_icons_warning_icon = memnew(TextureRect);
+ select_icons_warning_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ select_icons_warning_hb->add_child(select_icons_warning_icon);
+
+ select_icons_warning = memnew(Label);
+ select_icons_warning->set_text(TTR("Caution: Adding icon data may considerably increase the size of your Theme resource."));
+ select_icons_warning->set_autowrap(true);
+ select_icons_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ select_icons_warning_hb->add_child(select_icons_warning);
+ }
+ }
+
+ add_child(memnew(HSeparator));
+
+ HBoxContainer *import_buttons = memnew(HBoxContainer);
+ add_child(import_buttons);
+
+ import_collapse_types_button = memnew(Button);
+ import_collapse_types_button->set_flat(true);
+ import_collapse_types_button->set_tooltip(TTR("Collapse types."));
+ import_buttons->add_child(import_collapse_types_button);
+ import_collapse_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(true));
+ import_expand_types_button = memnew(Button);
+ import_expand_types_button->set_flat(true);
+ import_expand_types_button->set_tooltip(TTR("Expand types."));
+ import_buttons->add_child(import_expand_types_button);
+ import_expand_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(false));
+
+ import_buttons->add_child(memnew(VSeparator));
+
+ import_select_all_button = memnew(Button);
+ import_select_all_button->set_flat(true);
+ import_select_all_button->set_text(TTR("Select All"));
+ import_select_all_button->set_tooltip(TTR("Select all Theme items."));
+ import_buttons->add_child(import_select_all_button);
+ import_select_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_items_pressed));
+ import_select_full_button = memnew(Button);
+ import_select_full_button->set_flat(true);
+ import_select_full_button->set_text(TTR("Select With Data"));
+ import_select_full_button->set_tooltip(TTR("Select all Theme items with item data."));
+ import_buttons->add_child(import_select_full_button);
+ import_select_full_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_items_pressed));
+ import_deselect_all_button = memnew(Button);
+ import_deselect_all_button->set_flat(true);
+ import_deselect_all_button->set_text(TTR("Deselect All"));
+ import_deselect_all_button->set_tooltip(TTR("Deselect all Theme items."));
+ import_buttons->add_child(import_deselect_all_button);
+ import_deselect_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_items_pressed));
+
+ import_buttons->add_spacer();
+
+ Button *import_add_selected_button = memnew(Button);
+ import_add_selected_button->set_text(TTR("Import Selected"));
+ import_buttons->add_child(import_add_selected_button);
+ import_add_selected_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_import_selected));
+}
+
+void ThemeItemEditorDialog::ok_pressed() {
+ if (import_default_theme_items->has_selected_items() || import_editor_theme_items->has_selected_items() || import_other_theme_items->has_selected_items()) {
+ confirm_closing_dialog->set_text(TTR("Import Items tab has some items selected. Selection will be lost upon closing this window.\nClose anyway?"));
+ confirm_closing_dialog->popup_centered(Size2i(380, 120) * EDSCALE);
+ return;
+ }
+
+ hide();
+}
+
+void ThemeItemEditorDialog::_close_dialog() {
+ hide();
+}
void ThemeItemEditorDialog::_dialog_about_to_show() {
- ERR_FAIL_COND(edited_theme.is_null());
+ ERR_FAIL_COND_MSG(edited_theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing.");
_update_edit_types();
+
+ import_default_theme_items->set_edited_theme(edited_theme);
+ import_default_theme_items->set_base_theme(Theme::get_default());
+ import_default_theme_items->reset_item_tree();
+
+ import_editor_theme_items->set_edited_theme(edited_theme);
+ import_editor_theme_items->set_base_theme(EditorNode::get_singleton()->get_theme_base()->get_theme());
+ import_editor_theme_items->reset_item_tree();
+
+ import_other_theme_items->set_edited_theme(edited_theme);
+ import_other_theme_items->reset_item_tree();
}
void ThemeItemEditorDialog::_update_edit_types() {
@@ -79,11 +1263,6 @@ void ThemeItemEditorDialog::_update_edit_types() {
base_theme->get_type_list(&default_types);
default_types.sort_custom<StringName::AlphCompare>();
- edit_add_class_options->clear();
- for (List<StringName>::Element *E = default_types.front(); E; E = E->next()) {
- edit_add_class_options->add_item(E->get());
- }
-
String selected_type = "";
Vector<int> selected_ids = edit_type_list->get_selected_items();
if (selected_ids.size() > 0) {
@@ -127,7 +1306,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
List<StringName> names;
- {
+ { // Colors.
names.clear();
edited_theme->get_color_list(p_item_type, &names);
@@ -148,7 +1327,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Constants.
names.clear();
edited_theme->get_constant_list(p_item_type, &names);
@@ -169,7 +1348,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Fonts.
names.clear();
edited_theme->get_font_list(p_item_type, &names);
@@ -190,7 +1369,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Font sizes.
names.clear();
edited_theme->get_font_size_list(p_item_type, &names);
@@ -211,7 +1390,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Icons.
names.clear();
edited_theme->get_icon_list(p_item_type, &names);
@@ -232,7 +1411,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
}
}
- {
+ { // Styleboxes.
names.clear();
edited_theme->get_stylebox_list(p_item_type, &names);
@@ -280,65 +1459,17 @@ void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_colu
_update_edit_item_tree(edited_item_type);
}
-void ThemeItemEditorDialog::_add_class_type_items() {
- int selected_idx = edit_add_class_options->get_selected();
- String type_name = edit_add_class_options->get_item_text(selected_idx);
- List<StringName> names;
-
- {
- names.clear();
- Theme::get_default()->get_icon_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_icon(E->get(), type_name, Ref<Texture2D>());
- }
- }
- {
- names.clear();
- Theme::get_default()->get_stylebox_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_stylebox(E->get(), type_name, Ref<StyleBox>());
- }
- }
- {
- names.clear();
- Theme::get_default()->get_font_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_font(E->get(), type_name, Ref<Font>());
- }
- }
- {
- names.clear();
- Theme::get_default()->get_font_size_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_font_size(E->get(), type_name, Theme::get_default()->get_font_size(E->get(), type_name));
- }
- }
- {
- names.clear();
- Theme::get_default()->get_color_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_color(E->get(), type_name, Theme::get_default()->get_color(E->get(), type_name));
- }
- }
- {
- names.clear();
- Theme::get_default()->get_constant_list(type_name, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->set_constant(E->get(), type_name, Theme::get_default()->get_constant(E->get(), type_name));
- }
- }
-
+void ThemeItemEditorDialog::_add_theme_type() {
+ edited_theme->add_icon_type(edit_add_type_value->get_text());
+ edited_theme->add_stylebox_type(edit_add_type_value->get_text());
+ edited_theme->add_font_type(edit_add_type_value->get_text());
+ edited_theme->add_font_size_type(edit_add_type_value->get_text());
+ edited_theme->add_color_type(edit_add_type_value->get_text());
+ edited_theme->add_constant_type(edit_add_type_value->get_text());
_update_edit_types();
-}
-void ThemeItemEditorDialog::_add_custom_type() {
- edited_theme->add_icon_type(edit_add_custom_value->get_text());
- edited_theme->add_stylebox_type(edit_add_custom_value->get_text());
- edited_theme->add_font_type(edit_add_custom_value->get_text());
- edited_theme->add_font_size_type(edit_add_custom_value->get_text());
- edited_theme->add_color_type(edit_add_custom_value->get_text());
- edited_theme->add_constant_type(edit_add_custom_value->get_text());
- _update_edit_types();
+ // Force emit a change so that other parts of the editor can update.
+ edited_theme->emit_changed();
}
void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type) {
@@ -369,15 +1500,24 @@ void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String
void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type, String p_item_type) {
List<StringName> names;
+ // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ edited_theme->_freeze_change_propagation();
+
edited_theme->get_theme_item_list(p_data_type, p_item_type, &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
edited_theme->clear_theme_item(p_data_type, E->get(), p_item_type);
}
+
+ // Allow changes to be reported now that the operation is finished.
+ edited_theme->_unfreeze_and_propagate_changes();
}
void ThemeItemEditorDialog::_remove_class_items() {
List<StringName> names;
+ // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ edited_theme->_freeze_change_propagation();
+
for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
Theme::DataType data_type = (Theme::DataType)dt;
@@ -390,12 +1530,18 @@ void ThemeItemEditorDialog::_remove_class_items() {
}
}
+ // Allow changes to be reported now that the operation is finished.
+ edited_theme->_unfreeze_and_propagate_changes();
+
_update_edit_item_tree(edited_item_type);
}
void ThemeItemEditorDialog::_remove_custom_items() {
List<StringName> names;
+ // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ edited_theme->_freeze_change_propagation();
+
for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
Theme::DataType data_type = (Theme::DataType)dt;
@@ -408,12 +1554,18 @@ void ThemeItemEditorDialog::_remove_custom_items() {
}
}
+ // Allow changes to be reported now that the operation is finished.
+ edited_theme->_unfreeze_and_propagate_changes();
+
_update_edit_item_tree(edited_item_type);
}
void ThemeItemEditorDialog::_remove_all_items() {
List<StringName> names;
+ // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ edited_theme->_freeze_change_propagation();
+
for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
Theme::DataType data_type = (Theme::DataType)dt;
@@ -424,6 +1576,9 @@ void ThemeItemEditorDialog::_remove_all_items() {
}
}
+ // Allow changes to be reported now that the operation is finished.
+ edited_theme->_unfreeze_and_propagate_changes();
+
_update_edit_item_tree(edited_item_type);
}
@@ -536,6 +1691,26 @@ void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_
}
}
+void ThemeItemEditorDialog::_open_select_another_theme() {
+ import_another_theme_dialog->popup_file_dialog();
+}
+
+void ThemeItemEditorDialog::_select_another_theme_cbk(const String &p_path) {
+ Ref<Theme> loaded_theme = ResourceLoader::load(p_path);
+ if (loaded_theme.is_null()) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid file, not a Theme resource."));
+ return;
+ }
+ if (loaded_theme == edited_theme) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid file, same as the edited Theme resource."));
+ return;
+ }
+
+ import_another_theme_value->set_text(p_path);
+ import_other_theme_items->set_base_theme(loaded_theme);
+ import_other_theme_items->reset_item_tree();
+}
+
void ThemeItemEditorDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -553,6 +1728,11 @@ void ThemeItemEditorDialog::_notification(int p_what) {
edit_items_remove_class->set_icon(get_theme_icon("Control", "EditorIcons"));
edit_items_remove_custom->set_icon(get_theme_icon("ThemeRemoveCustomItems", "EditorIcons"));
edit_items_remove_all->set_icon(get_theme_icon("ThemeRemoveAllItems", "EditorIcons"));
+
+ import_another_theme_button->set_icon(get_theme_icon("Folder", "EditorIcons"));
+
+ tc->add_theme_style_override("tab_selected", get_theme_stylebox("tab_selected_odd", "TabContainer"));
+ tc->add_theme_style_override("panel", get_theme_stylebox("panel_odd", "TabContainer"));
} break;
}
}
@@ -562,10 +1742,18 @@ void ThemeItemEditorDialog::set_edited_theme(const Ref<Theme> &p_theme) {
}
ThemeItemEditorDialog::ThemeItemEditorDialog() {
- set_title(TTR("Edit Theme Items"));
+ set_title(TTR("Manage Theme Items"));
+ get_ok_button()->set_text(TTR("Close"));
+ set_hide_on_ok(false); // Closing may require a confirmation in some cases.
+
+ tc = memnew(TabContainer);
+ tc->set_tab_align(TabContainer::TabAlign::ALIGN_LEFT);
+ add_child(tc);
+ // Edit Items tab.
HSplitContainer *edit_dialog_hs = memnew(HSplitContainer);
- add_child(edit_dialog_hs);
+ tc->add_child(edit_dialog_hs);
+ tc->set_tab_title(0, TTR("Edit Items"));
VBoxContainer *edit_dialog_side_vb = memnew(VBoxContainer);
edit_dialog_side_vb->set_custom_minimum_size(Size2(200.0, 0.0) * EDSCALE);
@@ -580,33 +1768,19 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_dialog_side_vb->add_child(edit_type_list);
edit_type_list->connect("item_selected", callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected));
- Label *edit_add_class_label = memnew(Label);
- edit_add_class_label->set_text(TTR("Add Type from Class:"));
- edit_dialog_side_vb->add_child(edit_add_class_label);
-
- HBoxContainer *edit_add_class = memnew(HBoxContainer);
- edit_dialog_side_vb->add_child(edit_add_class);
- edit_add_class_options = memnew(OptionButton);
- edit_add_class_options->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- edit_add_class->add_child(edit_add_class_options);
- Button *edit_add_class_button = memnew(Button);
- edit_add_class_button->set_text(TTR("Add"));
- edit_add_class->add_child(edit_add_class_button);
- edit_add_class_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_class_type_items));
-
- Label *edit_add_custom_label = memnew(Label);
- edit_add_custom_label->set_text(TTR("Add Custom Type:"));
- edit_dialog_side_vb->add_child(edit_add_custom_label);
-
- HBoxContainer *edit_add_custom = memnew(HBoxContainer);
- edit_dialog_side_vb->add_child(edit_add_custom);
- edit_add_custom_value = memnew(LineEdit);
- edit_add_custom_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- edit_add_custom->add_child(edit_add_custom_value);
- Button *edit_add_custom_button = memnew(Button);
- edit_add_custom_button->set_text(TTR("Add"));
- edit_add_custom->add_child(edit_add_custom_button);
- edit_add_custom_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_custom_type));
+ Label *edit_add_type_label = memnew(Label);
+ edit_add_type_label->set_text(TTR("Add Type:"));
+ edit_dialog_side_vb->add_child(edit_add_type_label);
+
+ HBoxContainer *edit_add_type_hb = memnew(HBoxContainer);
+ edit_dialog_side_vb->add_child(edit_add_type_hb);
+ edit_add_type_value = memnew(LineEdit);
+ edit_add_type_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ edit_add_type_hb->add_child(edit_add_type_value);
+ Button *edit_add_type_button = memnew(Button);
+ edit_add_type_button->set_text(TTR("Add"));
+ edit_add_type_hb->add_child(edit_add_type_button);
+ edit_add_type_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type));
VBoxContainer *edit_items_vb = memnew(VBoxContainer);
edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -616,7 +1790,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_items_vb->add_child(edit_items_toolbar);
Label *edit_items_toolbar_add_label = memnew(Label);
- edit_items_toolbar_add_label->set_text(TTR("Add:"));
+ edit_items_toolbar_add_label->set_text(TTR("Add Item:"));
edit_items_toolbar->add_child(edit_items_toolbar_add_label);
edit_items_add_color = memnew(Button);
@@ -664,7 +1838,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_items_toolbar->add_child(memnew(VSeparator));
Label *edit_items_toolbar_remove_label = memnew(Label);
- edit_items_toolbar_remove_label->set_text(TTR("Remove:"));
+ edit_items_toolbar_remove_label->set_text(TTR("Remove Items:"));
edit_items_toolbar->add_child(edit_items_toolbar_remove_label);
edit_items_remove_class = memnew(Button);
@@ -716,576 +1890,1376 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
edit_theme_item_vb->add_child(theme_item_name);
theme_item_name->connect("gui_input", callable_mp(this, &ThemeItemEditorDialog::_edit_theme_item_gui_input));
edit_theme_item_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_confirm_edit_theme_item));
-}
-void ThemeEditor::edit(const Ref<Theme> &p_theme) {
- theme = p_theme;
- theme_edit_dialog->set_edited_theme(p_theme);
- main_panel->set_theme(p_theme);
- main_container->set_theme(p_theme);
-}
-
-void ThemeEditor::_propagate_redraw(Control *p_at) {
- p_at->notification(NOTIFICATION_THEME_CHANGED);
- p_at->minimum_size_changed();
- p_at->update();
- for (int i = 0; i < p_at->get_child_count(); i++) {
- Control *a = Object::cast_to<Control>(p_at->get_child(i));
- if (a) {
- _propagate_redraw(a);
- }
+ // Import Items tab.
+ TabContainer *import_tc = memnew(TabContainer);
+ tc->add_child(import_tc);
+ tc->set_tab_title(1, TTR("Import Items"));
+
+ import_default_theme_items = memnew(ThemeItemImportTree);
+ import_tc->add_child(import_default_theme_items);
+ import_tc->set_tab_title(0, TTR("Default Theme"));
+ import_default_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
+
+ import_editor_theme_items = memnew(ThemeItemImportTree);
+ import_tc->add_child(import_editor_theme_items);
+ import_tc->set_tab_title(1, TTR("Editor Theme"));
+ import_editor_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
+
+ VBoxContainer *import_another_theme_vb = memnew(VBoxContainer);
+
+ HBoxContainer *import_another_file_hb = memnew(HBoxContainer);
+ import_another_theme_vb->add_child(import_another_file_hb);
+ import_another_theme_value = memnew(LineEdit);
+ import_another_theme_value->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ import_another_theme_value->set_editable(false);
+ import_another_file_hb->add_child(import_another_theme_value);
+ import_another_theme_button = memnew(Button);
+ import_another_file_hb->add_child(import_another_theme_button);
+ import_another_theme_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_select_another_theme));
+
+ import_another_theme_dialog = memnew(EditorFileDialog);
+ import_another_theme_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ import_another_theme_dialog->set_title(TTR("Select Another Theme Resource:"));
+ List<String> ext;
+ ResourceLoader::get_recognized_extensions_for_type("Theme", &ext);
+ for (List<String>::Element *E = ext.front(); E; E = E->next()) {
+ import_another_theme_dialog->add_filter("*." + E->get() + "; Theme Resource");
}
+ import_another_file_hb->add_child(import_another_theme_dialog);
+ import_another_theme_dialog->connect("file_selected", callable_mp(this, &ThemeItemEditorDialog::_select_another_theme_cbk));
+
+ import_other_theme_items = memnew(ThemeItemImportTree);
+ import_other_theme_items->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ import_another_theme_vb->add_child(import_other_theme_items);
+
+ import_tc->add_child(import_another_theme_vb);
+ import_tc->set_tab_title(2, TTR("Another Theme"));
+ import_other_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types));
+
+ confirm_closing_dialog = memnew(ConfirmationDialog);
+ confirm_closing_dialog->set_autowrap(true);
+ add_child(confirm_closing_dialog);
+ confirm_closing_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_close_dialog));
}
-void ThemeEditor::_refresh_interval() {
- _propagate_redraw(main_panel);
- _propagate_redraw(main_container);
+VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) {
+ VBoxContainer *items_tab = memnew(VBoxContainer);
+ items_tab->set_custom_minimum_size(Size2(0, 160) * EDSCALE);
+ data_type_tabs->add_child(items_tab);
+ data_type_tabs->set_tab_title(data_type_tabs->get_tab_count() - 1, "");
+
+ ScrollContainer *items_sc = memnew(ScrollContainer);
+ items_sc->set_v_size_flags(SIZE_EXPAND_FILL);
+ items_sc->set_enable_h_scroll(false);
+ items_tab->add_child(items_sc);
+ VBoxContainer *items_list = memnew(VBoxContainer);
+ items_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ items_sc->add_child(items_list);
+
+ HBoxContainer *item_add_hb = memnew(HBoxContainer);
+ items_tab->add_child(item_add_hb);
+ LineEdit *item_add_edit = memnew(LineEdit);
+ item_add_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_add_hb->add_child(item_add_edit);
+ item_add_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_add_lineedit_cbk), varray(p_data_type, item_add_edit));
+ Button *item_add_button = memnew(Button);
+ item_add_button->set_text(TTR("Add"));
+ item_add_hb->add_child(item_add_button);
+ item_add_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_add_cbk), varray(p_data_type, item_add_edit));
+
+ return items_list;
}
-struct _TECategory {
- template <class T>
- struct RefItem {
- Ref<T> item;
- StringName name;
- bool operator<(const RefItem<T> &p) const { return item->get_instance_id() < p.item->get_instance_id(); }
- };
+void ThemeTypeEditor::_update_type_list() {
+ ERR_FAIL_COND(edited_theme.is_null());
- template <class T>
- struct Item {
- T item;
- String name;
- bool operator<(const Item<T> &p) const { return name < p.name; }
- };
-
- Set<RefItem<StyleBox>> stylebox_items;
- Set<RefItem<Font>> font_items;
- Set<Item<int>> font_size_items;
- Set<RefItem<Texture2D>> icon_items;
-
- Set<Item<Color>> color_items;
- Set<Item<int>> constant_items;
-};
-
-void ThemeEditor::_save_template_cbk(String fname) {
- String filename = file_dialog->get_current_path();
-
- Map<String, _TECategory> categories;
-
- // Fill types.
- List<StringName> type_list;
- Theme::get_default()->get_type_list(&type_list);
- for (List<StringName>::Element *E = type_list.front(); E; E = E->next()) {
- categories.insert(E->get(), _TECategory());
- }
-
- // Fill default theme.
- for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) {
- _TECategory &tc = E->get();
-
- List<StringName> stylebox_list;
- Theme::get_default()->get_stylebox_list(E->key(), &stylebox_list);
- for (List<StringName>::Element *F = stylebox_list.front(); F; F = F->next()) {
- _TECategory::RefItem<StyleBox> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_stylebox(F->get(), E->key());
- tc.stylebox_items.insert(it);
- }
-
- List<StringName> font_list;
- Theme::get_default()->get_font_list(E->key(), &font_list);
- for (List<StringName>::Element *F = font_list.front(); F; F = F->next()) {
- _TECategory::RefItem<Font> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_font(F->get(), E->key());
- tc.font_items.insert(it);
- }
-
- List<StringName> font_size_list;
- Theme::get_default()->get_font_size_list(E->key(), &font_list);
- for (List<StringName>::Element *F = font_size_list.front(); F; F = F->next()) {
- _TECategory::Item<int> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_font_size(F->get(), E->key());
- tc.font_size_items.insert(it);
- }
-
- List<StringName> icon_list;
- Theme::get_default()->get_icon_list(E->key(), &icon_list);
- for (List<StringName>::Element *F = icon_list.front(); F; F = F->next()) {
- _TECategory::RefItem<Texture2D> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_icon(F->get(), E->key());
- tc.icon_items.insert(it);
- }
-
- List<StringName> color_list;
- Theme::get_default()->get_color_list(E->key(), &color_list);
- for (List<StringName>::Element *F = color_list.front(); F; F = F->next()) {
- _TECategory::Item<Color> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_color(F->get(), E->key());
- tc.color_items.insert(it);
- }
-
- List<StringName> constant_list;
- Theme::get_default()->get_constant_list(E->key(), &constant_list);
- for (List<StringName>::Element *F = constant_list.front(); F; F = F->next()) {
- _TECategory::Item<int> it;
- it.name = F->get();
- it.item = Theme::get_default()->get_constant(F->get(), E->key());
- tc.constant_items.insert(it);
- }
- }
-
- FileAccess *file = FileAccess::open(filename, FileAccess::WRITE);
-
- ERR_FAIL_COND_MSG(!file, "Can't save theme to file '" + filename + "'.");
-
- file->store_line("; ******************* ");
- file->store_line("; Template Theme File ");
- file->store_line("; ******************* ");
- file->store_line("; ");
- file->store_line("; Theme Syntax: ");
- file->store_line("; ------------- ");
- file->store_line("; ");
- file->store_line("; Must be placed in section [theme]");
- file->store_line("; ");
- file->store_line("; Type.item = [value] ");
- file->store_line("; ");
- file->store_line("; [value] examples:");
- file->store_line("; ");
- file->store_line("; Type.item = 6 ; numeric constant. ");
- file->store_line("; Type.item = #FF00FF ; HTML color (magenta).");
- file->store_line("; Type.item = #FF00FF55 ; HTML color (magenta with alpha 0x55).");
- file->store_line("; Type.item = icon(image.png) ; icon in a png file (relative to theme file).");
- file->store_line("; Type.item = font(font.xres) ; font in a resource (relative to theme file).");
- file->store_line("; Type.item = sbox(stylebox.xres) ; stylebox in a resource (relative to theme file).");
- file->store_line("; Type.item = sboxf(2,#FF00FF) ; flat stylebox with margin 2.");
- file->store_line("; Type.item = sboxf(2,#FF00FF,#FFFFFF) ; flat stylebox with margin 2 and border.");
- file->store_line("; Type.item = sboxf(2,#FF00FF,#FFFFFF,#000000) ; flat stylebox with margin 2, light & dark borders.");
- file->store_line("; Type.item = sboxt(base.png,2,2,2,2) ; textured stylebox with 3x3 stretch and stretch margins.");
- file->store_line("; -Additionally, 4 extra integers can be added to sboxf and sboxt to specify custom padding of contents:");
- file->store_line("; Type.item = sboxt(base.png,2,2,2,2,5,4,2,4) ;");
- file->store_line("; -Order for all is always left, top, right, bottom.");
- file->store_line("; ");
- file->store_line("; Special values:");
- file->store_line("; Type.item = default ; use the value in the default theme (must exist there).");
- file->store_line("; Type.item = @somebutton_color ; reference to a library value previously defined.");
- file->store_line("; ");
- file->store_line("; Library Syntax: ");
- file->store_line("; --------------- ");
- file->store_line("; ");
- file->store_line("; Must be placed in section [library], but usage is optional.");
- file->store_line("; ");
- file->store_line("; item = [value] ; same as Theme, but assign to library.");
- file->store_line("; ");
- file->store_line("; examples:");
- file->store_line("; ");
- file->store_line("; [library]");
- file->store_line("; ");
- file->store_line("; default_button_color = #FF00FF");
- file->store_line("; ");
- file->store_line("; [theme]");
- file->store_line("; ");
- file->store_line("; Button.color = @default_button_color ; used reference.");
- file->store_line("; ");
- file->store_line("; ******************* ");
- file->store_line("; ");
- file->store_line("; Template Generated Using: " + String(VERSION_FULL_BUILD));
- file->store_line("; ");
- file->store_line("; ");
- file->store_line("");
- file->store_line("[library]");
- file->store_line("");
- file->store_line("; place library stuff here");
- file->store_line("");
- file->store_line("[theme]");
- file->store_line("");
- file->store_line("");
+ if (updating) {
+ return;
+ }
+ updating = true;
+
+ Control *focused = get_focus_owner();
+ if (focused) {
+ if (focusables.has(focused)) {
+ // If focus is currently on one of the internal property editors, don't update.
+ updating = false;
+ return;
+ }
- // Write default theme.
- for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) {
- _TECategory &tc = E->get();
+ Node *focus_parent = focused->get_parent();
+ while (focus_parent) {
+ Control *c = Object::cast_to<Control>(focus_parent);
+ if (c && focusables.has(c)) {
+ // If focus is currently on one of the internal property editors, don't update.
+ updating = false;
+ return;
+ }
- String underline = "; ";
- for (int i = 0; i < E->key().length(); i++) {
- underline += "*";
+ focus_parent = focus_parent->get_parent();
}
+ }
+
+ List<StringName> theme_types;
+ edited_theme->get_type_list(&theme_types);
+ theme_types.sort_custom<StringName::AlphCompare>();
+
+ theme_type_list->clear();
- file->store_line("");
- file->store_line(underline);
- file->store_line("; " + E->key());
- file->store_line(underline);
+ if (theme_types.size() > 0) {
+ theme_type_list->set_disabled(false);
- if (tc.stylebox_items.size()) {
- file->store_line("\n; StyleBox Items:\n");
+ bool item_reselected = false;
+ int e_idx = 0;
+ for (List<StringName>::Element *E = theme_types.front(); E; E = E->next()) {
+ Ref<Texture2D> item_icon;
+ if (E->get() == "") {
+ item_icon = get_theme_icon("NodeDisabled", "EditorIcons");
+ } else {
+ item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled");
+ }
+ theme_type_list->add_icon_item(item_icon, E->get());
+
+ if (E->get() == edited_type) {
+ theme_type_list->select(e_idx);
+ item_reselected = true;
+ }
+ e_idx++;
}
- for (Set<_TECategory::RefItem<StyleBox>>::Element *F = tc.stylebox_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
+ if (!item_reselected) {
+ theme_type_list->select(0);
+ _list_type_selected(0);
+ } else {
+ _update_type_items();
}
+ } else {
+ theme_type_list->set_disabled(true);
+ theme_type_list->add_item(TTR("None"));
+
+ edited_type = "";
+ _update_type_items();
+ }
+
+ updating = false;
+}
+
+void ThemeTypeEditor::_update_type_list_debounced() {
+ update_debounce_timer->start();
+}
+
+void ThemeTypeEditor::_update_add_type_options(const String &p_filter) {
+ add_type_options->clear();
- if (tc.font_items.size()) {
- file->store_line("\n; Font Items:\n");
+ List<StringName> names;
+ Theme::get_default()->get_type_list(&names);
+ names.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (!p_filter.is_subsequence_ofi(String(E->get()))) {
+ continue;
}
- for (Set<_TECategory::RefItem<Font>>::Element *F = tc.font_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
+ Ref<Texture2D> item_icon;
+ if (E->get() == "") {
+ item_icon = get_theme_icon("NodeDisabled", "EditorIcons");
+ } else {
+ item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled");
}
- if (tc.font_size_items.size()) {
- file->store_line("\n; Font Size Items:\n");
+ add_type_options->add_item(E->get(), item_icon);
+ }
+}
+
+OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default) {
+ OrderedHashMap<StringName, bool> items;
+ List<StringName> names;
+
+ if (include_default) {
+ names.clear();
+ (Theme::get_default().operator->()->*get_list_func)(p_type_name, &names);
+ names.sort_custom<StringName::AlphCompare>();
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ items[E->get()] = false;
}
+ }
- for (Set<_TECategory::Item<int>>::Element *F = tc.font_size_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
+ {
+ names.clear();
+ (edited_theme.operator->()->*get_list_func)(p_type_name, &names);
+ names.sort_custom<StringName::AlphCompare>();
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ items[E->get()] = true;
}
+ }
+
+ List<StringName> keys;
+ for (OrderedHashMap<StringName, bool>::Element E = items.front(); E; E = E.next()) {
+ keys.push_back(E.key());
+ }
+ keys.sort_custom<StringName::AlphCompare>();
+
+ OrderedHashMap<StringName, bool> ordered_items;
+ for (List<StringName>::Element *E = keys.front(); E; E = E->next()) {
+ ordered_items[E->get()] = items[E->get()];
+ }
- if (tc.icon_items.size()) {
- file->store_line("\n; Icon Items:\n");
+ return ordered_items;
+}
+
+HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable) {
+ HBoxContainer *item_control = memnew(HBoxContainer);
+
+ HBoxContainer *item_name_container = memnew(HBoxContainer);
+ item_name_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_name_container->set_stretch_ratio(2.0);
+ item_control->add_child(item_name_container);
+
+ Label *item_name = memnew(Label);
+ item_name->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_name->set_clip_text(true);
+ item_name->set_text(p_item_name);
+ item_name->set_tooltip(p_item_name);
+ item_name_container->add_child(item_name);
+
+ if (p_editable) {
+ LineEdit *item_name_edit = memnew(LineEdit);
+ item_name_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_name_edit->set_text(p_item_name);
+ item_name_container->add_child(item_name_edit);
+ item_name_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_rename_entered), varray(p_data_type, p_item_name, item_name_container));
+ item_name_edit->hide();
+
+ Button *item_rename_button = memnew(Button);
+ item_rename_button->set_icon(get_theme_icon("Edit", "EditorIcons"));
+ item_rename_button->set_tooltip(TTR("Rename Item"));
+ item_rename_button->set_flat(true);
+ item_name_container->add_child(item_rename_button);
+ item_rename_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_cbk), varray(p_data_type, p_item_name, item_name_container));
+
+ Button *item_remove_button = memnew(Button);
+ item_remove_button->set_icon(get_theme_icon("Remove", "EditorIcons"));
+ item_remove_button->set_tooltip(TTR("Remove Item"));
+ item_remove_button->set_flat(true);
+ item_name_container->add_child(item_remove_button);
+ item_remove_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_remove_cbk), varray(p_data_type, p_item_name));
+
+ Button *item_rename_confirm_button = memnew(Button);
+ item_rename_confirm_button->set_icon(get_theme_icon("ImportCheck", "EditorIcons"));
+ item_rename_confirm_button->set_tooltip(TTR("Confirm Item Rename"));
+ item_rename_confirm_button->set_flat(true);
+ item_name_container->add_child(item_rename_confirm_button);
+ item_rename_confirm_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_confirmed), varray(p_data_type, p_item_name, item_name_container));
+ item_rename_confirm_button->hide();
+
+ Button *item_rename_cancel_button = memnew(Button);
+ item_rename_cancel_button->set_icon(get_theme_icon("ImportFail", "EditorIcons"));
+ item_rename_cancel_button->set_tooltip(TTR("Cancel Item Rename"));
+ item_rename_cancel_button->set_flat(true);
+ item_name_container->add_child(item_rename_cancel_button);
+ item_rename_cancel_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_canceled), varray(p_data_type, p_item_name, item_name_container));
+ item_rename_cancel_button->hide();
+ } else {
+ item_name->add_theme_color_override("font_color", get_theme_color("disabled_font_color", "Editor"));
+
+ Button *item_override_button = memnew(Button);
+ item_override_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+ item_override_button->set_tooltip(TTR("Override Item"));
+ item_override_button->set_flat(true);
+ item_name_container->add_child(item_override_button);
+ item_override_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_override_cbk), varray(p_data_type, p_item_name));
+ }
+
+ return item_control;
+}
+
+void ThemeTypeEditor::_add_focusable(Control *p_control) {
+ focusables.append(p_control);
+}
+
+void ThemeTypeEditor::_update_type_items() {
+ bool show_default = show_default_items_button->is_pressed();
+ List<StringName> names;
+
+ focusables.clear();
+
+ // Colors.
+ {
+ for (int i = color_items_list->get_child_count() - 1; i >= 0; i--) {
+ Node *node = color_items_list->get_child(i);
+ node->queue_delete();
+ color_items_list->remove_child(node);
}
- for (Set<_TECategory::RefItem<Texture2D>>::Element *F = tc.icon_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
+ OrderedHashMap<StringName, bool> color_items = _get_type_items(edited_type, &Theme::get_color_list, show_default);
+ for (OrderedHashMap<StringName, bool>::Element E = color_items.front(); E; E = E.next()) {
+ HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_COLOR, E.key(), E.get());
+ ColorPickerButton *item_editor = memnew(ColorPickerButton);
+ item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_control->add_child(item_editor);
+
+ if (E.get()) {
+ item_editor->set_pick_color(edited_theme->get_color(E.key(), edited_type));
+ item_editor->connect("color_changed", callable_mp(this, &ThemeTypeEditor::_color_item_changed), varray(E.key()));
+ } else {
+ item_editor->set_pick_color(Theme::get_default()->get_color(E.key(), edited_type));
+ item_editor->set_disabled(true);
+ }
+
+ _add_focusable(item_editor);
+ color_items_list->add_child(item_control);
}
+ }
- if (tc.color_items.size()) {
- file->store_line("\n; Color Items:\n");
+ // Constants.
+ {
+ for (int i = constant_items_list->get_child_count() - 1; i >= 0; i--) {
+ Node *node = constant_items_list->get_child(i);
+ node->queue_delete();
+ constant_items_list->remove_child(node);
}
- for (Set<_TECategory::Item<Color>>::Element *F = tc.color_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
+ OrderedHashMap<StringName, bool> constant_items = _get_type_items(edited_type, &Theme::get_constant_list, show_default);
+ for (OrderedHashMap<StringName, bool>::Element E = constant_items.front(); E; E = E.next()) {
+ HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_CONSTANT, E.key(), E.get());
+ SpinBox *item_editor = memnew(SpinBox);
+ item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_editor->set_min(-100000);
+ item_editor->set_max(100000);
+ item_editor->set_step(1);
+ item_editor->set_allow_lesser(true);
+ item_editor->set_allow_greater(true);
+ item_control->add_child(item_editor);
+
+ if (E.get()) {
+ item_editor->set_value(edited_theme->get_constant(E.key(), edited_type));
+ item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_constant_item_changed), varray(E.key()));
+ } else {
+ item_editor->set_value(Theme::get_default()->get_constant(E.key(), edited_type));
+ item_editor->set_editable(false);
+ }
+
+ _add_focusable(item_editor);
+ constant_items_list->add_child(item_control);
}
+ }
- if (tc.constant_items.size()) {
- file->store_line("\n; Constant Items:\n");
+ // Fonts.
+ {
+ for (int i = font_items_list->get_child_count() - 1; i >= 0; i--) {
+ Node *node = font_items_list->get_child(i);
+ node->queue_delete();
+ font_items_list->remove_child(node);
}
- for (Set<_TECategory::Item<int>>::Element *F = tc.constant_items.front(); F; F = F->next()) {
- file->store_line(E->key() + "." + F->get().name + " = default");
+ OrderedHashMap<StringName, bool> font_items = _get_type_items(edited_type, &Theme::get_font_list, show_default);
+ for (OrderedHashMap<StringName, bool>::Element E = font_items.front(); E; E = E.next()) {
+ HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT, E.key(), E.get());
+ EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
+ item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_editor->set_base_type("Font");
+ item_control->add_child(item_editor);
+
+ if (E.get()) {
+ if (edited_theme->has_font(E.key(), edited_type)) {
+ item_editor->set_edited_resource(edited_theme->get_font(E.key(), edited_type));
+ } else {
+ item_editor->set_edited_resource(RES());
+ }
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
+ item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_font_item_changed), varray(E.key()));
+ } else {
+ if (Theme::get_default()->has_font(E.key(), edited_type)) {
+ item_editor->set_edited_resource(Theme::get_default()->get_font(E.key(), edited_type));
+ } else {
+ item_editor->set_edited_resource(RES());
+ }
+ item_editor->set_editable(false);
+ }
+
+ _add_focusable(item_editor);
+ font_items_list->add_child(item_control);
}
}
- file->close();
- memdelete(file);
-}
-
-void ThemeEditor::_theme_create_menu_cbk(int p_option) {
- bool import = (p_option == POPUP_IMPORT_EDITOR_THEME);
+ // Fonts sizes.
+ {
+ for (int i = font_size_items_list->get_child_count() - 1; i >= 0; i--) {
+ Node *node = font_size_items_list->get_child(i);
+ node->queue_delete();
+ font_size_items_list->remove_child(node);
+ }
- Ref<Theme> base_theme;
+ OrderedHashMap<StringName, bool> font_size_items = _get_type_items(edited_type, &Theme::get_font_size_list, show_default);
+ for (OrderedHashMap<StringName, bool>::Element E = font_size_items.front(); E; E = E.next()) {
+ HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT_SIZE, E.key(), E.get());
+ SpinBox *item_editor = memnew(SpinBox);
+ item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_editor->set_min(-100000);
+ item_editor->set_max(100000);
+ item_editor->set_step(1);
+ item_editor->set_allow_lesser(true);
+ item_editor->set_allow_greater(true);
+ item_control->add_child(item_editor);
+
+ if (E.get()) {
+ item_editor->set_value(edited_theme->get_font_size(E.key(), edited_type));
+ item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_font_size_item_changed), varray(E.key()));
+ } else {
+ item_editor->set_value(Theme::get_default()->get_font_size(E.key(), edited_type));
+ item_editor->set_editable(false);
+ }
- if (p_option == POPUP_CREATE_EMPTY) {
- base_theme = Theme::get_default();
- } else {
- base_theme = EditorNode::get_singleton()->get_theme_base()->get_theme();
+ _add_focusable(item_editor);
+ font_size_items_list->add_child(item_control);
+ }
}
+ // Icons.
{
- List<StringName> types;
- base_theme->get_type_list(&types);
+ for (int i = icon_items_list->get_child_count() - 1; i >= 0; i--) {
+ Node *node = icon_items_list->get_child(i);
+ node->queue_delete();
+ icon_items_list->remove_child(node);
+ }
+
+ OrderedHashMap<StringName, bool> icon_items = _get_type_items(edited_type, &Theme::get_icon_list, show_default);
+ for (OrderedHashMap<StringName, bool>::Element E = icon_items.front(); E; E = E.next()) {
+ HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_ICON, E.key(), E.get());
+ EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
+ item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_editor->set_base_type("Texture2D");
+ item_control->add_child(item_editor);
+
+ if (E.get()) {
+ if (edited_theme->has_icon(E.key(), edited_type)) {
+ item_editor->set_edited_resource(edited_theme->get_icon(E.key(), edited_type));
+ } else {
+ item_editor->set_edited_resource(RES());
+ }
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
+ item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_icon_item_changed), varray(E.key()));
+ } else {
+ if (Theme::get_default()->has_icon(E.key(), edited_type)) {
+ item_editor->set_edited_resource(Theme::get_default()->get_icon(E.key(), edited_type));
+ } else {
+ item_editor->set_edited_resource(RES());
+ }
+ item_editor->set_editable(false);
+ }
- for (List<StringName>::Element *T = types.front(); T; T = T->next()) {
- StringName type = T->get();
+ _add_focusable(item_editor);
+ icon_items_list->add_child(item_control);
+ }
+ }
- List<StringName> icons;
- base_theme->get_icon_list(type, &icons);
+ // Styleboxes.
+ {
+ for (int i = stylebox_items_list->get_child_count() - 1; i >= 0; i--) {
+ Node *node = stylebox_items_list->get_child(i);
+ node->queue_delete();
+ stylebox_items_list->remove_child(node);
+ }
- for (List<StringName>::Element *E = icons.front(); E; E = E->next()) {
- theme->set_icon(E->get(), type, import ? base_theme->get_icon(E->get(), type) : Ref<Texture2D>());
+ if (leading_stylebox.pinned) {
+ HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, leading_stylebox.item_name, true);
+ EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
+ item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_editor->set_stretch_ratio(1.5);
+ item_editor->set_base_type("StyleBox");
+
+ Button *pin_leader_button = memnew(Button);
+ pin_leader_button->set_flat(true);
+ pin_leader_button->set_toggle_mode(true);
+ pin_leader_button->set_pressed(true);
+ pin_leader_button->set_icon(get_theme_icon("Pin", "EditorIcons"));
+ pin_leader_button->set_tooltip(TTR("Unpin this StyleBox as a main style."));
+ item_control->add_child(pin_leader_button);
+ pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_unpin_leading_stylebox));
+
+ item_control->add_child(item_editor);
+
+ if (leading_stylebox.stylebox.is_valid()) {
+ item_editor->set_edited_resource(leading_stylebox.stylebox);
+ } else {
+ item_editor->set_edited_resource(RES());
}
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
+ item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(leading_stylebox.item_name));
- List<StringName> styleboxs;
- base_theme->get_stylebox_list(type, &styleboxs);
+ stylebox_items_list->add_child(item_control);
+ stylebox_items_list->add_child(memnew(HSeparator));
+ }
- for (List<StringName>::Element *E = styleboxs.front(); E; E = E->next()) {
- theme->set_stylebox(E->get(), type, import ? base_theme->get_stylebox(E->get(), type) : Ref<StyleBox>());
+ OrderedHashMap<StringName, bool> stylebox_items = _get_type_items(edited_type, &Theme::get_stylebox_list, show_default);
+ for (OrderedHashMap<StringName, bool>::Element E = stylebox_items.front(); E; E = E.next()) {
+ if (leading_stylebox.pinned && leading_stylebox.item_name == E.key()) {
+ continue;
}
- List<StringName> fonts;
- base_theme->get_font_list(type, &fonts);
-
- for (List<StringName>::Element *E = fonts.front(); E; E = E->next()) {
- theme->set_font(E->get(), type, Ref<Font>());
+ HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, E.key(), E.get());
+ EditorResourcePicker *item_editor = memnew(EditorResourcePicker);
+ item_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ item_editor->set_stretch_ratio(1.5);
+ item_editor->set_base_type("StyleBox");
+
+ if (E.get()) {
+ Ref<StyleBox> stylebox_value;
+ if (edited_theme->has_stylebox(E.key(), edited_type)) {
+ stylebox_value = edited_theme->get_stylebox(E.key(), edited_type);
+ item_editor->set_edited_resource(stylebox_value);
+ } else {
+ item_editor->set_edited_resource(RES());
+ }
+ item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item));
+ item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(E.key()));
+
+ Button *pin_leader_button = memnew(Button);
+ pin_leader_button->set_flat(true);
+ pin_leader_button->set_toggle_mode(true);
+ pin_leader_button->set_icon(get_theme_icon("Pin", "EditorIcons"));
+ pin_leader_button->set_tooltip(TTR("Pin this StyleBox as a main style. Editing its properties will update the same properties in all other StyleBoxes of this type."));
+ item_control->add_child(pin_leader_button);
+ pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_pin_leading_stylebox), varray(item_editor, E.key()));
+ } else {
+ if (Theme::get_default()->has_stylebox(E.key(), edited_type)) {
+ item_editor->set_edited_resource(Theme::get_default()->get_stylebox(E.key(), edited_type));
+ } else {
+ item_editor->set_edited_resource(RES());
+ }
+ item_editor->set_editable(false);
}
- List<StringName> font_sizes;
- base_theme->get_font_size_list(type, &font_sizes);
+ item_control->add_child(item_editor);
+ _add_focusable(item_editor);
+ stylebox_items_list->add_child(item_control);
+ }
+ }
+}
+
+void ThemeTypeEditor::_list_type_selected(int p_index) {
+ edited_type = theme_type_list->get_item_text(p_index);
+ _update_type_items();
+}
+
+void ThemeTypeEditor::_add_type_button_cbk() {
+ add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE);
+ add_type_filter->grab_focus();
+}
+
+void ThemeTypeEditor::_add_type_filter_cbk(const String &p_value) {
+ _update_add_type_options(p_value);
+}
+
+void ThemeTypeEditor::_add_type_options_cbk(int p_index) {
+ add_type_filter->set_text(add_type_options->get_item_text(p_index));
+}
+
+void ThemeTypeEditor::_add_type_dialog_confirmed() {
+ select_type(add_type_filter->get_text().strip_edges());
+}
+
+void ThemeTypeEditor::_add_type_dialog_entered(const String &p_value) {
+ select_type(p_value.strip_edges());
+ add_type_dialog->hide();
+}
+
+void ThemeTypeEditor::_add_type_dialog_activated(int p_index) {
+ select_type(add_type_options->get_item_text(p_index));
+ add_type_dialog->hide();
+}
+
+void ThemeTypeEditor::_add_default_type_items() {
+ List<StringName> names;
+
+ updating = true;
+ // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ edited_theme->_freeze_change_propagation();
- for (List<StringName>::Element *E = font_sizes.front(); E; E = E->next()) {
- theme->set_font_size(E->get(), type, base_theme->get_font_size(E->get(), type));
+ {
+ names.clear();
+ Theme::get_default()->get_icon_list(edited_type, &names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (!edited_theme->has_icon(E->get(), edited_type)) {
+ edited_theme->set_icon(E->get(), edited_type, Ref<Texture2D>());
+ }
+ }
+ }
+ {
+ names.clear();
+ Theme::get_default()->get_stylebox_list(edited_type, &names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (!edited_theme->has_stylebox(E->get(), edited_type)) {
+ edited_theme->set_stylebox(E->get(), edited_type, Ref<StyleBox>());
+ }
+ }
+ }
+ {
+ names.clear();
+ Theme::get_default()->get_font_list(edited_type, &names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (!edited_theme->has_font(E->get(), edited_type)) {
+ edited_theme->set_font(E->get(), edited_type, Ref<Font>());
+ }
+ }
+ }
+ {
+ names.clear();
+ Theme::get_default()->get_font_size_list(edited_type, &names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (!edited_theme->has_font_size(E->get(), edited_type)) {
+ edited_theme->set_font_size(E->get(), edited_type, Theme::get_default()->get_font_size(E->get(), edited_type));
+ }
+ }
+ }
+ {
+ names.clear();
+ Theme::get_default()->get_color_list(edited_type, &names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (!edited_theme->has_color(E->get(), edited_type)) {
+ edited_theme->set_color(E->get(), edited_type, Theme::get_default()->get_color(E->get(), edited_type));
+ }
+ }
+ }
+ {
+ names.clear();
+ Theme::get_default()->get_constant_list(edited_type, &names);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (!edited_theme->has_constant(E->get(), edited_type)) {
+ edited_theme->set_constant(E->get(), edited_type, Theme::get_default()->get_constant(E->get(), edited_type));
}
+ }
+ }
+
+ // Allow changes to be reported now that the operation is finished.
+ edited_theme->_unfreeze_and_propagate_changes();
+ updating = false;
- List<StringName> colors;
- base_theme->get_color_list(type, &colors);
+ _update_type_items();
+}
+
+void ThemeTypeEditor::_item_add_cbk(int p_data_type, Control *p_control) {
+ LineEdit *le = Object::cast_to<LineEdit>(p_control);
+ if (le->get_text().strip_edges().is_empty()) {
+ return;
+ }
+
+ String item_name = le->get_text().strip_edges();
+ switch (p_data_type) {
+ case Theme::DATA_TYPE_COLOR: {
+ edited_theme->set_color(item_name, edited_type, Color());
+ } break;
+ case Theme::DATA_TYPE_CONSTANT: {
+ edited_theme->set_constant(item_name, edited_type, 0);
+ } break;
+ case Theme::DATA_TYPE_FONT: {
+ edited_theme->set_font(item_name, edited_type, Ref<Font>());
+ } break;
+ case Theme::DATA_TYPE_FONT_SIZE: {
+ edited_theme->set_font_size(item_name, edited_type, -1);
+ } break;
+ case Theme::DATA_TYPE_ICON: {
+ edited_theme->set_icon(item_name, edited_type, Ref<Texture2D>());
+ } break;
+ case Theme::DATA_TYPE_STYLEBOX: {
+ edited_theme->set_stylebox(item_name, edited_type, Ref<StyleBox>());
+ } break;
+ }
+
+ le->set_text("");
+}
- for (List<StringName>::Element *E = colors.front(); E; E = E->next()) {
- theme->set_color(E->get(), type, import ? base_theme->get_color(E->get(), type) : Color());
+void ThemeTypeEditor::_item_add_lineedit_cbk(String p_value, int p_data_type, Control *p_control) {
+ _item_add_cbk(p_data_type, p_control);
+}
+
+void ThemeTypeEditor::_item_override_cbk(int p_data_type, String p_item_name) {
+ switch (p_data_type) {
+ case Theme::DATA_TYPE_COLOR: {
+ edited_theme->set_color(p_item_name, edited_type, Theme::get_default()->get_color(p_item_name, edited_type));
+ } break;
+ case Theme::DATA_TYPE_CONSTANT: {
+ edited_theme->set_constant(p_item_name, edited_type, Theme::get_default()->get_constant(p_item_name, edited_type));
+ } break;
+ case Theme::DATA_TYPE_FONT: {
+ edited_theme->set_font(p_item_name, edited_type, Ref<Font>());
+ } break;
+ case Theme::DATA_TYPE_FONT_SIZE: {
+ edited_theme->set_font_size(p_item_name, edited_type, Theme::get_default()->get_font_size(p_item_name, edited_type));
+ } break;
+ case Theme::DATA_TYPE_ICON: {
+ edited_theme->set_icon(p_item_name, edited_type, Ref<Texture2D>());
+ } break;
+ case Theme::DATA_TYPE_STYLEBOX: {
+ edited_theme->set_stylebox(p_item_name, edited_type, Ref<StyleBox>());
+ } break;
+ }
+}
+
+void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) {
+ switch (p_data_type) {
+ case Theme::DATA_TYPE_COLOR: {
+ edited_theme->clear_color(p_item_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_CONSTANT: {
+ edited_theme->clear_constant(p_item_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_FONT: {
+ edited_theme->clear_font(p_item_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_FONT_SIZE: {
+ edited_theme->clear_font_size(p_item_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_ICON: {
+ edited_theme->clear_icon(p_item_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_STYLEBOX: {
+ edited_theme->clear_stylebox(p_item_name, edited_type);
+
+ if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) {
+ _unpin_leading_stylebox();
}
+ } break;
+ }
+}
+
+void ThemeTypeEditor::_item_rename_cbk(int p_data_type, String p_item_name, Control *p_control) {
+ // Label
+ Object::cast_to<Label>(p_control->get_child(0))->hide();
+ // Label buttons
+ Object::cast_to<Button>(p_control->get_child(2))->hide();
+ Object::cast_to<Button>(p_control->get_child(3))->hide();
+
+ // LineEdit
+ Object::cast_to<LineEdit>(p_control->get_child(1))->set_text(p_item_name);
+ Object::cast_to<LineEdit>(p_control->get_child(1))->show();
+ // LineEdit buttons
+ Object::cast_to<Button>(p_control->get_child(4))->show();
+ Object::cast_to<Button>(p_control->get_child(5))->show();
+}
+
+void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name, Control *p_control) {
+ LineEdit *le = Object::cast_to<LineEdit>(p_control->get_child(1));
+ if (le->get_text().strip_edges().is_empty()) {
+ return;
+ }
- List<StringName> constants;
- base_theme->get_constant_list(type, &constants);
+ String new_name = le->get_text().strip_edges();
+ if (new_name == p_item_name) {
+ _item_rename_canceled(p_data_type, p_item_name, p_control);
+ return;
+ }
+
+ switch (p_data_type) {
+ case Theme::DATA_TYPE_COLOR: {
+ edited_theme->rename_color(p_item_name, new_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_CONSTANT: {
+ edited_theme->rename_constant(p_item_name, new_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_FONT: {
+ edited_theme->rename_font(p_item_name, new_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_FONT_SIZE: {
+ edited_theme->rename_font_size(p_item_name, new_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_ICON: {
+ edited_theme->rename_icon(p_item_name, new_name, edited_type);
+ } break;
+ case Theme::DATA_TYPE_STYLEBOX: {
+ edited_theme->rename_stylebox(p_item_name, new_name, edited_type);
- for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
- theme->set_constant(E->get(), type, base_theme->get_constant(E->get(), type));
+ if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) {
+ leading_stylebox.item_name = new_name;
}
+ } break;
+ }
+}
+
+void ThemeTypeEditor::_item_rename_entered(String p_value, int p_data_type, String p_item_name, Control *p_control) {
+ _item_rename_confirmed(p_data_type, p_item_name, p_control);
+}
+
+void ThemeTypeEditor::_item_rename_canceled(int p_data_type, String p_item_name, Control *p_control) {
+ // LineEdit
+ Object::cast_to<LineEdit>(p_control->get_child(1))->hide();
+ // LineEdit buttons
+ Object::cast_to<Button>(p_control->get_child(4))->hide();
+ Object::cast_to<Button>(p_control->get_child(5))->hide();
+
+ // Label
+ Object::cast_to<Label>(p_control->get_child(0))->show();
+ // Label buttons
+ Object::cast_to<Button>(p_control->get_child(2))->show();
+ Object::cast_to<Button>(p_control->get_child(3))->show();
+}
+
+void ThemeTypeEditor::_color_item_changed(Color p_value, String p_item_name) {
+ edited_theme->set_color(p_item_name, edited_type, p_value);
+}
+
+void ThemeTypeEditor::_constant_item_changed(float p_value, String p_item_name) {
+ edited_theme->set_constant(p_item_name, edited_type, int(p_value));
+}
+
+void ThemeTypeEditor::_font_size_item_changed(float p_value, String p_item_name) {
+ edited_theme->set_font_size(p_item_name, edited_type, int(p_value));
+}
+
+void ThemeTypeEditor::_edit_resource_item(RES p_resource) {
+ EditorNode::get_singleton()->edit_resource(p_resource);
+}
+
+void ThemeTypeEditor::_font_item_changed(Ref<Font> p_value, String p_item_name) {
+ edited_theme->set_font(p_item_name, edited_type, p_value);
+}
+
+void ThemeTypeEditor::_icon_item_changed(Ref<Texture2D> p_value, String p_item_name) {
+ edited_theme->set_icon(p_item_name, edited_type, p_value);
+}
+
+void ThemeTypeEditor::_stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name) {
+ edited_theme->set_stylebox(p_item_name, edited_type, p_value);
+
+ if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) {
+ if (leading_stylebox.stylebox.is_valid()) {
+ leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
+ }
+
+ leading_stylebox.stylebox = p_value;
+ leading_stylebox.ref_stylebox = (p_value.is_valid() ? p_value->duplicate() : RES());
+ if (p_value.is_valid()) {
+ leading_stylebox.stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
}
}
}
-void ThemeEditor::_theme_edit_button_cbk() {
- theme_edit_dialog->popup_centered(Size2(800, 640) * EDSCALE);
+void ThemeTypeEditor::_pin_leading_stylebox(Control *p_editor, String p_item_name) {
+ if (leading_stylebox.stylebox.is_valid()) {
+ leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
+ }
+
+ Ref<StyleBox> stylebox;
+ if (Object::cast_to<EditorResourcePicker>(p_editor)) {
+ stylebox = Object::cast_to<EditorResourcePicker>(p_editor)->get_edited_resource();
+ }
+
+ LeadingStylebox leader;
+ leader.pinned = true;
+ leader.item_name = p_item_name;
+ leader.stylebox = stylebox;
+ leader.ref_stylebox = (stylebox.is_valid() ? stylebox->duplicate() : RES());
+
+ leading_stylebox = leader;
+ if (leading_stylebox.stylebox.is_valid()) {
+ leading_stylebox.stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
+ }
+
+ _update_type_items();
}
-void ThemeEditor::_notification(int p_what) {
+void ThemeTypeEditor::_unpin_leading_stylebox() {
+ if (leading_stylebox.stylebox.is_valid()) {
+ leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading));
+ }
+
+ LeadingStylebox leader;
+ leader.pinned = false;
+ leading_stylebox = leader;
+
+ _update_type_items();
+}
+
+void ThemeTypeEditor::_update_stylebox_from_leading() {
+ if (!leading_stylebox.pinned || leading_stylebox.stylebox.is_null()) {
+ return;
+ }
+
+ // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ edited_theme->_freeze_change_propagation();
+
+ List<StringName> names;
+ edited_theme->get_stylebox_list(edited_type, &names);
+ List<Ref<StyleBox>> styleboxes;
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ if (E->get() == leading_stylebox.item_name) {
+ continue;
+ }
+
+ Ref<StyleBox> sb = edited_theme->get_stylebox(E->get(), edited_type);
+ if (sb->get_class() == leading_stylebox.stylebox->get_class()) {
+ styleboxes.push_back(sb);
+ }
+ }
+
+ List<PropertyInfo> props;
+ leading_stylebox.stylebox->get_property_list(&props);
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
+ continue;
+ }
+
+ Variant value = leading_stylebox.stylebox->get(E->get().name);
+ Variant ref_value = leading_stylebox.ref_stylebox->get(E->get().name);
+ if (value == ref_value) {
+ continue;
+ }
+
+ for (List<Ref<StyleBox>>::Element *F = styleboxes.front(); F; F = F->next()) {
+ Ref<StyleBox> sb = F->get();
+ sb->set(E->get().name, value);
+ }
+ }
+
+ leading_stylebox.ref_stylebox = leading_stylebox.stylebox->duplicate();
+
+ // Allow changes to be reported now that the operation is finished.
+ edited_theme->_unfreeze_and_propagate_changes();
+}
+
+void ThemeTypeEditor::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_PROCESS: {
- time_left -= get_process_delta_time();
- if (time_left < 0) {
- time_left = 1.5;
- _refresh_interval();
- }
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ add_type_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+
+ data_type_tabs->set_tab_icon(0, get_theme_icon("Color", "EditorIcons"));
+ data_type_tabs->set_tab_icon(1, get_theme_icon("MemberConstant", "EditorIcons"));
+ data_type_tabs->set_tab_icon(2, get_theme_icon("Font", "EditorIcons"));
+ data_type_tabs->set_tab_icon(3, get_theme_icon("FontSize", "EditorIcons"));
+ data_type_tabs->set_tab_icon(4, get_theme_icon("ImageTexture", "EditorIcons"));
+ data_type_tabs->set_tab_icon(5, get_theme_icon("StyleBoxFlat", "EditorIcons"));
+
+ data_type_tabs->add_theme_style_override("tab_selected", get_theme_stylebox("tab_selected_odd", "TabContainer"));
+ data_type_tabs->add_theme_style_override("panel", get_theme_stylebox("panel_odd", "TabContainer"));
+
+ _update_add_type_options();
} break;
}
}
-void ThemeEditor::_bind_methods() {
+void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) {
+ if (edited_theme.is_valid()) {
+ edited_theme->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced));
+ }
+
+ edited_theme = p_theme;
+ edited_theme->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced));
+ _update_type_list();
+}
+
+void ThemeTypeEditor::select_type(String p_type_name) {
+ edited_type = p_type_name;
+ bool type_exists = false;
+
+ for (int i = 0; i < theme_type_list->get_item_count(); i++) {
+ String type_name = theme_type_list->get_item_text(i);
+ if (type_name == edited_type) {
+ theme_type_list->select(i);
+ type_exists = true;
+ break;
+ }
+ }
+
+ if (type_exists) {
+ _update_type_items();
+ } else {
+ edited_theme->add_icon_type(edited_type);
+ edited_theme->add_stylebox_type(edited_type);
+ edited_theme->add_font_type(edited_type);
+ edited_theme->add_font_size_type(edited_type);
+ edited_theme->add_color_type(edited_type);
+ edited_theme->add_constant_type(edited_type);
+
+ _update_type_list();
+ }
+}
+
+ThemeTypeEditor::ThemeTypeEditor() {
+ VBoxContainer *main_vb = memnew(VBoxContainer);
+ add_child(main_vb);
+
+ HBoxContainer *type_list_hb = memnew(HBoxContainer);
+ main_vb->add_child(type_list_hb);
+
+ Label *type_list_label = memnew(Label);
+ type_list_label->set_text(TTR("Type:"));
+ type_list_hb->add_child(type_list_label);
+
+ theme_type_list = memnew(OptionButton);
+ theme_type_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ type_list_hb->add_child(theme_type_list);
+ theme_type_list->connect("item_selected", callable_mp(this, &ThemeTypeEditor::_list_type_selected));
+
+ add_type_button = memnew(Button);
+ add_type_button->set_tooltip(TTR("Add Type"));
+ type_list_hb->add_child(add_type_button);
+ add_type_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_button_cbk));
+
+ add_type_dialog = memnew(ConfirmationDialog);
+ add_type_dialog->set_title(TTR("Add Item Type"));
+ type_list_hb->add_child(add_type_dialog);
+ add_type_dialog->connect("confirmed", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_confirmed));
+
+ VBoxContainer *add_type_vb = memnew(VBoxContainer);
+ add_type_dialog->add_child(add_type_vb);
+
+ Label *add_type_filter_label = memnew(Label);
+ add_type_filter_label->set_text(TTR("Name:"));
+ add_type_vb->add_child(add_type_filter_label);
+ add_type_filter = memnew(LineEdit);
+ add_type_vb->add_child(add_type_filter);
+ add_type_filter->connect("text_changed", callable_mp(this, &ThemeTypeEditor::_add_type_filter_cbk));
+ add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_entered));
+ Label *add_type_options_label = memnew(Label);
+ add_type_options_label->set_text(TTR("Node Types:"));
+ add_type_vb->add_child(add_type_options_label);
+ add_type_options = memnew(ItemList);
+ add_type_options->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_type_vb->add_child(add_type_options);
+ add_type_options->connect("item_selected", callable_mp(this, &ThemeTypeEditor::_add_type_options_cbk));
+ add_type_options->connect("item_activated", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_activated));
+
+ HBoxContainer *type_controls = memnew(HBoxContainer);
+ main_vb->add_child(type_controls);
+
+ show_default_items_button = memnew(CheckButton);
+ show_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL);
+ show_default_items_button->set_text(TTR("Show Default"));
+ show_default_items_button->set_tooltip(TTR("Show default type items alongside items that have been overridden."));
+ show_default_items_button->set_pressed(true);
+ type_controls->add_child(show_default_items_button);
+ show_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_update_type_items));
+
+ Button *add_default_items_button = memnew(Button);
+ add_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL);
+ add_default_items_button->set_text(TTR("Override All"));
+ add_default_items_button->set_tooltip(TTR("Override all default type items."));
+ type_controls->add_child(add_default_items_button);
+ add_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_default_type_items));
+
+ data_type_tabs = memnew(TabContainer);
+ main_vb->add_child(data_type_tabs);
+ data_type_tabs->set_v_size_flags(SIZE_EXPAND_FILL);
+ data_type_tabs->set_use_hidden_tabs_for_min_size(true);
+
+ color_items_list = _create_item_list(Theme::DATA_TYPE_COLOR);
+ constant_items_list = _create_item_list(Theme::DATA_TYPE_CONSTANT);
+ font_items_list = _create_item_list(Theme::DATA_TYPE_FONT);
+ font_size_items_list = _create_item_list(Theme::DATA_TYPE_FONT_SIZE);
+ icon_items_list = _create_item_list(Theme::DATA_TYPE_ICON);
+ stylebox_items_list = _create_item_list(Theme::DATA_TYPE_STYLEBOX);
+
+ update_debounce_timer = memnew(Timer);
+ update_debounce_timer->set_one_shot(true);
+ update_debounce_timer->set_wait_time(0.5);
+ update_debounce_timer->connect("timeout", callable_mp(this, &ThemeTypeEditor::_update_type_list));
+ add_child(update_debounce_timer);
+}
+
+void ThemeEditor::edit(const Ref<Theme> &p_theme) {
+ if (theme == p_theme) {
+ return;
+ }
+
+ theme = p_theme;
+ theme_type_editor->set_edited_theme(p_theme);
+ theme_edit_dialog->set_edited_theme(p_theme);
+
+ for (int i = 0; i < preview_tabs_content->get_child_count(); i++) {
+ ThemeEditorPreview *preview_tab = Object::cast_to<ThemeEditorPreview>(preview_tabs_content->get_child(i));
+ if (!preview_tab) {
+ continue;
+ }
+
+ preview_tab->set_preview_theme(p_theme);
+ }
+
+ theme_name->set_text(TTR("Theme") + ": " + theme->get_path().get_file());
+}
+
+Ref<Theme> ThemeEditor::get_edited_theme() {
+ return theme;
+}
+
+void ThemeEditor::_theme_save_button_cbk(bool p_save_as) {
+ ERR_FAIL_COND_MSG(theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing.");
+
+ if (p_save_as) {
+ EditorNode::get_singleton()->save_resource_as(theme);
+ } else {
+ EditorNode::get_singleton()->save_resource(theme);
+ }
+}
+
+void ThemeEditor::_theme_edit_button_cbk() {
+ theme_edit_dialog->popup_centered(Size2(850, 760) * EDSCALE);
+}
+
+void ThemeEditor::_add_preview_button_cbk() {
+ preview_scene_dialog->popup_file_dialog();
+}
+
+void ThemeEditor::_preview_scene_dialog_cbk(const String &p_path) {
+ SceneThemeEditorPreview *preview_tab = memnew(SceneThemeEditorPreview);
+ if (!preview_tab->set_preview_scene(p_path)) {
+ return;
+ }
+
+ _add_preview_tab(preview_tab, p_path.get_file(), get_theme_icon("PackedScene", "EditorIcons"));
+ preview_tab->connect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid), varray(preview_tab));
+ preview_tab->connect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab), varray(preview_tab));
+}
+
+void ThemeEditor::_add_preview_tab(ThemeEditorPreview *p_preview_tab, const String &p_preview_name, const Ref<Texture2D> &p_icon) {
+ p_preview_tab->set_preview_theme(theme);
+
+ preview_tabs->add_tab(p_preview_name, p_icon);
+ preview_tabs_content->add_child(p_preview_tab);
+ preview_tabs->set_tab_right_button(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("close", "Tabs"));
+ p_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked));
+
+ preview_tabs->set_current_tab(preview_tabs->get_tab_count() - 1);
+}
+
+void ThemeEditor::_change_preview_tab(int p_tab) {
+ ERR_FAIL_INDEX_MSG(p_tab, preview_tabs_content->get_child_count(), "Attempting to open a preview tab that doesn't exist.");
+
+ for (int i = 0; i < preview_tabs_content->get_child_count(); i++) {
+ Control *c = Object::cast_to<Control>(preview_tabs_content->get_child(i));
+ if (!c) {
+ continue;
+ }
+
+ c->set_visible(i == p_tab);
+ }
+}
+
+void ThemeEditor::_remove_preview_tab(int p_tab) {
+ ERR_FAIL_INDEX_MSG(p_tab, preview_tabs_content->get_child_count(), "Attempting to remove a preview tab that doesn't exist.");
+
+ ThemeEditorPreview *preview_tab = Object::cast_to<ThemeEditorPreview>(preview_tabs_content->get_child(p_tab));
+ ERR_FAIL_COND_MSG(Object::cast_to<DefaultThemeEditorPreview>(preview_tab), "Attemptying to remove the default preview tab.");
+
+ if (preview_tab) {
+ preview_tab->disconnect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked));
+ if (preview_tab->is_connected("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid))) {
+ preview_tab->disconnect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid));
+ }
+ if (preview_tab->is_connected("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab))) {
+ preview_tab->disconnect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab));
+ }
+
+ preview_tabs_content->remove_child(preview_tab);
+ preview_tabs->remove_tab(p_tab);
+ _change_preview_tab(preview_tabs->get_current_tab());
+ }
+}
+
+void ThemeEditor::_remove_preview_tab_invalid(Node *p_tab_control) {
+ int tab_index = p_tab_control->get_index();
+ _remove_preview_tab(tab_index);
+}
+
+void ThemeEditor::_update_preview_tab(Node *p_tab_control) {
+ if (!Object::cast_to<SceneThemeEditorPreview>(p_tab_control)) {
+ return;
+ }
+
+ int tab_index = p_tab_control->get_index();
+ SceneThemeEditorPreview *scene_preview = Object::cast_to<SceneThemeEditorPreview>(p_tab_control);
+ preview_tabs->set_tab_title(tab_index, scene_preview->get_preview_scene_path().get_file());
+}
+
+void ThemeEditor::_preview_control_picked(String p_class_name) {
+ theme_type_editor->select_type(p_class_name);
+}
+
+void ThemeEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ preview_tabs->add_theme_style_override("tab_selected", get_theme_stylebox("ThemeEditorPreviewFG", "EditorStyles"));
+ preview_tabs->add_theme_style_override("tab_unselected", get_theme_stylebox("ThemeEditorPreviewBG", "EditorStyles"));
+ preview_tabs_content->add_theme_style_override("panel", get_theme_stylebox("panel_odd", "TabContainer"));
+
+ add_preview_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+ } break;
+ }
}
ThemeEditor::ThemeEditor() {
HBoxContainer *top_menu = memnew(HBoxContainer);
add_child(top_menu);
- top_menu->add_child(memnew(Label(TTR("Preview:"))));
+ theme_name = memnew(Label);
+ theme_name->set_text(TTR("Theme") + ": ");
+ top_menu->add_child(theme_name);
+
top_menu->add_spacer(false);
- theme_create_menu = memnew(MenuButton);
- theme_create_menu->set_text(TTR("Create Theme..."));
- theme_create_menu->set_tooltip(TTR("Create a new Theme."));
- theme_create_menu->get_popup()->add_item(TTR("Empty Template"), POPUP_CREATE_EMPTY);
- theme_create_menu->get_popup()->add_separator();
- theme_create_menu->get_popup()->add_item(TTR("Empty Editor Template"), POPUP_CREATE_EDITOR_EMPTY);
- theme_create_menu->get_popup()->add_item(TTR("From Current Editor Theme"), POPUP_IMPORT_EDITOR_THEME);
- top_menu->add_child(theme_create_menu);
- theme_create_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_theme_create_menu_cbk));
-
- theme_edit_button = memnew(Button);
- theme_edit_button->set_text(TTR("Edit Theme Items"));
- theme_edit_button->set_tooltip(TTR("Customize Theme items."));
+ Button *theme_save_button = memnew(Button);
+ theme_save_button->set_text(TTR("Save"));
+ theme_save_button->set_flat(true);
+ theme_save_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk), varray(false));
+ top_menu->add_child(theme_save_button);
+
+ Button *theme_save_as_button = memnew(Button);
+ theme_save_as_button->set_text(TTR("Save As..."));
+ theme_save_as_button->set_flat(true);
+ theme_save_as_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk), varray(true));
+ top_menu->add_child(theme_save_as_button);
+
+ top_menu->add_child(memnew(VSeparator));
+
+ Button *theme_edit_button = memnew(Button);
+ theme_edit_button->set_text(TTR("Manage Items..."));
+ theme_edit_button->set_tooltip(TTR("Add, remove, organize and import Theme items."));
theme_edit_button->set_flat(true);
theme_edit_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_edit_button_cbk));
top_menu->add_child(theme_edit_button);
- ScrollContainer *scroll = memnew(ScrollContainer);
- add_child(scroll);
- scroll->set_enable_v_scroll(true);
- scroll->set_enable_h_scroll(true);
- scroll->set_v_size_flags(SIZE_EXPAND_FILL);
-
- MarginContainer *root_container = memnew(MarginContainer);
- scroll->add_child(root_container);
- root_container->set_theme(Theme::get_default());
- root_container->set_clip_contents(true);
- root_container->set_custom_minimum_size(Size2(700, 0) * EDSCALE);
- root_container->set_v_size_flags(SIZE_EXPAND_FILL);
- root_container->set_h_size_flags(SIZE_EXPAND_FILL);
-
- //// Preview Controls ////
-
- main_panel = memnew(Panel);
- root_container->add_child(main_panel);
-
- main_container = memnew(MarginContainer);
- root_container->add_child(main_container);
- main_container->add_theme_constant_override("margin_right", 4 * EDSCALE);
- main_container->add_theme_constant_override("margin_top", 4 * EDSCALE);
- main_container->add_theme_constant_override("margin_left", 4 * EDSCALE);
- main_container->add_theme_constant_override("margin_bottom", 4 * EDSCALE);
-
- HBoxContainer *main_hb = memnew(HBoxContainer);
- main_container->add_child(main_hb);
-
- VBoxContainer *first_vb = memnew(VBoxContainer);
- main_hb->add_child(first_vb);
- first_vb->set_h_size_flags(SIZE_EXPAND_FILL);
- first_vb->add_theme_constant_override("separation", 10 * EDSCALE);
-
- first_vb->add_child(memnew(Label("Label")));
-
- first_vb->add_child(memnew(Button("Button")));
- Button *bt = memnew(Button);
- bt->set_text(TTR("Toggle Button"));
- bt->set_toggle_mode(true);
- bt->set_pressed(true);
- first_vb->add_child(bt);
- bt = memnew(Button);
- bt->set_text(TTR("Disabled Button"));
- bt->set_disabled(true);
- first_vb->add_child(bt);
- Button *tb = memnew(Button);
- tb->set_flat(true);
- tb->set_text("Button");
- first_vb->add_child(tb);
-
- CheckButton *cb = memnew(CheckButton);
- cb->set_text("CheckButton");
- first_vb->add_child(cb);
- CheckBox *cbx = memnew(CheckBox);
- cbx->set_text("CheckBox");
- first_vb->add_child(cbx);
-
- MenuButton *test_menu_button = memnew(MenuButton);
- test_menu_button->set_text("MenuButton");
- test_menu_button->get_popup()->add_item(TTR("Item"));
- test_menu_button->get_popup()->add_item(TTR("Disabled Item"));
- test_menu_button->get_popup()->set_item_disabled(1, true);
- test_menu_button->get_popup()->add_separator();
- test_menu_button->get_popup()->add_check_item(TTR("Check Item"));
- test_menu_button->get_popup()->add_check_item(TTR("Checked Item"));
- test_menu_button->get_popup()->set_item_checked(4, true);
- test_menu_button->get_popup()->add_separator();
- test_menu_button->get_popup()->add_radio_check_item(TTR("Radio Item"));
- test_menu_button->get_popup()->add_radio_check_item(TTR("Checked Radio Item"));
- test_menu_button->get_popup()->set_item_checked(7, true);
- test_menu_button->get_popup()->add_separator(TTR("Named Sep."));
-
- PopupMenu *test_submenu = memnew(PopupMenu);
- test_menu_button->get_popup()->add_child(test_submenu);
- test_submenu->set_name("submenu");
- test_menu_button->get_popup()->add_submenu_item(TTR("Submenu"), "submenu");
- test_submenu->add_item(TTR("Subitem 1"));
- test_submenu->add_item(TTR("Subitem 2"));
- first_vb->add_child(test_menu_button);
-
- OptionButton *test_option_button = memnew(OptionButton);
- test_option_button->add_item("OptionButton");
- test_option_button->add_separator();
- test_option_button->add_item(TTR("Has"));
- test_option_button->add_item(TTR("Many"));
- test_option_button->add_item(TTR("Options"));
- first_vb->add_child(test_option_button);
- first_vb->add_child(memnew(ColorPickerButton));
-
- VBoxContainer *second_vb = memnew(VBoxContainer);
- second_vb->set_h_size_flags(SIZE_EXPAND_FILL);
- main_hb->add_child(second_vb);
- second_vb->add_theme_constant_override("separation", 10 * EDSCALE);
- LineEdit *le = memnew(LineEdit);
- le->set_text("LineEdit");
- second_vb->add_child(le);
- le = memnew(LineEdit);
- le->set_text(TTR("Disabled LineEdit"));
- le->set_editable(false);
- second_vb->add_child(le);
- TextEdit *te = memnew(TextEdit);
- te->set_text("TextEdit");
- te->set_custom_minimum_size(Size2(0, 100) * EDSCALE);
- second_vb->add_child(te);
- second_vb->add_child(memnew(SpinBox));
-
- HBoxContainer *vhb = memnew(HBoxContainer);
- second_vb->add_child(vhb);
- vhb->set_custom_minimum_size(Size2(0, 100) * EDSCALE);
- vhb->add_child(memnew(VSlider));
- VScrollBar *vsb = memnew(VScrollBar);
- vsb->set_page(25);
- vhb->add_child(vsb);
- vhb->add_child(memnew(VSeparator));
- VBoxContainer *hvb = memnew(VBoxContainer);
- vhb->add_child(hvb);
- hvb->set_alignment(ALIGN_CENTER);
- hvb->set_h_size_flags(SIZE_EXPAND_FILL);
- hvb->add_child(memnew(HSlider));
- HScrollBar *hsb = memnew(HScrollBar);
- hsb->set_page(25);
- hvb->add_child(hsb);
- HSlider *hs = memnew(HSlider);
- hs->set_editable(false);
- hvb->add_child(hs);
- hvb->add_child(memnew(HSeparator));
- ProgressBar *pb = memnew(ProgressBar);
- pb->set_value(50);
- hvb->add_child(pb);
-
- VBoxContainer *third_vb = memnew(VBoxContainer);
- third_vb->set_h_size_flags(SIZE_EXPAND_FILL);
- third_vb->add_theme_constant_override("separation", 10 * EDSCALE);
- main_hb->add_child(third_vb);
-
- TabContainer *tc = memnew(TabContainer);
- third_vb->add_child(tc);
- tc->set_custom_minimum_size(Size2(0, 135) * EDSCALE);
- Control *tcc = memnew(Control);
- tcc->set_name(TTR("Tab 1"));
- tc->add_child(tcc);
- tcc = memnew(Control);
- tcc->set_name(TTR("Tab 2"));
- tc->add_child(tcc);
- tcc = memnew(Control);
- tcc->set_name(TTR("Tab 3"));
- tc->add_child(tcc);
- tc->set_tab_disabled(2, true);
-
- Tree *test_tree = memnew(Tree);
- third_vb->add_child(test_tree);
- test_tree->set_custom_minimum_size(Size2(0, 175) * EDSCALE);
- test_tree->add_theme_constant_override("draw_relationship_lines", 1);
-
- TreeItem *item = test_tree->create_item();
- item->set_text(0, "Tree");
- item = test_tree->create_item(test_tree->get_root());
- item->set_text(0, "Item");
- item = test_tree->create_item(test_tree->get_root());
- item->set_editable(0, true);
- item->set_text(0, TTR("Editable Item"));
- TreeItem *sub_tree = test_tree->create_item(test_tree->get_root());
- sub_tree->set_text(0, TTR("Subtree"));
- item = test_tree->create_item(sub_tree);
- item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
- item->set_editable(0, true);
- item->set_text(0, "Check Item");
- item = test_tree->create_item(sub_tree);
- item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE);
- item->set_editable(0, true);
- item->set_range_config(0, 0, 20, 0.1);
- item->set_range(0, 2);
- item = test_tree->create_item(sub_tree);
- item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE);
- item->set_editable(0, true);
- item->set_text(0, TTR("Has,Many,Options"));
- item->set_range(0, 2);
-
- main_hb->add_theme_constant_override("separation", 20 * EDSCALE);
-
theme_edit_dialog = memnew(ThemeItemEditorDialog);
theme_edit_dialog->hide();
- add_child(theme_edit_dialog);
+ top_menu->add_child(theme_edit_dialog);
+
+ HSplitContainer *main_hs = memnew(HSplitContainer);
+ main_hs->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(main_hs);
+
+ VBoxContainer *preview_tabs_vb = memnew(VBoxContainer);
+ preview_tabs_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ preview_tabs_vb->set_custom_minimum_size(Size2(520, 0) * EDSCALE);
+ preview_tabs_vb->add_theme_constant_override("separation", 2 * EDSCALE);
+ main_hs->add_child(preview_tabs_vb);
+ HBoxContainer *preview_tabbar_hb = memnew(HBoxContainer);
+ preview_tabs_vb->add_child(preview_tabbar_hb);
+ preview_tabs_content = memnew(PanelContainer);
+ preview_tabs_content->set_v_size_flags(SIZE_EXPAND_FILL);
+ preview_tabs_content->set_draw_behind_parent(true);
+ preview_tabs_vb->add_child(preview_tabs_content);
+
+ preview_tabs = memnew(Tabs);
+ preview_tabs->set_tab_align(Tabs::ALIGN_LEFT);
+ preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL);
+ preview_tabbar_hb->add_child(preview_tabs);
+ preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab));
+ preview_tabs->connect("right_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab));
+
+ HBoxContainer *add_preview_button_hb = memnew(HBoxContainer);
+ preview_tabbar_hb->add_child(add_preview_button_hb);
+ add_preview_button = memnew(Button);
+ add_preview_button->set_text(TTR("Add Preview"));
+ add_preview_button_hb->add_child(add_preview_button);
+ add_preview_button->connect("pressed", callable_mp(this, &ThemeEditor::_add_preview_button_cbk));
+
+ DefaultThemeEditorPreview *default_preview_tab = memnew(DefaultThemeEditorPreview);
+ preview_tabs_content->add_child(default_preview_tab);
+ default_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked));
+ preview_tabs->add_tab(TTR("Default Preview"));
+
+ preview_scene_dialog = memnew(EditorFileDialog);
+ preview_scene_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ preview_scene_dialog->set_title(TTR("Select UI Scene:"));
+ List<String> ext;
+ ResourceLoader::get_recognized_extensions_for_type("PackedScene", &ext);
+ for (List<String>::Element *E = ext.front(); E; E = E->next()) {
+ preview_scene_dialog->add_filter("*." + E->get() + "; Scene");
+ }
+ main_hs->add_child(preview_scene_dialog);
+ preview_scene_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_preview_scene_dialog_cbk));
- file_dialog = memnew(EditorFileDialog);
- file_dialog->add_filter("*.theme ; " + TTR("Theme File"));
- add_child(file_dialog);
- file_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_save_template_cbk));
+ theme_type_editor = memnew(ThemeTypeEditor);
+ main_hs->add_child(theme_type_editor);
+ theme_type_editor->set_custom_minimum_size(Size2(360, 0) * EDSCALE);
}
void ThemeEditorPlugin::edit(Object *p_node) {
if (Object::cast_to<Theme>(p_node)) {
theme_editor->edit(Object::cast_to<Theme>(p_node));
+ } else if (Object::cast_to<Font>(p_node) || Object::cast_to<StyleBox>(p_node) || Object::cast_to<Texture2D>(p_node)) {
+ // Do nothing, keep editing the existing theme.
} else {
theme_editor->edit(Ref<Theme>());
}
}
bool ThemeEditorPlugin::handles(Object *p_node) const {
- return p_node->is_class("Theme");
+ if (Object::cast_to<Theme>(p_node)) {
+ return true;
+ }
+
+ Ref<Theme> edited_theme = theme_editor->get_edited_theme();
+ if (edited_theme.is_null()) {
+ return false;
+ }
+
+ // If we are editing a theme already and this particular resource happens to belong to it,
+ // then we just keep editing it, despite not being able to directly handle it.
+ // This only goes one layer deep, but if required this can be extended to support, say, FontData inside of Font.
+ bool belongs_to_theme = false;
+
+ if (Object::cast_to<Font>(p_node)) {
+ Ref<Font> font_item = Object::cast_to<Font>(p_node);
+ List<StringName> types;
+ List<StringName> names;
+
+ edited_theme->get_font_type_list(&types);
+ for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ names.clear();
+ edited_theme->get_font_list(E->get(), &names);
+
+ for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
+ if (font_item == edited_theme->get_font(F->get(), E->get())) {
+ belongs_to_theme = true;
+ break;
+ }
+ }
+ }
+ } else if (Object::cast_to<StyleBox>(p_node)) {
+ Ref<StyleBox> stylebox_item = Object::cast_to<StyleBox>(p_node);
+ List<StringName> types;
+ List<StringName> names;
+
+ edited_theme->get_stylebox_type_list(&types);
+ for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ names.clear();
+ edited_theme->get_stylebox_list(E->get(), &names);
+
+ for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
+ if (stylebox_item == edited_theme->get_stylebox(F->get(), E->get())) {
+ belongs_to_theme = true;
+ break;
+ }
+ }
+ }
+ } else if (Object::cast_to<Texture2D>(p_node)) {
+ Ref<Texture2D> icon_item = Object::cast_to<Texture2D>(p_node);
+ List<StringName> types;
+ List<StringName> names;
+
+ edited_theme->get_icon_type_list(&types);
+ for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ names.clear();
+ edited_theme->get_icon_list(E->get(), &names);
+
+ for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
+ if (icon_item == edited_theme->get_icon(F->get(), E->get())) {
+ belongs_to_theme = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return belongs_to_theme;
}
void ThemeEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
- theme_editor->set_process(true);
button->show();
editor->make_bottom_panel_item_visible(theme_editor);
} else {
- theme_editor->set_process(false);
if (theme_editor->is_visible_in_tree()) {
editor->hide_bottom_panel();
}
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index 0a840aecd7..cdedbbec8d 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -31,24 +31,160 @@
#ifndef THEME_EDITOR_PLUGIN_H
#define THEME_EDITOR_PLUGIN_H
-#include "scene/gui/check_box.h"
-#include "scene/gui/file_dialog.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/option_button.h"
#include "scene/gui/scroll_container.h"
+#include "scene/gui/tabs.h"
#include "scene/gui/texture_rect.h"
#include "scene/resources/theme.h"
+#include "theme_editor_preview.h"
#include "editor/editor_node.h"
+class ThemeItemImportTree : public VBoxContainer {
+ GDCLASS(ThemeItemImportTree, VBoxContainer);
+
+ Ref<Theme> edited_theme;
+ Ref<Theme> base_theme;
+
+ struct ThemeItem {
+ String type_name;
+ Theme::DataType data_type;
+ String item_name;
+
+ bool operator<(const ThemeItem &p_item) const {
+ if (type_name == p_item.type_name && data_type == p_item.data_type) {
+ return item_name < p_item.item_name;
+ }
+ if (type_name == p_item.type_name) {
+ return data_type < p_item.data_type;
+ }
+ return type_name < p_item.type_name;
+ }
+ };
+
+ enum ItemCheckedState {
+ SELECT_IMPORT_DEFINITION,
+ SELECT_IMPORT_FULL,
+ };
+
+ Map<ThemeItem, ItemCheckedState> selected_items;
+
+ LineEdit *import_items_filter;
+
+ Tree *import_items_tree;
+ List<TreeItem *> tree_color_items;
+ List<TreeItem *> tree_constant_items;
+ List<TreeItem *> tree_font_items;
+ List<TreeItem *> tree_font_size_items;
+ List<TreeItem *> tree_icon_items;
+ List<TreeItem *> tree_stylebox_items;
+
+ bool updating_tree = false;
+
+ enum ItemActionFlag {
+ IMPORT_ITEM = 1,
+ IMPORT_ITEM_DATA = 2,
+ };
+
+ TextureRect *select_colors_icon;
+ Label *select_colors_label;
+ Button *select_all_colors_button;
+ Button *select_full_colors_button;
+ Button *deselect_all_colors_button;
+ Label *total_selected_colors_label;
+
+ TextureRect *select_constants_icon;
+ Label *select_constants_label;
+ Button *select_all_constants_button;
+ Button *select_full_constants_button;
+ Button *deselect_all_constants_button;
+ Label *total_selected_constants_label;
+
+ TextureRect *select_fonts_icon;
+ Label *select_fonts_label;
+ Button *select_all_fonts_button;
+ Button *select_full_fonts_button;
+ Button *deselect_all_fonts_button;
+ Label *total_selected_fonts_label;
+
+ TextureRect *select_font_sizes_icon;
+ Label *select_font_sizes_label;
+ Button *select_all_font_sizes_button;
+ Button *select_full_font_sizes_button;
+ Button *deselect_all_font_sizes_button;
+ Label *total_selected_font_sizes_label;
+
+ TextureRect *select_icons_icon;
+ Label *select_icons_label;
+ Button *select_all_icons_button;
+ Button *select_full_icons_button;
+ Button *deselect_all_icons_button;
+ Label *total_selected_icons_label;
+
+ TextureRect *select_styleboxes_icon;
+ Label *select_styleboxes_label;
+ Button *select_all_styleboxes_button;
+ Button *select_full_styleboxes_button;
+ Button *deselect_all_styleboxes_button;
+ Label *total_selected_styleboxes_label;
+
+ HBoxContainer *select_icons_warning_hb;
+ TextureRect *select_icons_warning_icon;
+ Label *select_icons_warning;
+
+ Button *import_collapse_types_button;
+ Button *import_expand_types_button;
+ Button *import_select_all_button;
+ Button *import_select_full_button;
+ Button *import_deselect_all_button;
+
+ void _update_items_tree();
+ void _toggle_type_items(bool p_collapse);
+ void _filter_text_changed(const String &p_value);
+
+ void _store_selected_item(TreeItem *p_tree_item);
+ void _restore_selected_item(TreeItem *p_tree_item);
+ void _update_total_selected(Theme::DataType p_data_type);
+
+ void _tree_item_edited();
+ void _select_all_subitems(TreeItem *p_root_item, bool p_select_with_data);
+ void _deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely);
+ void _update_parent_items(TreeItem *p_root_item);
+
+ void _select_all_items_pressed();
+ void _select_full_items_pressed();
+ void _deselect_all_items_pressed();
+
+ void _select_all_data_type_pressed(int p_data_type);
+ void _select_full_data_type_pressed(int p_data_type);
+ void _deselect_all_data_type_pressed(int p_data_type);
+
+ void _import_selected();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_edited_theme(const Ref<Theme> &p_theme);
+ void set_base_theme(const Ref<Theme> &p_theme);
+ void reset_item_tree();
+
+ bool has_selected_items() const;
+
+ ThemeItemImportTree();
+};
+
class ThemeItemEditorDialog : public AcceptDialog {
GDCLASS(ThemeItemEditorDialog, AcceptDialog);
Ref<Theme> edited_theme;
+ TabContainer *tc;
+
ItemList *edit_type_list;
- OptionButton *edit_add_class_options;
- LineEdit *edit_add_custom_value;
+ LineEdit *edit_add_type_value;
String edited_item_type;
Button *edit_items_add_color;
@@ -83,6 +219,19 @@ class ThemeItemEditorDialog : public AcceptDialog {
String edit_item_old_name;
Theme::DataType edit_item_data_type = Theme::DATA_TYPE_MAX;
+ ThemeItemImportTree *import_default_theme_items;
+ ThemeItemImportTree *import_editor_theme_items;
+ ThemeItemImportTree *import_other_theme_items;
+
+ LineEdit *import_another_theme_value;
+ Button *import_another_theme_button;
+ EditorFileDialog *import_another_theme_dialog;
+
+ ConfirmationDialog *confirm_closing_dialog;
+
+ void ok_pressed() override;
+ void _close_dialog();
+
void _dialog_about_to_show();
void _update_edit_types();
void _edited_type_selected(int p_item_idx);
@@ -90,8 +239,7 @@ class ThemeItemEditorDialog : public AcceptDialog {
void _update_edit_item_tree(String p_item_type);
void _item_tree_button_pressed(Object *p_item, int p_column, int p_id);
- void _add_class_type_items();
- void _add_custom_type();
+ void _add_theme_type();
void _add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type);
void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type);
void _remove_class_items();
@@ -103,6 +251,9 @@ class ThemeItemEditorDialog : public AcceptDialog {
void _confirm_edit_theme_item();
void _edit_theme_item_gui_input(const Ref<InputEvent> &p_event);
+ void _open_select_another_theme();
+ void _select_another_theme_cbk(const String &p_path);
+
protected:
void _notification(int p_what);
@@ -112,41 +263,123 @@ public:
ThemeItemEditorDialog();
};
+class ThemeTypeEditor : public MarginContainer {
+ GDCLASS(ThemeTypeEditor, MarginContainer);
+
+ Ref<Theme> edited_theme;
+ String edited_type;
+ bool updating = false;
+
+ struct LeadingStylebox {
+ bool pinned = false;
+ StringName item_name;
+ Ref<StyleBox> stylebox;
+ Ref<StyleBox> ref_stylebox;
+ };
+
+ LeadingStylebox leading_stylebox;
+
+ OptionButton *theme_type_list;
+ Button *add_type_button;
+ ConfirmationDialog *add_type_dialog;
+ LineEdit *add_type_filter;
+ ItemList *add_type_options;
+
+ CheckButton *show_default_items_button;
+
+ TabContainer *data_type_tabs;
+ VBoxContainer *color_items_list;
+ VBoxContainer *constant_items_list;
+ VBoxContainer *font_items_list;
+ VBoxContainer *font_size_items_list;
+ VBoxContainer *icon_items_list;
+ VBoxContainer *stylebox_items_list;
+
+ Vector<Control *> focusables;
+ Timer *update_debounce_timer;
+
+ VBoxContainer *_create_item_list(Theme::DataType p_data_type);
+ void _update_type_list();
+ void _update_type_list_debounced();
+ void _update_add_type_options(const String &p_filter = "");
+ OrderedHashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default);
+ HBoxContainer *_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable);
+ void _add_focusable(Control *p_control);
+ void _update_type_items();
+
+ void _list_type_selected(int p_index);
+ void _select_type(String p_type_name);
+ void _add_type_button_cbk();
+ void _add_type_filter_cbk(const String &p_value);
+ void _add_type_options_cbk(int p_index);
+ void _add_type_dialog_confirmed();
+ void _add_type_dialog_entered(const String &p_value);
+ void _add_type_dialog_activated(int p_index);
+ void _add_default_type_items();
+
+ void _item_add_cbk(int p_data_type, Control *p_control);
+ void _item_add_lineedit_cbk(String p_value, int p_data_type, Control *p_control);
+ void _item_override_cbk(int p_data_type, String p_item_name);
+ void _item_remove_cbk(int p_data_type, String p_item_name);
+ void _item_rename_cbk(int p_data_type, String p_item_name, Control *p_control);
+ void _item_rename_confirmed(int p_data_type, String p_item_name, Control *p_control);
+ void _item_rename_entered(String p_value, int p_data_type, String p_item_name, Control *p_control);
+ void _item_rename_canceled(int p_data_type, String p_item_name, Control *p_control);
+
+ void _color_item_changed(Color p_value, String p_item_name);
+ void _constant_item_changed(float p_value, String p_item_name);
+ void _font_size_item_changed(float p_value, String p_item_name);
+ void _edit_resource_item(RES p_resource);
+ void _font_item_changed(Ref<Font> p_value, String p_item_name);
+ void _icon_item_changed(Ref<Texture2D> p_value, String p_item_name);
+ void _stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name);
+ void _pin_leading_stylebox(Control *p_editor, String p_item_name);
+ void _unpin_leading_stylebox();
+ void _update_stylebox_from_leading();
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void set_edited_theme(const Ref<Theme> &p_theme);
+ void select_type(String p_type_name);
+
+ ThemeTypeEditor();
+};
+
class ThemeEditor : public VBoxContainer {
GDCLASS(ThemeEditor, VBoxContainer);
- Panel *main_panel;
- MarginContainer *main_container;
Ref<Theme> theme;
- EditorFileDialog *file_dialog;
+ Tabs *preview_tabs;
+ PanelContainer *preview_tabs_content;
+ Button *add_preview_button;
+ EditorFileDialog *preview_scene_dialog;
- double time_left = 0;
+ ThemeTypeEditor *theme_type_editor;
- Button *theme_edit_button;
- MenuButton *theme_create_menu;
+ Label *theme_name;
ThemeItemEditorDialog *theme_edit_dialog;
- enum CreatePopupMode {
- POPUP_CREATE_EMPTY,
- POPUP_CREATE_EDITOR_EMPTY,
- POPUP_IMPORT_EDITOR_THEME,
- };
-
- Tree *test_tree;
-
- void _save_template_cbk(String fname);
+ void _theme_save_button_cbk(bool p_save_as);
void _theme_edit_button_cbk();
- void _theme_create_menu_cbk(int p_option);
- void _propagate_redraw(Control *p_at);
- void _refresh_interval();
+
+ void _add_preview_button_cbk();
+ void _preview_scene_dialog_cbk(const String &p_path);
+ void _add_preview_tab(ThemeEditorPreview *p_preview_tab, const String &p_preview_name, const Ref<Texture2D> &p_icon);
+ void _change_preview_tab(int p_tab);
+ void _remove_preview_tab(int p_tab);
+ void _remove_preview_tab_invalid(Node *p_tab_control);
+ void _update_preview_tab(Node *p_tab_control);
+ void _preview_control_picked(String p_class_name);
protected:
void _notification(int p_what);
- static void _bind_methods();
public:
void edit(const Ref<Theme> &p_theme);
+ Ref<Theme> get_edited_theme();
ThemeEditor();
};
diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp
new file mode 100644
index 0000000000..0b02150444
--- /dev/null
+++ b/editor/plugins/theme_editor_preview.cpp
@@ -0,0 +1,464 @@
+/*************************************************************************/
+/* theme_editor_preview.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 "theme_editor_preview.h"
+
+#include "core/input/input.h"
+#include "core/math/math_funcs.h"
+#include "scene/resources/packed_scene.h"
+
+#include "editor/editor_scale.h"
+
+void ThemeEditorPreview::set_preview_theme(const Ref<Theme> &p_theme) {
+ preview_content->set_theme(p_theme);
+}
+
+void ThemeEditorPreview::add_preview_overlay(Control *p_overlay) {
+ preview_overlay->add_child(p_overlay);
+ p_overlay->hide();
+}
+
+void ThemeEditorPreview::_propagate_redraw(Control *p_at) {
+ p_at->notification(NOTIFICATION_THEME_CHANGED);
+ p_at->minimum_size_changed();
+ p_at->update();
+ for (int i = 0; i < p_at->get_child_count(); i++) {
+ Control *a = Object::cast_to<Control>(p_at->get_child(i));
+ if (a) {
+ _propagate_redraw(a);
+ }
+ }
+}
+
+void ThemeEditorPreview::_refresh_interval() {
+ // In case the project settings have changed.
+ preview_bg->set_color(GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
+
+ _propagate_redraw(preview_bg);
+ _propagate_redraw(preview_content);
+}
+
+void ThemeEditorPreview::_preview_visibility_changed() {
+ set_process(is_visible());
+}
+
+void ThemeEditorPreview::_picker_button_cbk() {
+ picker_overlay->set_visible(picker_button->is_pressed());
+}
+
+Control *ThemeEditorPreview::_find_hovered_control(Control *p_parent, Vector2 p_mouse_position) {
+ Control *found = nullptr;
+
+ for (int i = 0; i < p_parent->get_child_count(); i++) {
+ Control *cc = Object::cast_to<Control>(p_parent->get_child(i));
+ if (!cc || !cc->is_visible()) {
+ continue;
+ }
+
+ Rect2 crect = cc->get_rect();
+ if (crect.has_point(p_mouse_position)) {
+ // Check if there is a child control under mouse.
+ if (cc->get_child_count() > 0) {
+ found = _find_hovered_control(cc, p_mouse_position - cc->get_position());
+ }
+
+ // If there are no applicable children, use the control itself.
+ if (!found) {
+ found = cc;
+ }
+ break;
+ }
+ }
+
+ return found;
+}
+
+void ThemeEditorPreview::_draw_picker_overlay() {
+ if (!picker_button->is_pressed()) {
+ return;
+ }
+
+ picker_overlay->draw_rect(Rect2(Vector2(0.0, 0.0), picker_overlay->get_size()), get_theme_color("preview_picker_overlay_color", "ThemeEditor"));
+ if (hovered_control) {
+ Rect2 highlight_rect = hovered_control->get_global_rect();
+ highlight_rect.position = picker_overlay->get_global_transform().affine_inverse().xform(highlight_rect.position);
+
+ picker_overlay->draw_style_box(get_theme_stylebox("preview_picker_overlay", "ThemeEditor"), highlight_rect);
+ }
+}
+
+void ThemeEditorPreview::_gui_input_picker_overlay(const Ref<InputEvent> &p_event) {
+ if (!picker_button->is_pressed()) {
+ return;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (hovered_control) {
+ StringName theme_type = hovered_control->get_theme_custom_type();
+ if (theme_type == StringName()) {
+ theme_type = hovered_control->get_class_name();
+ }
+
+ emit_signal("control_picked", theme_type);
+ picker_button->set_pressed(false);
+ picker_overlay->set_visible(false);
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid()) {
+ Vector2 mp = preview_content->get_local_mouse_position();
+ hovered_control = _find_hovered_control(preview_content, mp);
+ picker_overlay->update();
+ }
+}
+
+void ThemeEditorPreview::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (is_visible_in_tree()) {
+ set_process(true);
+ }
+
+ connect("visibility_changed", callable_mp(this, &ThemeEditorPreview::_preview_visibility_changed));
+ [[fallthrough]];
+ }
+ case NOTIFICATION_THEME_CHANGED: {
+ picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
+ } break;
+ case NOTIFICATION_PROCESS: {
+ time_left -= get_process_delta_time();
+ if (time_left < 0) {
+ time_left = 1.5;
+ _refresh_interval();
+ }
+ } break;
+ }
+}
+
+void ThemeEditorPreview::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("control_picked", PropertyInfo(Variant::STRING, "class_name")));
+}
+
+ThemeEditorPreview::ThemeEditorPreview() {
+ preview_toolbar = memnew(HBoxContainer);
+ add_child(preview_toolbar);
+
+ picker_button = memnew(Button);
+ preview_toolbar->add_child(picker_button);
+ picker_button->set_flat(true);
+ picker_button->set_toggle_mode(true);
+ picker_button->set_tooltip(TTR("Toggle the control picker, allowing to visually select control types for edit."));
+ picker_button->connect("pressed", callable_mp(this, &ThemeEditorPreview::_picker_button_cbk));
+
+ MarginContainer *preview_body = memnew(MarginContainer);
+ preview_body->set_custom_minimum_size(Size2(480, 0) * EDSCALE);
+ preview_body->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(preview_body);
+
+ ScrollContainer *preview_container = memnew(ScrollContainer);
+ preview_container->set_enable_v_scroll(true);
+ preview_container->set_enable_h_scroll(true);
+ preview_body->add_child(preview_container);
+
+ MarginContainer *preview_root = memnew(MarginContainer);
+ preview_container->add_child(preview_root);
+ preview_root->set_theme(Theme::get_default());
+ preview_root->set_clip_contents(true);
+ preview_root->set_custom_minimum_size(Size2(450, 0) * EDSCALE);
+ preview_root->set_v_size_flags(SIZE_EXPAND_FILL);
+ preview_root->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ preview_bg = memnew(ColorRect);
+ preview_bg->set_anchors_and_offsets_preset(PRESET_WIDE);
+ preview_bg->set_color(GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
+ preview_root->add_child(preview_bg);
+
+ preview_content = memnew(MarginContainer);
+ preview_root->add_child(preview_content);
+ preview_content->add_theme_constant_override("margin_right", 4 * EDSCALE);
+ preview_content->add_theme_constant_override("margin_top", 4 * EDSCALE);
+ preview_content->add_theme_constant_override("margin_left", 4 * EDSCALE);
+ preview_content->add_theme_constant_override("margin_bottom", 4 * EDSCALE);
+
+ preview_overlay = memnew(MarginContainer);
+ preview_overlay->set_mouse_filter(MOUSE_FILTER_IGNORE);
+ preview_body->add_child(preview_overlay);
+
+ picker_overlay = memnew(Control);
+ add_preview_overlay(picker_overlay);
+ picker_overlay->connect("draw", callable_mp(this, &ThemeEditorPreview::_draw_picker_overlay));
+ picker_overlay->connect("gui_input", callable_mp(this, &ThemeEditorPreview::_gui_input_picker_overlay));
+}
+
+DefaultThemeEditorPreview::DefaultThemeEditorPreview() {
+ Panel *main_panel = memnew(Panel);
+ preview_content->add_child(main_panel);
+
+ MarginContainer *main_mc = memnew(MarginContainer);
+ main_mc->add_theme_constant_override("margin_right", 4 * EDSCALE);
+ main_mc->add_theme_constant_override("margin_top", 4 * EDSCALE);
+ main_mc->add_theme_constant_override("margin_left", 4 * EDSCALE);
+ main_mc->add_theme_constant_override("margin_bottom", 4 * EDSCALE);
+ preview_content->add_child(main_mc);
+
+ HBoxContainer *main_hb = memnew(HBoxContainer);
+ main_mc->add_child(main_hb);
+ main_hb->add_theme_constant_override("separation", 20 * EDSCALE);
+
+ VBoxContainer *first_vb = memnew(VBoxContainer);
+ main_hb->add_child(first_vb);
+ first_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ first_vb->add_theme_constant_override("separation", 10 * EDSCALE);
+
+ first_vb->add_child(memnew(Label("Label")));
+
+ first_vb->add_child(memnew(Button("Button")));
+ Button *bt = memnew(Button);
+ bt->set_text(TTR("Toggle Button"));
+ bt->set_toggle_mode(true);
+ bt->set_pressed(true);
+ first_vb->add_child(bt);
+ bt = memnew(Button);
+ bt->set_text(TTR("Disabled Button"));
+ bt->set_disabled(true);
+ first_vb->add_child(bt);
+ Button *tb = memnew(Button);
+ tb->set_flat(true);
+ tb->set_text("Button");
+ first_vb->add_child(tb);
+
+ CheckButton *cb = memnew(CheckButton);
+ cb->set_text("CheckButton");
+ first_vb->add_child(cb);
+ CheckBox *cbx = memnew(CheckBox);
+ cbx->set_text("CheckBox");
+ first_vb->add_child(cbx);
+
+ MenuButton *test_menu_button = memnew(MenuButton);
+ test_menu_button->set_text("MenuButton");
+ test_menu_button->get_popup()->add_item(TTR("Item"));
+ test_menu_button->get_popup()->add_item(TTR("Disabled Item"));
+ test_menu_button->get_popup()->set_item_disabled(1, true);
+ test_menu_button->get_popup()->add_separator();
+ test_menu_button->get_popup()->add_check_item(TTR("Check Item"));
+ test_menu_button->get_popup()->add_check_item(TTR("Checked Item"));
+ test_menu_button->get_popup()->set_item_checked(4, true);
+ test_menu_button->get_popup()->add_separator();
+ test_menu_button->get_popup()->add_radio_check_item(TTR("Radio Item"));
+ test_menu_button->get_popup()->add_radio_check_item(TTR("Checked Radio Item"));
+ test_menu_button->get_popup()->set_item_checked(7, true);
+ test_menu_button->get_popup()->add_separator(TTR("Named Separator"));
+
+ PopupMenu *test_submenu = memnew(PopupMenu);
+ test_menu_button->get_popup()->add_child(test_submenu);
+ test_submenu->set_name("submenu");
+ test_menu_button->get_popup()->add_submenu_item(TTR("Submenu"), "submenu");
+ test_submenu->add_item(TTR("Subitem 1"));
+ test_submenu->add_item(TTR("Subitem 2"));
+ first_vb->add_child(test_menu_button);
+
+ OptionButton *test_option_button = memnew(OptionButton);
+ test_option_button->add_item("OptionButton");
+ test_option_button->add_separator();
+ test_option_button->add_item(TTR("Has"));
+ test_option_button->add_item(TTR("Many"));
+ test_option_button->add_item(TTR("Options"));
+ first_vb->add_child(test_option_button);
+ first_vb->add_child(memnew(ColorPickerButton));
+
+ VBoxContainer *second_vb = memnew(VBoxContainer);
+ second_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ main_hb->add_child(second_vb);
+ second_vb->add_theme_constant_override("separation", 10 * EDSCALE);
+ LineEdit *le = memnew(LineEdit);
+ le->set_text("LineEdit");
+ second_vb->add_child(le);
+ le = memnew(LineEdit);
+ le->set_text(TTR("Disabled LineEdit"));
+ le->set_editable(false);
+ second_vb->add_child(le);
+ TextEdit *te = memnew(TextEdit);
+ te->set_text("TextEdit");
+ te->set_custom_minimum_size(Size2(0, 100) * EDSCALE);
+ second_vb->add_child(te);
+ second_vb->add_child(memnew(SpinBox));
+
+ HBoxContainer *vhb = memnew(HBoxContainer);
+ second_vb->add_child(vhb);
+ vhb->set_custom_minimum_size(Size2(0, 100) * EDSCALE);
+ vhb->add_child(memnew(VSlider));
+ VScrollBar *vsb = memnew(VScrollBar);
+ vsb->set_page(25);
+ vhb->add_child(vsb);
+ vhb->add_child(memnew(VSeparator));
+ VBoxContainer *hvb = memnew(VBoxContainer);
+ vhb->add_child(hvb);
+ hvb->set_alignment(BoxContainer::ALIGN_CENTER);
+ hvb->set_h_size_flags(SIZE_EXPAND_FILL);
+ hvb->add_child(memnew(HSlider));
+ HScrollBar *hsb = memnew(HScrollBar);
+ hsb->set_page(25);
+ hvb->add_child(hsb);
+ HSlider *hs = memnew(HSlider);
+ hs->set_editable(false);
+ hvb->add_child(hs);
+ hvb->add_child(memnew(HSeparator));
+ ProgressBar *pb = memnew(ProgressBar);
+ pb->set_value(50);
+ hvb->add_child(pb);
+
+ VBoxContainer *third_vb = memnew(VBoxContainer);
+ third_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ third_vb->add_theme_constant_override("separation", 10 * EDSCALE);
+ main_hb->add_child(third_vb);
+
+ TabContainer *tc = memnew(TabContainer);
+ third_vb->add_child(tc);
+ tc->set_custom_minimum_size(Size2(0, 135) * EDSCALE);
+ Control *tcc = memnew(Control);
+ tcc->set_name(TTR("Tab 1"));
+ tc->add_child(tcc);
+ tcc = memnew(Control);
+ tcc->set_name(TTR("Tab 2"));
+ tc->add_child(tcc);
+ tcc = memnew(Control);
+ tcc->set_name(TTR("Tab 3"));
+ tc->add_child(tcc);
+ tc->set_tab_disabled(2, true);
+
+ Tree *test_tree = memnew(Tree);
+ third_vb->add_child(test_tree);
+ test_tree->set_custom_minimum_size(Size2(0, 175) * EDSCALE);
+
+ TreeItem *item = test_tree->create_item();
+ item->set_text(0, "Tree");
+ item = test_tree->create_item(test_tree->get_root());
+ item->set_text(0, "Item");
+ item = test_tree->create_item(test_tree->get_root());
+ item->set_editable(0, true);
+ item->set_text(0, TTR("Editable Item"));
+ TreeItem *sub_tree = test_tree->create_item(test_tree->get_root());
+ sub_tree->set_text(0, TTR("Subtree"));
+ item = test_tree->create_item(sub_tree);
+ item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+ item->set_editable(0, true);
+ item->set_text(0, "Check Item");
+ item = test_tree->create_item(sub_tree);
+ item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE);
+ item->set_editable(0, true);
+ item->set_range_config(0, 0, 20, 0.1);
+ item->set_range(0, 2);
+ item = test_tree->create_item(sub_tree);
+ item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE);
+ item->set_editable(0, true);
+ item->set_text(0, TTR("Has,Many,Options"));
+ item->set_range(0, 2);
+}
+
+void SceneThemeEditorPreview::_reload_scene() {
+ if (loaded_scene.is_null()) {
+ return;
+ }
+
+ if (loaded_scene->get_path().is_empty() || !ResourceLoader::exists(loaded_scene->get_path())) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid path, the PackedScene resource was probably moved or removed."));
+ emit_signal("scene_invalidated");
+ return;
+ }
+
+ for (int i = preview_content->get_child_count() - 1; i >= 0; i--) {
+ Node *node = preview_content->get_child(i);
+ node->queue_delete();
+ preview_content->remove_child(node);
+ }
+
+ Node *instance = loaded_scene->instantiate();
+ if (!instance || !Object::cast_to<Control>(instance)) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid PackedScene resource, must have a Control node at its root."));
+ emit_signal("scene_invalidated");
+ return;
+ }
+
+ preview_content->add_child(instance);
+ emit_signal("scene_reloaded");
+}
+
+void SceneThemeEditorPreview::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ reload_scene_button->set_icon(get_theme_icon("Reload", "EditorIcons"));
+ } break;
+ }
+}
+
+void SceneThemeEditorPreview::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("scene_invalidated"));
+ ADD_SIGNAL(MethodInfo("scene_reloaded"));
+}
+
+bool SceneThemeEditorPreview::set_preview_scene(const String &p_path) {
+ loaded_scene = ResourceLoader::load(p_path);
+ if (loaded_scene.is_null()) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid file, not a PackedScene resource."));
+ return false;
+ }
+
+ Node *instance = loaded_scene->instantiate();
+ if (!instance || !Object::cast_to<Control>(instance)) {
+ EditorNode::get_singleton()->show_warning(TTR("Invalid PackedScene resource, must have a Control node at its root."));
+ return false;
+ }
+
+ preview_content->add_child(instance);
+ return true;
+}
+
+String SceneThemeEditorPreview::get_preview_scene_path() const {
+ if (loaded_scene.is_null()) {
+ return "";
+ }
+
+ return loaded_scene->get_path();
+}
+
+SceneThemeEditorPreview::SceneThemeEditorPreview() {
+ preview_toolbar->add_child(memnew(VSeparator));
+
+ reload_scene_button = memnew(Button);
+ reload_scene_button->set_flat(true);
+ reload_scene_button->set_tooltip(TTR("Reload the scene to reflect its most actual state."));
+ preview_toolbar->add_child(reload_scene_button);
+ reload_scene_button->connect("pressed", callable_mp(this, &SceneThemeEditorPreview::_reload_scene));
+}
diff --git a/editor/plugins/theme_editor_preview.h b/editor/plugins/theme_editor_preview.h
new file mode 100644
index 0000000000..efb7e424d4
--- /dev/null
+++ b/editor/plugins/theme_editor_preview.h
@@ -0,0 +1,118 @@
+/*************************************************************************/
+/* theme_editor_preview.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 THEME_EDITOR_PREVIEW_H
+#define THEME_EDITOR_PREVIEW_H
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/check_box.h"
+#include "scene/gui/check_button.h"
+#include "scene/gui/color_picker.h"
+#include "scene/gui/color_rect.h"
+#include "scene/gui/label.h"
+#include "scene/gui/margin_container.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/option_button.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/progress_bar.h"
+#include "scene/gui/scroll_container.h"
+#include "scene/gui/separator.h"
+#include "scene/gui/spin_box.h"
+#include "scene/gui/tab_container.h"
+#include "scene/gui/text_edit.h"
+#include "scene/gui/tree.h"
+#include "scene/resources/theme.h"
+
+#include "editor/editor_node.h"
+
+class ThemeEditorPreview : public VBoxContainer {
+ GDCLASS(ThemeEditorPreview, VBoxContainer);
+
+ ColorRect *preview_bg;
+ MarginContainer *preview_overlay;
+ Control *picker_overlay;
+ Control *hovered_control = nullptr;
+
+ double time_left = 0;
+
+ void _propagate_redraw(Control *p_at);
+ void _refresh_interval();
+ void _preview_visibility_changed();
+
+ void _picker_button_cbk();
+ Control *_find_hovered_control(Control *p_parent, Vector2 p_mouse_position);
+
+ void _draw_picker_overlay();
+ void _gui_input_picker_overlay(const Ref<InputEvent> &p_event);
+
+protected:
+ HBoxContainer *preview_toolbar;
+ MarginContainer *preview_content;
+ Button *picker_button;
+
+ void add_preview_overlay(Control *p_overlay);
+
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_preview_theme(const Ref<Theme> &p_theme);
+
+ ThemeEditorPreview();
+};
+
+class DefaultThemeEditorPreview : public ThemeEditorPreview {
+ GDCLASS(DefaultThemeEditorPreview, ThemeEditorPreview);
+
+public:
+ DefaultThemeEditorPreview();
+};
+
+class SceneThemeEditorPreview : public ThemeEditorPreview {
+ GDCLASS(SceneThemeEditorPreview, ThemeEditorPreview);
+
+ Ref<PackedScene> loaded_scene;
+
+ Button *reload_scene_button;
+
+ void _reload_scene();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ bool set_preview_scene(const String &p_path);
+ String get_preview_scene_path() const;
+
+ SceneThemeEditorPreview();
+};
+
+#endif // THEME_EDITOR_PREVIEW_H
diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp
deleted file mode 100644
index bd721244ea..0000000000
--- a/editor/plugins/tile_map_editor_plugin.cpp
+++ /dev/null
@@ -1,2329 +0,0 @@
-/*************************************************************************/
-/* tile_map_editor_plugin.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 "tile_map_editor_plugin.h"
-
-#include "canvas_item_editor_plugin.h"
-#include "core/input/input.h"
-#include "core/math/math_funcs.h"
-#include "core/os/keyboard.h"
-#include "editor/editor_scale.h"
-#include "editor/editor_settings.h"
-#include "scene/gui/split_container.h"
-
-void TileMapEditor::_node_removed(Node *p_node) {
- if (p_node == node && node) {
- Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed);
-
- if (node->is_connected("settings_changed", callable_tileset_settings_changed)) {
- // Fixes #44824, which describes a situation where you can reselect a TileMap node without first de-selecting it when switching scenes.
- node->disconnect("settings_changed", callable_tileset_settings_changed);
- }
- node = nullptr;
- }
-}
-
-void TileMapEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_PROCESS: {
- if (bucket_queue.size()) {
- CanvasItemEditor::get_singleton()->update_viewport();
- }
-
- } break;
-
- case NOTIFICATION_ENTER_TREE: {
- get_tree()->connect("node_removed", callable_mp(this, &TileMapEditor::_node_removed));
- [[fallthrough]];
- }
-
- case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- if (is_visible_in_tree()) {
- _update_palette();
- }
-
- paint_button->set_icon(get_theme_icon("Edit", "EditorIcons"));
- line_button->set_icon(get_theme_icon("CurveLinear", "EditorIcons"));
- rectangle_button->set_icon(get_theme_icon("Rectangle", "EditorIcons"));
- bucket_fill_button->set_icon(get_theme_icon("Bucket", "EditorIcons"));
- picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
- select_button->set_icon(get_theme_icon("ActionCopy", "EditorIcons"));
-
- rotate_left_button->set_icon(get_theme_icon("RotateLeft", "EditorIcons"));
- rotate_right_button->set_icon(get_theme_icon("RotateRight", "EditorIcons"));
- flip_horizontal_button->set_icon(get_theme_icon("MirrorX", "EditorIcons"));
- flip_vertical_button->set_icon(get_theme_icon("MirrorY", "EditorIcons"));
- clear_transform_button->set_icon(get_theme_icon("Clear", "EditorIcons"));
-
- search_box->set_right_icon(get_theme_icon("Search", "EditorIcons"));
- search_box->set_clear_button_enabled(true);
-
- PopupMenu *p = options->get_popup();
- p->set_item_icon(p->get_item_index(OPTION_CUT), get_theme_icon("ActionCut", "EditorIcons"));
- p->set_item_icon(p->get_item_index(OPTION_COPY), get_theme_icon("Duplicate", "EditorIcons"));
- p->set_item_icon(p->get_item_index(OPTION_ERASE_SELECTION), get_theme_icon("Remove", "EditorIcons"));
-
- } break;
-
- case NOTIFICATION_EXIT_TREE: {
- get_tree()->disconnect("node_removed", callable_mp(this, &TileMapEditor::_node_removed));
- } break;
-
- case NOTIFICATION_APPLICATION_FOCUS_OUT: {
- if (tool == TOOL_PAINTING) {
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _set_cell(over_tile, ids, flip_h, flip_v, transpose);
- _finish_undo();
-
- paint_undo.clear();
- }
-
- tool = TOOL_NONE;
- _update_button_tool();
- }
-
- // set flag to ignore over_tile on refocus
- refocus_over_tile = true;
- } break;
- }
-}
-
-void TileMapEditor::_update_button_tool() {
- Button *tb[6] = { paint_button, line_button, rectangle_button, bucket_fill_button, picker_button, select_button };
-
- // Unpress all buttons
- for (int i = 0; i < 6; i++) {
- tb[i]->set_pressed(false);
- }
-
- // Press the good button
- switch (tool) {
- case TOOL_NONE:
- case TOOL_PAINTING: {
- paint_button->set_pressed(true);
- } break;
- case TOOL_LINE_PAINT: {
- line_button->set_pressed(true);
- } break;
- case TOOL_RECTANGLE_PAINT: {
- rectangle_button->set_pressed(true);
- } break;
- case TOOL_BUCKET: {
- bucket_fill_button->set_pressed(true);
- } break;
- case TOOL_PICKING: {
- picker_button->set_pressed(true);
- } break;
- case TOOL_SELECTING: {
- select_button->set_pressed(true);
- } break;
- default:
- break;
- }
-
- if (tool != TOOL_PICKING) {
- last_tool = tool;
- }
-}
-
-void TileMapEditor::_button_tool_select(int p_tool) {
- tool = (Tool)p_tool;
- _update_button_tool();
- switch (tool) {
- case TOOL_SELECTING: {
- selection_active = false;
- } break;
- default:
- break;
- }
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_menu_option(int p_option) {
- switch (p_option) {
- case OPTION_COPY: {
- _update_copydata();
-
- if (selection_active) {
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- }
- } break;
- case OPTION_ERASE_SELECTION: {
- if (!selection_active) {
- return;
- }
-
- _start_undo(TTR("Erase Selection"));
- _erase_selection();
- _finish_undo();
-
- selection_active = false;
- copydata.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
- } break;
- case OPTION_FIX_INVALID: {
- undo_redo->create_action(TTR("Fix Invalid Tiles"));
- undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
- node->fix_invalid_tiles();
- undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
- undo_redo->commit_action();
-
- } break;
- case OPTION_CUT: {
- if (selection_active) {
- _update_copydata();
-
- _start_undo(TTR("Cut Selection"));
- _erase_selection();
- _finish_undo();
-
- selection_active = false;
-
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- }
- } break;
- }
- _update_button_tool();
-}
-
-void TileMapEditor::_palette_selected(int index) {
- _update_palette();
-}
-
-void TileMapEditor::_palette_multi_selected(int index, bool selected) {
- _update_palette();
-}
-
-void TileMapEditor::_palette_input(const Ref<InputEvent> &p_event) {
- const Ref<InputEventMouseButton> mb = p_event;
-
- // Zoom in/out using Ctrl + mouse wheel.
- if (mb.is_valid() && mb->is_pressed() && mb->get_command()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {
- size_slider->set_value(size_slider->get_value() + 0.2);
- }
-
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {
- size_slider->set_value(size_slider->get_value() - 0.2);
- }
- }
-}
-
-void TileMapEditor::_canvas_mouse_enter() {
- mouse_over = true;
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_canvas_mouse_exit() {
- mouse_over = false;
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-Vector<int> TileMapEditor::get_selected_tiles() const {
- Vector<int> items = palette->get_selected_items();
-
- if (items.size() == 0) {
- items.push_back(TileMap::INVALID_CELL);
- return items;
- }
-
- for (int i = items.size() - 1; i >= 0; i--) {
- items.write[i] = palette->get_item_metadata(items[i]);
- }
- return items;
-}
-
-void TileMapEditor::set_selected_tiles(Vector<int> p_tiles) {
- palette->deselect_all();
-
- for (int i = p_tiles.size() - 1; i >= 0; i--) {
- int idx = palette->find_metadata(p_tiles[i]);
-
- if (idx >= 0) {
- palette->select(idx, false);
- }
- }
-
- palette->ensure_current_is_visible();
-}
-
-Dictionary TileMapEditor::_create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord) {
- Dictionary cell;
-
- cell["id"] = tile;
- cell["flip_h"] = flip_x;
- cell["flip_y"] = flip_y;
- cell["transpose"] = transpose;
- cell["auto_coord"] = autotile_coord;
-
- return cell;
-}
-
-void TileMapEditor::_create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new) {
- Dictionary cell_old = _create_cell_dictionary(p_cell_old.idx, p_cell_old.xf, p_cell_old.yf, p_cell_old.tr, p_cell_old.ac);
- Dictionary cell_new = _create_cell_dictionary(p_cell_new.idx, p_cell_new.xf, p_cell_new.yf, p_cell_new.tr, p_cell_new.ac);
-
- undo_redo->add_undo_method(node, "_set_celld", p_vec, cell_old);
- undo_redo->add_do_method(node, "_set_celld", p_vec, cell_new);
-}
-
-void TileMapEditor::_start_undo(const String &p_action) {
- undo_data.clear();
- undo_redo->create_action(p_action);
-}
-
-void TileMapEditor::_finish_undo() {
- if (undo_data.size()) {
- for (Map<Point2i, CellOp>::Element *E = undo_data.front(); E; E = E->next()) {
- _create_set_cell_undo_redo(E->key(), E->get(), _get_op_from_cell(E->key()));
- }
-
- undo_data.clear();
- }
-
- undo_redo->commit_action();
-}
-
-void TileMapEditor::_set_cell(const Point2i &p_pos, Vector<int> p_values, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord) {
- ERR_FAIL_COND(!node);
-
- if (p_values.size() == 0) {
- return;
- }
-
- int p_value = p_values[Math::rand() % p_values.size()];
- int prev_val = node->get_cell(p_pos.x, p_pos.y);
-
- bool prev_flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y);
- bool prev_flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y);
- bool prev_transpose = node->is_cell_transposed(p_pos.x, p_pos.y);
- Vector2 prev_position = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
-
- Vector2 position;
- int current = manual_palette->get_current();
- if (current != -1) {
- if (tool != TOOL_PASTING) {
- position = manual_palette->get_item_metadata(current);
- } else {
- position = p_autotile_coord;
- }
- } else {
- // If there is no manual tile selected, that either means that
- // autotiling is enabled, or the given tile is not autotiling. Either
- // way, the coordinate of the tile does not matter, so assigning it to
- // the coordinate of the existing tile works fine.
- position = prev_position;
- }
-
- if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose && prev_position == position) {
- return; // Check that it's actually different.
- }
-
- for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) {
- for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) {
- Point2i p = Point2i(x, y);
- if (!undo_data.has(p)) {
- undo_data[p] = _get_op_from_cell(p);
- }
- }
- }
-
- node->_set_celld(p_pos, _create_cell_dictionary(p_value, p_flip_h, p_flip_v, p_transpose, p_autotile_coord));
-
- if (tool == TOOL_PASTING) {
- return;
- }
-
- if (manual_autotile || (p_value != -1 && node->get_tileset()->tile_get_tile_mode(p_value) == TileSet::ATLAS_TILE)) {
- if (current != -1) {
- node->set_cell_autotile_coord(p_pos.x, p_pos.y, position);
- } else if (node->get_tileset()->tile_get_tile_mode(p_value) == TileSet::ATLAS_TILE && priority_atlastile) {
- // BIND_CENTER is used to indicate that bitmask should not update for this tile cell.
- node->get_tileset()->autotile_set_bitmask(p_value, Vector2(p_pos.x, p_pos.y), TileSet::BIND_CENTER);
- node->update_cell_bitmask(p_pos.x, p_pos.y);
- }
- } else {
- node->update_bitmask_area(Point2(p_pos));
- }
-}
-
-void TileMapEditor::_manual_toggled(bool p_enabled) {
- manual_autotile = p_enabled;
- _update_palette();
-}
-
-void TileMapEditor::_priority_toggled(bool p_enabled) {
- priority_atlastile = p_enabled;
- _update_palette();
-}
-
-void TileMapEditor::_text_entered(const String &p_text) {
- canvas_item_editor_viewport->grab_focus();
-}
-
-void TileMapEditor::_text_changed(const String &p_text) {
- _update_palette();
-}
-
-void TileMapEditor::_sbox_input(const Ref<InputEvent> &p_ie) {
- Ref<InputEventKey> k = p_ie;
-
- if (k.is_valid() && (k->get_keycode() == KEY_UP ||
- k->get_keycode() == KEY_DOWN ||
- k->get_keycode() == KEY_PAGEUP ||
- k->get_keycode() == KEY_PAGEDOWN)) {
- palette->call("_gui_input", k);
- search_box->accept_event();
- }
-}
-
-// Implementation detail of TileMapEditor::_update_palette();
-// In modern C++ this could have been inside its body.
-namespace {
-struct _PaletteEntry {
- int id = 0;
- String name;
-
- bool operator<(const _PaletteEntry &p_rhs) const {
- // Natural no case comparison will compare strings based on CharType
- // order (except digits) and on numbers that start on the same position.
- return name.naturalnocasecmp_to(p_rhs.name) < 0;
- }
-};
-} // namespace
-
-void TileMapEditor::_update_palette() {
- if (!node) {
- return;
- }
-
- // Update the clear button.
- clear_transform_button->set_disabled(!flip_h && !flip_v && !transpose);
-
- // Update the palette.
- Vector<int> selected = get_selected_tiles();
- int selected_single = palette->get_current();
- int selected_manual = manual_palette->get_current();
- palette->clear();
- manual_palette->clear();
- manual_palette->hide();
-
- Ref<TileSet> tileset = node->get_tileset();
- if (tileset.is_null()) {
- search_box->set_text("");
- search_box->set_editable(false);
- info_message->show();
- return;
- }
-
- search_box->set_editable(true);
- info_message->hide();
-
- List<int> tiles;
- tileset->get_tile_list(&tiles);
- if (tiles.is_empty()) {
- return;
- }
-
- float min_size = EDITOR_DEF("editors/tile_map/preview_size", 64);
- min_size *= EDSCALE;
- int hseparation = EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8);
- bool show_tile_names = bool(EDITOR_DEF("editors/tile_map/show_tile_names", true));
- bool show_tile_ids = bool(EDITOR_DEF("editors/tile_map/show_tile_ids", false));
- bool sort_by_name = bool(EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true));
-
- palette->add_theme_constant_override("hseparation", hseparation * EDSCALE);
-
- palette->set_fixed_icon_size(Size2(min_size, min_size));
- palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1));
- palette->set_same_column_width(true);
- manual_palette->set_fixed_icon_size(Size2(min_size, min_size));
- manual_palette->set_same_column_width(true);
-
- String filter = search_box->get_text().strip_edges();
-
- Vector<_PaletteEntry> entries;
-
- for (List<int>::Element *E = tiles.front(); E; E = E->next()) {
- String name = tileset->tile_get_name(E->get());
-
- if (name != "") {
- if (show_tile_ids) {
- if (sort_by_name) {
- name = name + " - " + itos(E->get());
- } else {
- name = itos(E->get()) + " - " + name;
- }
- }
- } else {
- name = "#" + itos(E->get());
- }
-
- if (filter != "" && !filter.is_subsequence_ofi(name)) {
- continue;
- }
-
- const _PaletteEntry entry = { E->get(), name };
- entries.push_back(entry);
- }
-
- if (sort_by_name) {
- entries.sort();
- }
-
- for (int i = 0; i < entries.size(); i++) {
- if (show_tile_names) {
- palette->add_item(entries[i].name);
- } else {
- palette->add_item(String());
- }
-
- Ref<Texture2D> tex = tileset->tile_get_texture(entries[i].id);
-
- if (tex.is_valid()) {
- Rect2 region = tileset->tile_get_region(entries[i].id);
-
- if (tileset->tile_get_tile_mode(entries[i].id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(entries[i].id) == TileSet::ATLAS_TILE) {
- int spacing = tileset->autotile_get_spacing(entries[i].id);
- region.size = tileset->autotile_get_size(entries[i].id);
- region.position += (region.size + Vector2(spacing, spacing)) * tileset->autotile_get_icon_coordinate(entries[i].id);
- }
-
- // Transpose and flip.
- palette->set_item_icon_transposed(palette->get_item_count() - 1, transpose);
- if (flip_h) {
- region.size.x = -region.size.x;
- }
- if (flip_v) {
- region.size.y = -region.size.y;
- }
-
- // Set region.
- if (region.size != Size2()) {
- palette->set_item_icon_region(palette->get_item_count() - 1, region);
- }
-
- // Set icon.
- palette->set_item_icon(palette->get_item_count() - 1, tex);
-
- // Modulation.
- Color color = tileset->tile_get_modulate(entries[i].id);
- palette->set_item_icon_modulate(palette->get_item_count() - 1, color);
- }
-
- palette->set_item_metadata(palette->get_item_count() - 1, entries[i].id);
- }
-
- int sel_tile = selected.get(0);
- if (selected.get(0) != TileMap::INVALID_CELL) {
- set_selected_tiles(selected);
- sel_tile = selected.get(Math::rand() % selected.size());
- } else if (palette->get_item_count() > 0) {
- palette->select(0);
- sel_tile = palette->get_selected_items().get(0);
- }
-
- if (sel_tile != TileMap::INVALID_CELL && ((manual_autotile && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) || (!priority_atlastile && tileset->tile_get_tile_mode(sel_tile) == TileSet::ATLAS_TILE))) {
- const Map<Vector2, uint32_t> &tiles2 = tileset->autotile_get_bitmask_map(sel_tile);
-
- Vector<Vector2> entries2;
- for (const Map<Vector2, uint32_t>::Element *E = tiles2.front(); E; E = E->next()) {
- entries2.push_back(E->key());
- }
- // Sort tiles in row-major order.
- struct SwapComparator {
- _FORCE_INLINE_ bool operator()(const Vector2 &v_l, const Vector2 &v_r) const {
- return v_l.y != v_r.y ? v_l.y < v_r.y : v_l.x < v_r.x;
- }
- };
- entries2.sort_custom<SwapComparator>();
-
- Ref<Texture2D> tex = tileset->tile_get_texture(sel_tile);
-
- for (int i = 0; i < entries2.size(); i++) {
- manual_palette->add_item(String());
-
- if (tex.is_valid()) {
- Rect2 region = tileset->tile_get_region(sel_tile);
- int spacing = tileset->autotile_get_spacing(sel_tile);
- region.size = tileset->autotile_get_size(sel_tile); // !!
- region.position += (region.size + Vector2(spacing, spacing)) * entries2[i];
-
- if (!region.has_no_area()) {
- manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region);
- }
-
- manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex);
- }
-
- manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries2[i]);
- }
- }
-
- if (manual_palette->get_item_count() > 0) {
- // Only show the manual palette if at least tile exists in it.
- if (selected_manual == -1 || selected_single != palette->get_current()) {
- selected_manual = 0;
- }
- if (selected_manual < manual_palette->get_item_count()) {
- manual_palette->set_current(selected_manual);
- }
- manual_palette->show();
- }
-
- if (sel_tile != TileMap::INVALID_CELL && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) {
- manual_button->show();
- priority_button->hide();
- } else {
- manual_button->hide();
- priority_button->show();
- }
-}
-
-void TileMapEditor::_pick_tile(const Point2 &p_pos) {
- int id = node->get_cell(p_pos.x, p_pos.y);
-
- if (id == TileMap::INVALID_CELL) {
- return;
- }
-
- if (search_box->get_text() != "") {
- search_box->set_text("");
- _update_palette();
- }
-
- flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y);
- flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y);
- transpose = node->is_cell_transposed(p_pos.x, p_pos.y);
- autotile_coord = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
-
- Vector<int> selected;
- selected.push_back(id);
- set_selected_tiles(selected);
- _update_palette();
-
- if ((manual_autotile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::AUTO_TILE) || (!priority_atlastile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::ATLAS_TILE)) {
- manual_palette->select(manual_palette->find_metadata((Point2)autotile_coord));
- }
-
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-Vector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool erase, bool preview) {
- int prev_id = node->get_cell(p_start.x, p_start.y);
- Vector<int> ids;
- ids.push_back(TileMap::INVALID_CELL);
- if (!erase) {
- ids = get_selected_tiles();
-
- if (ids.size() == 0 || ids[0] == TileMap::INVALID_CELL) {
- return Vector<Vector2>();
- }
- } else if (prev_id == TileMap::INVALID_CELL) {
- return Vector<Vector2>();
- }
-
- // Check if the tile variation is the same
- Vector2 prev_position = node->get_cell_autotile_coord(p_start.x, p_start.y);
- if (ids.size() == 1 && ids[0] == prev_id) {
- int current = manual_palette->get_current();
- Vector2 position = manual_palette->get_item_metadata(current);
- if (prev_position == position) {
- // Same ID and variation, nothing to change
- return Vector<Vector2>();
- }
- }
-
- Rect2i r = node->get_used_rect();
-
- int area = r.get_area();
- if (preview) {
- // Test if we can re-use the result from preview bucket fill
- bool invalidate_cache = false;
- // Area changed
- if (r != bucket_cache_rect) {
- _clear_bucket_cache();
- }
- // Cache grid is not initialized
- if (bucket_cache_visited == nullptr) {
- bucket_cache_visited = new bool[area];
- invalidate_cache = true;
- }
- // Tile ID changed or position wasn't visited by the previous fill
- const int loc = (p_start.x - r.position.x) + (p_start.y - r.position.y) * r.get_size().x;
- const bool in_range = 0 <= loc && loc < area;
- if (prev_id != bucket_cache_tile || (in_range && !bucket_cache_visited[loc])) {
- invalidate_cache = true;
- }
- if (invalidate_cache) {
- for (int i = 0; i < area; ++i) {
- bucket_cache_visited[i] = false;
- }
- bucket_cache = Vector<Vector2>();
- bucket_cache_tile = prev_id;
- bucket_cache_rect = r;
- bucket_queue.clear();
- }
- }
-
- Vector<Vector2> points;
- Vector<Vector2> non_preview_cache;
- int count = 0;
- int limit = 0;
-
- if (preview) {
- limit = 1024;
- } else {
- bucket_queue.clear();
- }
-
- bucket_queue.push_back(p_start);
-
- while (bucket_queue.size()) {
- Point2i n = bucket_queue.front()->get();
- bucket_queue.pop_front();
-
- if (!r.has_point(n)) {
- continue;
- }
-
- if (node->get_cell(n.x, n.y) == prev_id) {
- if (preview) {
- int loc = (n.x - r.position.x) + (n.y - r.position.y) * r.get_size().x;
- if (bucket_cache_visited[loc]) {
- continue;
- }
- bucket_cache_visited[loc] = true;
- bucket_cache.push_back(n);
- } else {
- if (non_preview_cache.find(n) >= 0) {
- continue;
- }
- points.push_back(n);
- non_preview_cache.push_back(n);
- }
-
- bucket_queue.push_back(Point2i(n.x, n.y + 1));
- bucket_queue.push_back(Point2i(n.x, n.y - 1));
- bucket_queue.push_back(Point2i(n.x + 1, n.y));
- bucket_queue.push_back(Point2i(n.x - 1, n.y));
- count++;
- }
-
- if (limit > 0 && count >= limit) {
- break;
- }
- }
-
- return preview ? bucket_cache : points;
-}
-
-void TileMapEditor::_fill_points(const Vector<Vector2> &p_points, const Dictionary &p_op) {
- int len = p_points.size();
- const Vector2 *pr = p_points.ptr();
-
- Vector<int> ids = p_op["id"];
- bool xf = p_op["flip_h"];
- bool yf = p_op["flip_v"];
- bool tr = p_op["transpose"];
-
- for (int i = 0; i < len; i++) {
- _set_cell(pr[i], ids, xf, yf, tr);
- node->make_bitmask_area_dirty(pr[i]);
- }
- if (!manual_autotile) {
- node->update_dirty_bitmask();
- }
-}
-
-void TileMapEditor::_erase_points(const Vector<Vector2> &p_points) {
- int len = p_points.size();
- const Vector2 *pr = p_points.ptr();
-
- for (int i = 0; i < len; i++) {
- _set_cell(pr[i], invalid_cell);
- }
-}
-
-void TileMapEditor::_select(const Point2i &p_from, const Point2i &p_to) {
- Point2i begin = p_from;
- Point2i end = p_to;
-
- if (begin.x > end.x) {
- SWAP(begin.x, end.x);
- }
- if (begin.y > end.y) {
- SWAP(begin.y, end.y);
- }
-
- rectangle.position = begin;
- rectangle.size = end - begin;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_erase_selection() {
- if (!selection_active) {
- return;
- }
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _set_cell(Point2i(j, i), invalid_cell, false, false, false);
- }
- }
-}
-
-void TileMapEditor::_draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) {
- Ref<Texture2D> t = node->get_tileset()->tile_get_texture(p_cell);
-
- if (t.is_null()) {
- return;
- }
-
- Vector2 tile_ofs = node->get_tileset()->tile_get_texture_offset(p_cell);
-
- Rect2 r = node->get_tileset()->tile_get_region(p_cell);
- if (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::AUTO_TILE || node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::ATLAS_TILE) {
- Vector2 offset;
- if (tool != TOOL_PASTING) {
- int selected = manual_palette->get_current();
- if ((manual_autotile || (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::ATLAS_TILE && !priority_atlastile)) && selected != -1) {
- offset = manual_palette->get_item_metadata(selected);
- } else {
- offset = node->get_tileset()->autotile_get_icon_coordinate(p_cell);
- }
- } else {
- offset = p_autotile_coord;
- }
-
- int spacing = node->get_tileset()->autotile_get_spacing(p_cell);
- r.size = node->get_tileset()->autotile_get_size(p_cell);
- r.position += (r.size + Vector2(spacing, spacing)) * offset;
- }
- Size2 cell_size = node->get_cell_size();
- bool centered_texture = node->is_centered_textures_enabled();
- bool compatibility_mode_enabled = node->is_compatibility_mode_enabled();
- Rect2 rect = Rect2();
- rect.position = node->map_to_world(p_point) + node->get_cell_draw_offset();
-
- if (r.has_no_area()) {
- rect.size = t->get_size();
- } else {
- rect.size = r.size;
- }
-
- if (compatibility_mode_enabled && !centered_texture) {
- if (rect.size.y > rect.size.x) {
- if ((p_flip_h && (p_flip_v || p_transpose)) || (p_flip_v && !p_transpose)) {
- tile_ofs.y += rect.size.y - rect.size.x;
- }
- } else if (rect.size.y < rect.size.x) {
- if ((p_flip_v && (p_flip_h || p_transpose)) || (p_flip_h && !p_transpose)) {
- tile_ofs.x += rect.size.x - rect.size.y;
- }
- }
- }
-
- if (p_transpose) {
- SWAP(tile_ofs.x, tile_ofs.y);
- if (centered_texture) {
- rect.position.x += cell_size.x / 2 - rect.size.y / 2;
- rect.position.y += cell_size.y / 2 - rect.size.x / 2;
- }
- } else if (centered_texture) {
- rect.position += cell_size / 2 - rect.size / 2;
- }
-
- if (p_flip_h) {
- rect.size.x *= -1.0;
- tile_ofs.x *= -1.0;
- }
-
- if (p_flip_v) {
- rect.size.y *= -1.0;
- tile_ofs.y *= -1.0;
- }
-
- if (compatibility_mode_enabled && !centered_texture) {
- if (node->get_tile_origin() == TileMap::TILE_ORIGIN_TOP_LEFT) {
- rect.position += tile_ofs;
- } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_BOTTOM_LEFT) {
- rect.position += tile_ofs;
-
- if (p_transpose) {
- if (p_flip_h) {
- rect.position.x -= cell_size.x;
- } else {
- rect.position.x += cell_size.x;
- }
- } else {
- if (p_flip_v) {
- rect.position.y -= cell_size.y;
- } else {
- rect.position.y += cell_size.y;
- }
- }
-
- } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_CENTER) {
- rect.position += tile_ofs;
-
- if (p_flip_h) {
- rect.position.x -= cell_size.x / 2;
- } else {
- rect.position.x += cell_size.x / 2;
- }
-
- if (p_flip_v) {
- rect.position.y -= cell_size.y / 2;
- } else {
- rect.position.y += cell_size.y / 2;
- }
- }
- } else {
- rect.position += tile_ofs;
- }
-
- Color modulate = node->get_tileset()->tile_get_modulate(p_cell);
- modulate.a = 0.5;
-
- Transform2D old_transform = p_viewport->get_viewport_transform();
- p_viewport->draw_set_transform_matrix(p_xform); // Take into account TileMap transformation when displaying cell
- if (r.has_no_area()) {
- p_viewport->draw_texture_rect(t, rect, false, modulate, p_transpose);
- } else {
- p_viewport->draw_texture_rect_region(t, rect, r, modulate, p_transpose);
- }
- p_viewport->draw_set_transform_matrix(old_transform);
-}
-
-void TileMapEditor::_draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) {
- Vector<Vector2> points = _bucket_fill(p_point, false, true);
- const Vector2 *pr = points.ptr();
- int len = points.size();
-
- for (int i = 0; i < len; ++i) {
- _draw_cell(p_viewport, p_cell, pr[i], p_flip_h, p_flip_v, p_transpose, p_autotile_coord, p_xform);
- }
-}
-
-void TileMapEditor::_clear_bucket_cache() {
- if (bucket_cache_visited) {
- delete[] bucket_cache_visited;
- bucket_cache_visited = nullptr;
- }
-}
-
-void TileMapEditor::_update_copydata() {
- copydata.clear();
-
- if (!selection_active) {
- return;
- }
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- TileData tcd;
-
- tcd.cell = node->get_cell(j, i);
- if (tcd.cell != TileMap::INVALID_CELL) {
- tcd.pos = Point2i(j, i);
- tcd.flip_h = node->is_cell_x_flipped(j, i);
- tcd.flip_v = node->is_cell_y_flipped(j, i);
- tcd.transpose = node->is_cell_transposed(j, i);
- tcd.autotile_coord = node->get_cell_autotile_coord(j, i);
-
- copydata.push_back(tcd);
- }
- }
- }
-}
-
-static inline Vector<Point2i> line(int x0, int x1, int y0, int y1) {
- Vector<Point2i> points;
-
- float dx = ABS(x1 - x0);
- float dy = ABS(y1 - y0);
-
- int x = x0;
- int y = y0;
-
- int sx = x0 > x1 ? -1 : 1;
- int sy = y0 > y1 ? -1 : 1;
-
- if (dx > dy) {
- float err = dx / 2;
-
- for (; x != x1; x += sx) {
- points.push_back(Vector2(x, y));
-
- err -= dy;
- if (err < 0) {
- y += sy;
- err += dx;
- }
- }
- } else {
- float err = dy / 2;
-
- for (; y != y1; y += sy) {
- points.push_back(Vector2(x, y));
-
- err -= dx;
- if (err < 0) {
- x += sx;
- err += dy;
- }
- }
- }
-
- points.push_back(Vector2(x, y));
-
- return points;
-}
-
-bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
- if (!node || !node->get_tileset().is_valid() || !node->is_visible_in_tree() || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
- return false;
- }
-
- Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
- Transform2D xform_inv = xform.affine_inverse();
-
- Ref<InputEventMouseButton> mb = p_event;
-
- if (mb.is_valid()) {
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (mb->is_pressed()) {
- if (Input::get_singleton()->is_key_pressed(KEY_SPACE)) {
- return false; // Drag.
- }
-
- if (tool == TOOL_NONE) {
- tool = TOOL_PAINTING;
- _update_button_tool();
-
- if (mb->get_command()) {
- tool = TOOL_PICKING;
- _pick_tile(over_tile);
- _update_button_tool();
-
- return true;
- }
- }
-
- if (tool == TOOL_LINE_PAINT || tool == TOOL_RECTANGLE_PAINT) {
- selection_active = false;
- rectangle_begin = over_tile;
-
- mouse_down = true;
- } else if (tool == TOOL_PAINTING) {
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- tool = TOOL_PAINTING;
-
- _start_undo(TTR("Paint TileMap"));
- }
- } else if (tool == TOOL_PICKING) {
- _pick_tile(over_tile);
- } else if (tool == TOOL_SELECTING) {
- selection_active = true;
- rectangle_begin = over_tile;
- }
-
- _update_button_tool();
- return true;
-
- } else {
- // Mousebutton was released.
- if (tool != TOOL_NONE) {
- if (tool == TOOL_PAINTING) {
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _set_cell(over_tile, ids, flip_h, flip_v, transpose);
- _finish_undo();
-
- paint_undo.clear();
- }
- } else if (tool == TOOL_LINE_PAINT) {
- if (!mouse_down) {
- return true;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Line Draw"));
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- _set_cell(E->key(), ids, flip_h, flip_v, transpose);
- }
- _finish_undo();
-
- paint_undo.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- } else if (tool == TOOL_RECTANGLE_PAINT) {
- if (!mouse_down) {
- return true;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Rectangle Paint"));
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _set_cell(Point2i(j, i), ids, flip_h, flip_v, transpose);
- }
- }
- _finish_undo();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- } else if (tool == TOOL_PASTING) {
- Point2 ofs = over_tile - rectangle.position;
- Vector<int> ids;
-
- _start_undo(TTR("Paste"));
- ids.push_back(0);
- for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
- ids.write[0] = E->get().cell;
- _set_cell(E->get().pos + ofs, ids, E->get().flip_h, E->get().flip_v, E->get().transpose, E->get().autotile_coord);
- }
- _finish_undo();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- return true; // We want to keep the Pasting tool.
- } else if (tool == TOOL_SELECTING) {
- CanvasItemEditor::get_singleton()->update_viewport();
-
- } else if (tool == TOOL_BUCKET) {
- Vector<Vector2> points = _bucket_fill(over_tile);
-
- if (points.size() == 0) {
- return false;
- }
-
- _start_undo(TTR("Bucket Fill"));
-
- Dictionary op;
- op["id"] = get_selected_tiles();
- op["flip_h"] = flip_h;
- op["flip_v"] = flip_v;
- op["transpose"] = transpose;
-
- _fill_points(points, op);
-
- _finish_undo();
-
- // So the fill preview is cleared right after the click.
- CanvasItemEditor::get_singleton()->update_viewport();
-
- // We want to keep the bucket-tool active.
- return true;
- }
-
- tool = TOOL_NONE;
- _update_button_tool();
-
- return true;
- }
- }
- } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- if (mb->is_pressed()) {
- if (tool == TOOL_SELECTING || selection_active) {
- tool = TOOL_NONE;
- selection_active = false;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_PASTING) {
- tool = TOOL_NONE;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_LINE_PAINT) {
- tool = TOOL_LINE_ERASE;
- mouse_down = true;
- rectangle_begin = over_tile;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_RECTANGLE_PAINT) {
- tool = TOOL_RECTANGLE_ERASE;
- mouse_down = true;
- rectangle_begin = over_tile;
- copydata.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (tool == TOOL_NONE) {
- paint_undo.clear();
-
- Point2 local = node->world_to_map(xform_inv.xform(mb->get_position()));
-
- _start_undo(TTR("Erase TileMap"));
- tool = TOOL_ERASING;
- _set_cell(local, invalid_cell);
-
- _update_button_tool();
- return true;
- }
-
- } else {
- if (tool == TOOL_LINE_ERASE) {
- if (!mouse_down) {
- return true;
- }
-
- tool = TOOL_LINE_PAINT;
- _update_button_tool();
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Line Erase"));
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- _set_cell(E->key(), invalid_cell, flip_h, flip_v, transpose);
- }
- _finish_undo();
- paint_undo.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- } else if (tool == TOOL_RECTANGLE_ERASE) {
- if (!mouse_down) {
- return true;
- }
-
- tool = TOOL_RECTANGLE_PAINT;
- _update_button_tool();
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- _start_undo(TTR("Rectangle Erase"));
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _set_cell(Point2i(j, i), invalid_cell, flip_h, flip_v, transpose);
- }
- }
- _finish_undo();
- paint_undo.clear();
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- mouse_down = false;
- return true;
- }
-
- mouse_down = false;
- tool = TOOL_RECTANGLE_PAINT;
- }
-
- if (tool == TOOL_ERASING) {
- tool = TOOL_NONE;
- _update_button_tool();
-
- return true;
- } else if (tool == TOOL_BUCKET) {
- Vector<int> ids;
- ids.push_back(node->get_cell(over_tile.x, over_tile.y));
- Dictionary pop;
- pop["id"] = ids;
- pop["flip_h"] = node->is_cell_x_flipped(over_tile.x, over_tile.y);
- pop["flip_v"] = node->is_cell_y_flipped(over_tile.x, over_tile.y);
- pop["transpose"] = node->is_cell_transposed(over_tile.x, over_tile.y);
-
- Vector<Vector2> points = _bucket_fill(over_tile, true);
-
- if (points.size() == 0) {
- return false;
- }
-
- undo_redo->create_action(TTR("Bucket Fill"));
-
- undo_redo->add_do_method(this, "_erase_points", points);
- undo_redo->add_undo_method(this, "_fill_points", points, pop);
-
- undo_redo->commit_action();
- }
- }
- }
- }
-
- Ref<InputEventMouseMotion> mm = p_event;
-
- if (mm.is_valid()) {
- Point2i new_over_tile = node->world_to_map(xform_inv.xform(mm->get_position()));
- Point2i old_over_tile = over_tile;
-
- if (new_over_tile != over_tile) {
- over_tile = new_over_tile;
- CanvasItemEditor::get_singleton()->update_viewport();
- }
-
- if (refocus_over_tile) {
- // editor lost focus; forget last tile position
- old_over_tile = new_over_tile;
- refocus_over_tile = false;
- }
-
- int tile_under = node->get_cell(over_tile.x, over_tile.y);
- String tile_name = "none";
-
- if (node->get_tileset()->has_tile(tile_under)) {
- tile_name = node->get_tileset()->tile_get_name(tile_under);
- }
- tile_info->show();
- tile_info->set_text(String::num(over_tile.x) + ", " + String::num(over_tile.y) + " [" + tile_name + "]");
-
- if (tool == TOOL_PAINTING) {
- // Paint using bresenham line to prevent holes in painting if the user moves fast.
-
- Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y);
- Vector<int> ids = get_selected_tiles();
-
- for (int i = 0; i < points.size(); ++i) {
- Point2i pos = points[i];
-
- if (!paint_undo.has(pos)) {
- paint_undo[pos] = _get_op_from_cell(pos);
- }
-
- _set_cell(pos, ids, flip_h, flip_v, transpose);
- }
-
- return true;
- }
-
- if (tool == TOOL_ERASING) {
- // Erase using bresenham line to prevent holes in painting if the user moves fast.
-
- Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y);
-
- for (int i = 0; i < points.size(); ++i) {
- Point2i pos = points[i];
-
- _set_cell(pos, invalid_cell);
- }
-
- return true;
- }
-
- if (tool == TOOL_SELECTING) {
- _select(rectangle_begin, over_tile);
-
- return true;
- }
-
- if (tool == TOOL_LINE_PAINT || tool == TOOL_LINE_ERASE) {
- Vector<int> ids = get_selected_tiles();
- Vector<int> tmp_cell;
- bool erasing = (tool == TOOL_LINE_ERASE);
-
- if (!mouse_down) {
- return true;
- }
-
- tmp_cell.push_back(0);
- if (erasing && paint_undo.size()) {
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- tmp_cell.write[0] = E->get().idx;
- _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr);
- }
- }
-
- paint_undo.clear();
-
- if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) {
- Vector<Point2i> points = line(rectangle_begin.x, over_tile.x, rectangle_begin.y, over_tile.y);
-
- for (int i = 0; i < points.size(); i++) {
- paint_undo[points[i]] = _get_op_from_cell(points[i]);
-
- if (erasing) {
- _set_cell(points[i], invalid_cell);
- }
- }
-
- CanvasItemEditor::get_singleton()->update_viewport();
- }
-
- return true;
- }
- if (tool == TOOL_RECTANGLE_PAINT || tool == TOOL_RECTANGLE_ERASE) {
- Vector<int> tmp_cell;
- tmp_cell.push_back(0);
-
- Point2i end_tile = over_tile;
-
- if (!mouse_down) {
- return true;
- }
-
- if (mm->get_shift()) {
- int size = fmax(ABS(end_tile.x - rectangle_begin.x), ABS(end_tile.y - rectangle_begin.y));
- int xDirection = MAX(MIN(end_tile.x - rectangle_begin.x, 1), -1);
- int yDirection = MAX(MIN(end_tile.y - rectangle_begin.y, 1), -1);
- end_tile = rectangle_begin + Point2i(xDirection * size, yDirection * size);
- }
-
- _select(rectangle_begin, end_tile);
-
- if (tool == TOOL_RECTANGLE_ERASE) {
- if (paint_undo.size()) {
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- tmp_cell.write[0] = E->get().idx;
- _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr);
- }
- }
-
- paint_undo.clear();
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- Point2i tile = Point2i(j, i);
- paint_undo[tile] = _get_op_from_cell(tile);
-
- _set_cell(tile, invalid_cell);
- }
- }
- }
-
- return true;
- }
- if (tool == TOOL_PICKING && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) {
- _pick_tile(over_tile);
-
- return true;
- }
- }
-
- Ref<InputEventKey> k = p_event;
-
- if (k.is_valid() && k->is_pressed()) {
- if (last_tool == TOOL_NONE && tool == TOOL_PICKING && k->get_keycode() == KEY_SHIFT && k->get_command()) {
- // trying to draw a rectangle with the painting tool, so change to the correct tool
- tool = last_tool;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- _update_button_tool();
- }
-
- if (k->get_keycode() == KEY_ESCAPE) {
- if (tool == TOOL_PASTING) {
- copydata.clear();
- } else if (tool == TOOL_SELECTING || selection_active) {
- selection_active = false;
- }
-
- tool = TOOL_NONE;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
-
- if (!mouse_over) {
- // Editor shortcuts should not fire if mouse not in viewport.
- return false;
- }
-
- if (ED_IS_SHORTCUT("tile_map_editor/paint_tile", p_event)) {
- // NOTE: We do not set tool = TOOL_PAINTING as this begins painting
- // immediately without pressing the left mouse button first.
- tool = TOOL_NONE;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/line_fill", p_event)) {
- tool = TOOL_LINE_PAINT;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/rectangle_fill", p_event)) {
- tool = TOOL_RECTANGLE_PAINT;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/bucket_fill", p_event)) {
- tool = TOOL_BUCKET;
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/erase_selection", p_event)) {
- _menu_option(OPTION_ERASE_SELECTION);
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/select", p_event)) {
- tool = TOOL_SELECTING;
- selection_active = false;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/copy_selection", p_event)) {
- _update_copydata();
-
- if (selection_active) {
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
-
- _update_button_tool();
- return true;
- }
- }
- if (ED_IS_SHORTCUT("tile_map_editor/cut_selection", p_event)) {
- if (selection_active) {
- _update_copydata();
-
- _start_undo(TTR("Cut Selection"));
- _erase_selection();
- _finish_undo();
-
- selection_active = false;
-
- tool = TOOL_PASTING;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- _update_button_tool();
- return true;
- }
- }
- if (ED_IS_SHORTCUT("tile_map_editor/find_tile", p_event)) {
- search_box->select_all();
- search_box->grab_focus();
-
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/rotate_left", p_event)) {
- _rotate(-1);
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/rotate_right", p_event)) {
- _rotate(1);
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/flip_horizontal", p_event)) {
- _flip_horizontal();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/flip_vertical", p_event)) {
- _flip_vertical();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/clear_transform", p_event)) {
- _clear_transform();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- if (ED_IS_SHORTCUT("tile_map_editor/transpose", p_event)) {
- transpose = !transpose;
- _update_palette();
- CanvasItemEditor::get_singleton()->update_viewport();
- return true;
- }
- } else if (k.is_valid()) { // Release event.
-
- if (tool == TOOL_NONE) {
- if (k->get_keycode() == KEY_SHIFT && k->get_command()) {
- tool = TOOL_PICKING;
- _update_button_tool();
- }
- } else if (tool == TOOL_PICKING) {
-#ifdef APPLE_STYLE_KEYS
- if (k->get_keycode() == KEY_META) {
-#else
- if (k->get_keycode() == KEY_CONTROL) {
-#endif
- // Go back to that last tool if KEY_CONTROL was released.
- tool = last_tool;
-
- CanvasItemEditor::get_singleton()->update_viewport();
- _update_button_tool();
- }
- }
- }
- return false;
-}
-
-void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
- if (!node || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
- return;
- }
-
- Transform2D cell_xf = node->get_cell_transform();
- Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform();
- Transform2D xform_inv = xform.affine_inverse();
-
- Size2 screen_size = p_overlay->get_size();
- {
- Rect2 aabb;
- aabb.position = node->world_to_map(xform_inv.xform(Vector2()));
- aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(0, screen_size.height))));
- aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0))));
- aabb.expand_to(node->world_to_map(xform_inv.xform(screen_size)));
- Rect2i si = aabb.grow(1.0);
-
- if (node->get_half_offset() != TileMap::HALF_OFFSET_X && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_X) {
- int max_lines = 2000; //avoid crash if size too small
-
- for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) {
- Vector2 from = xform.xform(node->map_to_world(Vector2(i, si.position.y)));
- Vector2 to = xform.xform(node->map_to_world(Vector2(i, si.position.y + si.size.y + 1)));
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
- if (max_lines-- == 0) {
- break;
- }
- }
- } else {
- int max_lines = 10000; //avoid crash if size too small
-
- for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) {
- for (int j = (si.position.y) - 1; j <= (si.position.y + si.size.y); j++) {
- Vector2 ofs;
- if (ABS(j) & 1) {
- ofs = cell_xf[0] * (node->get_half_offset() == TileMap::HALF_OFFSET_X ? 0.5 : -0.5);
- }
-
- Vector2 from = xform.xform(node->map_to_world(Vector2(i, j), true) + ofs);
- Vector2 to = xform.xform(node->map_to_world(Vector2(i, j + 1), true) + ofs);
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
-
- if (--max_lines == 0) {
- break;
- }
- }
- if (max_lines == 0) {
- break;
- }
- }
- }
-
- int max_lines = 10000; //avoid crash if size too small
-
- if (node->get_half_offset() != TileMap::HALF_OFFSET_Y && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_Y) {
- for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) {
- Vector2 from = xform.xform(node->map_to_world(Vector2(si.position.x, i)));
- Vector2 to = xform.xform(node->map_to_world(Vector2(si.position.x + si.size.x + 1, i)));
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
-
- if (max_lines-- == 0) {
- break;
- }
- }
- } else {
- for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) {
- for (int j = (si.position.x) - 1; j <= (si.position.x + si.size.x); j++) {
- Vector2 ofs;
- if (ABS(j) & 1) {
- ofs = cell_xf[1] * (node->get_half_offset() == TileMap::HALF_OFFSET_Y ? 0.5 : -0.5);
- }
-
- Vector2 from = xform.xform(node->map_to_world(Vector2(j, i), true) + ofs);
- Vector2 to = xform.xform(node->map_to_world(Vector2(j + 1, i), true) + ofs);
-
- Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2);
- p_overlay->draw_line(from, to, col, 1);
-
- if (--max_lines == 0) {
- break;
- }
- }
- if (max_lines == 0) {
- break;
- }
- }
- }
- }
-
- if (selection_active) {
- Vector<Vector2> points;
- points.push_back(xform.xform(node->map_to_world((rectangle.position))));
- points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, 0)))));
- points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, rectangle.size.y + 1)))));
- points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(0, rectangle.size.y + 1)))));
-
- p_overlay->draw_colored_polygon(points, Color(0.2, 0.8, 1, 0.4));
- }
-
- if (mouse_over && node->get_tileset().is_valid()) {
- Vector2 endpoints[4] = {
- node->map_to_world(over_tile, true),
- node->map_to_world((over_tile + Point2(1, 0)), true),
- node->map_to_world((over_tile + Point2(1, 1)), true),
- node->map_to_world((over_tile + Point2(0, 1)), true)
- };
-
- for (int i = 0; i < 4; i++) {
- if (node->get_half_offset() == TileMap::HALF_OFFSET_X && ABS(over_tile.y) & 1) {
- endpoints[i] += cell_xf[0] * 0.5;
- }
- if (node->get_half_offset() == TileMap::HALF_OFFSET_NEGATIVE_X && ABS(over_tile.y) & 1) {
- endpoints[i] += cell_xf[0] * -0.5;
- }
- if (node->get_half_offset() == TileMap::HALF_OFFSET_Y && ABS(over_tile.x) & 1) {
- endpoints[i] += cell_xf[1] * 0.5;
- }
- if (node->get_half_offset() == TileMap::HALF_OFFSET_NEGATIVE_Y && ABS(over_tile.x) & 1) {
- endpoints[i] += cell_xf[1] * -0.5;
- }
- endpoints[i] = xform.xform(endpoints[i]);
- }
- Color col;
- if (node->get_cell(over_tile.x, over_tile.y) != TileMap::INVALID_CELL) {
- col = Color(0.2, 0.8, 1.0, 0.8);
- } else {
- col = Color(1.0, 0.4, 0.2, 0.8);
- }
-
- for (int i = 0; i < 4; i++) {
- p_overlay->draw_line(endpoints[i], endpoints[(i + 1) % 4], col, 2);
- }
-
- bool bucket_preview = EditorSettings::get_singleton()->get("editors/tile_map/bucket_fill_preview");
- if (tool == TOOL_SELECTING || tool == TOOL_PICKING || !bucket_preview) {
- return;
- }
-
- if (tool == TOOL_LINE_PAINT) {
- if (!mouse_down) {
- return;
- }
-
- if (paint_undo.is_empty()) {
- return;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) {
- return;
- }
-
- for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
- _draw_cell(p_overlay, ids[0], E->key(), flip_h, flip_v, transpose, autotile_coord, xform);
- }
-
- } else if (tool == TOOL_RECTANGLE_PAINT) {
- if (!mouse_down) {
- return;
- }
-
- Vector<int> ids = get_selected_tiles();
-
- if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) {
- return;
- }
-
- for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
- for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
- _draw_cell(p_overlay, ids[0], Point2i(j, i), flip_h, flip_v, transpose, autotile_coord, xform);
- }
- }
- } else if (tool == TOOL_PASTING) {
- if (copydata.is_empty()) {
- return;
- }
-
- Ref<TileSet> ts = node->get_tileset();
-
- if (ts.is_null()) {
- return;
- }
-
- Point2 ofs = over_tile - rectangle.position;
-
- for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
- if (!ts->has_tile(E->get().cell)) {
- continue;
- }
-
- TileData tcd = E->get();
-
- _draw_cell(p_overlay, tcd.cell, tcd.pos + ofs, tcd.flip_h, tcd.flip_v, tcd.transpose, tcd.autotile_coord, xform);
- }
-
- Rect2i duplicate = rectangle;
- duplicate.position = over_tile;
-
- Vector<Vector2> points;
- points.push_back(xform.xform(node->map_to_world(duplicate.position)));
- points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, 0)))));
- points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, duplicate.size.y + 1)))));
- points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(0, duplicate.size.y + 1)))));
-
- p_overlay->draw_colored_polygon(points, Color(0.2, 1.0, 0.8, 0.2));
-
- } else if (tool == TOOL_BUCKET) {
- Vector<int> tiles = get_selected_tiles();
- _draw_fill_preview(p_overlay, tiles[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform);
-
- } else {
- Vector<int> st = get_selected_tiles();
-
- if (st.size() == 1 && st[0] == TileMap::INVALID_CELL) {
- return;
- }
-
- _draw_cell(p_overlay, st[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform);
- }
- }
-}
-
-void TileMapEditor::edit(Node *p_tile_map) {
- search_box->set_text("");
-
- if (!canvas_item_editor_viewport) {
- canvas_item_editor_viewport = CanvasItemEditor::get_singleton()->get_viewport_control();
- }
-
- if (node) {
- Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed);
-
- if (node->is_connected("settings_changed", callable_tileset_settings_changed)) {
- node->disconnect("settings_changed", callable_tileset_settings_changed);
- }
- }
- if (p_tile_map) {
- node = Object::cast_to<TileMap>(p_tile_map);
- if (!canvas_item_editor_viewport->is_connected("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter))) {
- canvas_item_editor_viewport->connect("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter));
- }
- if (!canvas_item_editor_viewport->is_connected("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit))) {
- canvas_item_editor_viewport->connect("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit));
- }
-
- _update_palette();
-
- } else {
- node = nullptr;
-
- if (canvas_item_editor_viewport->is_connected("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter))) {
- canvas_item_editor_viewport->disconnect("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter));
- }
- if (canvas_item_editor_viewport->is_connected("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit))) {
- canvas_item_editor_viewport->disconnect("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit));
- }
-
- _update_palette();
- }
-
- if (node) {
- Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed);
-
- if (!node->is_connected("settings_changed", callable_tileset_settings_changed)) {
- node->connect("settings_changed", callable_tileset_settings_changed);
- }
- }
-
- _clear_bucket_cache();
-}
-
-void TileMapEditor::_tileset_settings_changed() {
- _update_palette();
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
-void TileMapEditor::_icon_size_changed(float p_value) {
- if (node) {
- palette->set_icon_scale(p_value);
- manual_palette->set_icon_scale(p_value);
- _update_palette();
- }
-}
-
-void TileMapEditor::_bind_methods() {
- ClassDB::bind_method(D_METHOD("_fill_points"), &TileMapEditor::_fill_points);
- ClassDB::bind_method(D_METHOD("_erase_points"), &TileMapEditor::_erase_points);
-}
-
-TileMapEditor::CellOp TileMapEditor::_get_op_from_cell(const Point2i &p_pos) {
- CellOp op;
- op.idx = node->get_cell(p_pos.x, p_pos.y);
- if (op.idx != TileMap::INVALID_CELL) {
- if (node->is_cell_x_flipped(p_pos.x, p_pos.y)) {
- op.xf = true;
- }
- if (node->is_cell_y_flipped(p_pos.x, p_pos.y)) {
- op.yf = true;
- }
- if (node->is_cell_transposed(p_pos.x, p_pos.y)) {
- op.tr = true;
- }
- op.ac = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
- }
- return op;
-}
-
-void TileMapEditor::_rotate(int steps) {
- const bool normal_rotation_matrix[][3] = {
- { false, false, false },
- { true, true, false },
- { false, true, true },
- { true, false, true }
- };
-
- const bool mirrored_rotation_matrix[][3] = {
- { false, true, false },
- { true, true, true },
- { false, false, true },
- { true, false, false }
- };
-
- if (transpose ^ flip_h ^ flip_v) {
- // Odd number of flags activated = mirrored rotation
- for (int i = 0; i < 4; i++) {
- if (transpose == mirrored_rotation_matrix[i][0] &&
- flip_h == mirrored_rotation_matrix[i][1] &&
- flip_v == mirrored_rotation_matrix[i][2]) {
- int new_id = Math::wrapi(i + steps, 0, 4);
- transpose = mirrored_rotation_matrix[new_id][0];
- flip_h = mirrored_rotation_matrix[new_id][1];
- flip_v = mirrored_rotation_matrix[new_id][2];
- break;
- }
- }
- } else {
- // Even number of flags activated = normal rotation
- for (int i = 0; i < 4; i++) {
- if (transpose == normal_rotation_matrix[i][0] &&
- flip_h == normal_rotation_matrix[i][1] &&
- flip_v == normal_rotation_matrix[i][2]) {
- int new_id = Math::wrapi(i + steps, 0, 4);
- transpose = normal_rotation_matrix[new_id][0];
- flip_h = normal_rotation_matrix[new_id][1];
- flip_v = normal_rotation_matrix[new_id][2];
- break;
- }
- }
- }
-
- _update_palette();
-}
-
-void TileMapEditor::_flip_horizontal() {
- flip_h = !flip_h;
- _update_palette();
-}
-
-void TileMapEditor::_flip_vertical() {
- flip_v = !flip_v;
- _update_palette();
-}
-
-void TileMapEditor::_clear_transform() {
- transpose = false;
- flip_h = false;
- flip_v = false;
- _update_palette();
-}
-
-TileMapEditor::TileMapEditor(EditorNode *p_editor) {
- node = nullptr;
- manual_autotile = false;
- priority_atlastile = false;
- manual_position = Vector2(0, 0);
- canvas_item_editor_viewport = nullptr;
- editor = p_editor;
- undo_redo = EditorNode::get_undo_redo();
-
- tool = TOOL_NONE;
- selection_active = false;
- mouse_over = false;
- mouse_down = false;
-
- flip_h = false;
- flip_v = false;
- transpose = false;
-
- bucket_cache_tile = -1;
- bucket_cache_visited = nullptr;
-
- invalid_cell.resize(1);
- invalid_cell.write[0] = TileMap::INVALID_CELL;
-
- ED_SHORTCUT("tile_map_editor/erase_selection", TTR("Erase Selection"), KEY_DELETE);
- ED_SHORTCUT("tile_map_editor/find_tile", TTR("Find Tile"), KEY_MASK_CMD + KEY_F);
- ED_SHORTCUT("tile_map_editor/transpose", TTR("Transpose"), KEY_T);
-
- HBoxContainer *tool_hb = memnew(HBoxContainer);
- add_child(tool_hb);
-
- manual_button = memnew(CheckBox);
- manual_button->set_text(TTR("Disable Autotile"));
- manual_button->connect("toggled", callable_mp(this, &TileMapEditor::_manual_toggled));
- add_child(manual_button);
-
- priority_button = memnew(CheckBox);
- priority_button->set_text(TTR("Enable Priority"));
- priority_button->connect("toggled", callable_mp(this, &TileMapEditor::_priority_toggled));
- add_child(priority_button);
-
- search_box = memnew(LineEdit);
- search_box->set_placeholder(TTR("Filter tiles"));
- search_box->set_h_size_flags(SIZE_EXPAND_FILL);
- search_box->connect("text_entered", callable_mp(this, &TileMapEditor::_text_entered));
- search_box->connect("text_changed", callable_mp(this, &TileMapEditor::_text_changed));
- search_box->connect("gui_input", callable_mp(this, &TileMapEditor::_sbox_input));
- add_child(search_box);
-
- size_slider = memnew(HSlider);
- size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
- size_slider->set_min(0.1f);
- size_slider->set_max(4.0f);
- size_slider->set_step(0.1f);
- size_slider->set_value(1.0f);
- size_slider->connect("value_changed", callable_mp(this, &TileMapEditor::_icon_size_changed));
- add_child(size_slider);
-
- int mw = EDITOR_DEF("editors/tile_map/palette_min_width", 80);
-
- VSplitContainer *palette_container = memnew(VSplitContainer);
- palette_container->set_v_size_flags(SIZE_EXPAND_FILL);
- palette_container->set_custom_minimum_size(Size2(mw, 0));
- add_child(palette_container);
-
- // Add tile palette.
- palette = memnew(ItemList);
- palette->set_h_size_flags(SIZE_EXPAND_FILL);
- palette->set_v_size_flags(SIZE_EXPAND_FILL);
- palette->set_max_columns(0);
- palette->set_icon_mode(ItemList::ICON_MODE_TOP);
- palette->set_max_text_lines(2);
- palette->set_select_mode(ItemList::SELECT_MULTI);
- palette->add_theme_constant_override("vseparation", 8 * EDSCALE);
- palette->connect("item_selected", callable_mp(this, &TileMapEditor::_palette_selected));
- palette->connect("multi_selected", callable_mp(this, &TileMapEditor::_palette_multi_selected));
- palette->connect("gui_input", callable_mp(this, &TileMapEditor::_palette_input));
- palette_container->add_child(palette);
-
- // Add message for when no texture is selected.
- info_message = memnew(Label);
- info_message->set_text(TTR("Give a TileSet resource to this TileMap to use its tiles."));
- info_message->set_valign(Label::VALIGN_CENTER);
- info_message->set_align(Label::ALIGN_CENTER);
- info_message->set_autowrap(true);
- info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
- info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
- palette->add_child(info_message);
-
- // Add autotile override palette.
- manual_palette = memnew(ItemList);
- manual_palette->set_h_size_flags(SIZE_EXPAND_FILL);
- manual_palette->set_v_size_flags(SIZE_EXPAND_FILL);
- manual_palette->set_max_columns(0);
- manual_palette->set_icon_mode(ItemList::ICON_MODE_TOP);
- manual_palette->set_max_text_lines(2);
- manual_palette->hide();
- palette_container->add_child(manual_palette);
-
- // Add menu items.
- toolbar = memnew(HBoxContainer);
- toolbar->hide();
- CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar);
-
- toolbar->add_child(memnew(VSeparator));
-
- // Tools.
- paint_button = memnew(Button);
- paint_button->set_flat(true);
- paint_button->set_shortcut(ED_SHORTCUT("tile_map_editor/paint_tile", TTR("Paint Tile"), KEY_P));
- paint_button->set_shortcut_context(this);
- paint_button->set_tooltip(TTR("RMB: Erase"));
- paint_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_NONE));
- paint_button->set_toggle_mode(true);
- toolbar->add_child(paint_button);
-
- line_button = memnew(Button);
- line_button->set_flat(true);
- line_button->set_shortcut(ED_SHORTCUT("tile_map_editor/line_fill", TTR("Line Fill"), KEY_L));
- line_button->set_shortcut_context(this);
- line_button->set_tooltip(TTR("RMB: Erase"));
- line_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_LINE_PAINT));
- line_button->set_toggle_mode(true);
- toolbar->add_child(line_button);
-
- rectangle_button = memnew(Button);
- rectangle_button->set_flat(true);
- rectangle_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rectangle_fill", TTR("Rectangle Fill"), KEY_O));
- rectangle_button->set_shortcut_context(this);
- rectangle_button->set_tooltip(TTR("Shift+LMB: Keep 1:1 proporsions\nRMB: Erase"));
- rectangle_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_RECTANGLE_PAINT));
- rectangle_button->set_toggle_mode(true);
- toolbar->add_child(rectangle_button);
-
- bucket_fill_button = memnew(Button);
- bucket_fill_button->set_flat(true);
- bucket_fill_button->set_shortcut(ED_SHORTCUT("tile_map_editor/bucket_fill", TTR("Bucket Fill"), KEY_B));
- bucket_fill_button->set_shortcut_context(this);
- bucket_fill_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_BUCKET));
- bucket_fill_button->set_toggle_mode(true);
- toolbar->add_child(bucket_fill_button);
-
- picker_button = memnew(Button);
- picker_button->set_flat(true);
- picker_button->set_shortcut(ED_SHORTCUT("tile_map_editor/pick_tile", TTR("Pick Tile"), KEY_I));
- picker_button->set_shortcut_context(this);
- picker_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_PICKING));
- picker_button->set_toggle_mode(true);
- toolbar->add_child(picker_button);
-
- select_button = memnew(Button);
- select_button->set_flat(true);
- select_button->set_shortcut(ED_SHORTCUT("tile_map_editor/select", TTR("Select"), KEY_M));
- select_button->set_shortcut_context(this);
- select_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_SELECTING));
- select_button->set_toggle_mode(true);
- toolbar->add_child(select_button);
-
- _update_button_tool();
-
- // Container to the right of the toolbar.
- toolbar_right = memnew(HBoxContainer);
- toolbar_right->hide();
- toolbar_right->set_h_size_flags(SIZE_EXPAND_FILL);
- toolbar_right->set_alignment(BoxContainer::ALIGN_END);
- CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar_right);
-
- // Tile position.
- tile_info = memnew(Label);
- tile_info->set_modulate(Color(1, 1, 1, 0.8));
- tile_info->set_mouse_filter(MOUSE_FILTER_IGNORE);
- tile_info->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
- tile_info->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts"));
- // The tile info is only displayed after a tile has been hovered.
- tile_info->hide();
- CanvasItemEditor::get_singleton()->add_control_to_info_overlay(tile_info);
-
- // Menu.
- options = memnew(MenuButton);
- options->set_shortcut_context(this);
- options->set_text("TileMap");
- options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("TileMap", "EditorIcons"));
- toolbar_right->add_child(options);
-
- PopupMenu *p = options->get_popup();
- p->add_shortcut(ED_SHORTCUT("tile_map_editor/cut_selection", TTR("Cut Selection"), KEY_MASK_CMD + KEY_X), OPTION_CUT);
- p->add_shortcut(ED_SHORTCUT("tile_map_editor/copy_selection", TTR("Copy Selection"), KEY_MASK_CMD + KEY_C), OPTION_COPY);
- p->add_shortcut(ED_GET_SHORTCUT("tile_map_editor/erase_selection"), OPTION_ERASE_SELECTION);
- p->add_separator();
- p->add_item(TTR("Fix Invalid Tiles"), OPTION_FIX_INVALID);
- p->connect("id_pressed", callable_mp(this, &TileMapEditor::_menu_option));
-
- rotate_left_button = memnew(Button);
- rotate_left_button->set_flat(true);
- rotate_left_button->set_tooltip(TTR("Rotate Left"));
- rotate_left_button->set_focus_mode(FOCUS_NONE);
- rotate_left_button->connect("pressed", callable_mp(this, &TileMapEditor::_rotate), varray(-1));
- rotate_left_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_left", TTR("Rotate Left"), KEY_A));
- rotate_left_button->set_shortcut_context(this);
- tool_hb->add_child(rotate_left_button);
-
- rotate_right_button = memnew(Button);
- rotate_right_button->set_flat(true);
- rotate_right_button->set_tooltip(TTR("Rotate Right"));
- rotate_right_button->set_focus_mode(FOCUS_NONE);
- rotate_right_button->connect("pressed", callable_mp(this, &TileMapEditor::_rotate), varray(1));
- rotate_right_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_right", TTR("Rotate Right"), KEY_S));
- rotate_right_button->set_shortcut_context(this);
- tool_hb->add_child(rotate_right_button);
-
- flip_horizontal_button = memnew(Button);
- flip_horizontal_button->set_flat(true);
- flip_horizontal_button->set_tooltip(TTR("Flip Horizontally"));
- flip_horizontal_button->set_focus_mode(FOCUS_NONE);
- flip_horizontal_button->connect("pressed", callable_mp(this, &TileMapEditor::_flip_horizontal));
- flip_horizontal_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_horizontal", TTR("Flip Horizontally"), KEY_X));
- flip_horizontal_button->set_shortcut_context(this);
- tool_hb->add_child(flip_horizontal_button);
-
- flip_vertical_button = memnew(Button);
- flip_vertical_button->set_flat(true);
- flip_vertical_button->set_tooltip(TTR("Flip Vertically"));
- flip_vertical_button->set_focus_mode(FOCUS_NONE);
- flip_vertical_button->connect("pressed", callable_mp(this, &TileMapEditor::_flip_vertical));
- flip_vertical_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_vertical", TTR("Flip Vertically"), KEY_Z));
- flip_vertical_button->set_shortcut_context(this);
- tool_hb->add_child(flip_vertical_button);
-
- clear_transform_button = memnew(Button);
- clear_transform_button->set_flat(true);
- clear_transform_button->set_tooltip(TTR("Clear Transform"));
- clear_transform_button->set_focus_mode(FOCUS_NONE);
- clear_transform_button->connect("pressed", callable_mp(this, &TileMapEditor::_clear_transform));
- clear_transform_button->set_shortcut(ED_SHORTCUT("tile_map_editor/clear_transform", TTR("Clear Transform"), KEY_W));
- clear_transform_button->set_shortcut_context(this);
- tool_hb->add_child(clear_transform_button);
-
- clear_transform_button->set_disabled(true);
-}
-
-TileMapEditor::~TileMapEditor() {
- _clear_bucket_cache();
- copydata.clear();
-}
-
-///////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////
-
-void TileMapEditorPlugin::_notification(int p_what) {
- if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
- switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) {
- case 0: { // Left.
- CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 0);
- } break;
- case 1: { // Right.
- CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 1);
- } break;
- }
- }
-}
-
-void TileMapEditorPlugin::edit(Object *p_object) {
- tile_map_editor->edit(Object::cast_to<Node>(p_object));
-}
-
-bool TileMapEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("TileMap");
-}
-
-void TileMapEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- tile_map_editor->show();
- tile_map_editor->get_toolbar()->show();
- tile_map_editor->get_toolbar_right()->show();
- // `tile_info` isn't shown here, as it's displayed after a tile has been hovered.
- // Otherwise, a translucent black rectangle would be visible as there would be an
- // empty Label in the CanvasItemEditor's info overlay.
-
- // Change to TOOL_SELECT when TileMap node is selected, to prevent accidental movement.
- CanvasItemEditor::get_singleton()->set_current_tool(CanvasItemEditor::TOOL_SELECT);
- } else {
- tile_map_editor->hide();
- tile_map_editor->get_toolbar()->hide();
- tile_map_editor->get_toolbar_right()->hide();
- tile_map_editor->get_tile_info()->hide();
- tile_map_editor->edit(nullptr);
- }
-}
-
-TileMapEditorPlugin::TileMapEditorPlugin(EditorNode *p_node) {
- EDITOR_DEF("editors/tile_map/preview_size", 64);
- EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8);
- EDITOR_DEF("editors/tile_map/show_tile_names", true);
- EDITOR_DEF("editors/tile_map/show_tile_ids", false);
- EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true);
- EDITOR_DEF("editors/tile_map/bucket_fill_preview", true);
- EDITOR_DEF("editors/tile_map/editor_side", 1);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/tile_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right"));
-
- tile_map_editor = memnew(TileMapEditor(p_node));
- switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) {
- case 0: { // Left.
- add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_LEFT, tile_map_editor);
- } break;
- case 1: { // Right.
- add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_RIGHT, tile_map_editor);
- } break;
- }
- tile_map_editor->hide();
-}
-
-TileMapEditorPlugin::~TileMapEditorPlugin() {
-}
diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h
deleted file mode 100644
index 421a3b3f68..0000000000
--- a/editor/plugins/tile_map_editor_plugin.h
+++ /dev/null
@@ -1,242 +0,0 @@
-/*************************************************************************/
-/* tile_map_editor_plugin.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 TILE_MAP_EDITOR_PLUGIN_H
-#define TILE_MAP_EDITOR_PLUGIN_H
-
-#include "editor/editor_node.h"
-#include "editor/editor_plugin.h"
-#include "scene/2d/tile_map.h"
-#include "scene/gui/check_box.h"
-#include "scene/gui/label.h"
-#include "scene/gui/line_edit.h"
-#include "scene/gui/menu_button.h"
-
-class TileMapEditor : public VBoxContainer {
- GDCLASS(TileMapEditor, VBoxContainer);
-
- enum Tool {
- TOOL_NONE,
- TOOL_PAINTING,
- TOOL_ERASING,
- TOOL_RECTANGLE_PAINT,
- TOOL_RECTANGLE_ERASE,
- TOOL_LINE_PAINT,
- TOOL_LINE_ERASE,
- TOOL_SELECTING,
- TOOL_BUCKET,
- TOOL_PICKING,
- TOOL_PASTING
- };
-
- enum Options {
- OPTION_COPY,
- OPTION_ERASE_SELECTION,
- OPTION_FIX_INVALID,
- OPTION_CUT
- };
-
- TileMap *node;
- bool manual_autotile;
- bool priority_atlastile;
- Vector2 manual_position;
-
- EditorNode *editor;
- UndoRedo *undo_redo;
- Control *canvas_item_editor_viewport;
-
- LineEdit *search_box;
- HSlider *size_slider;
- ItemList *palette;
- ItemList *manual_palette;
-
- Label *info_message;
-
- HBoxContainer *toolbar;
- HBoxContainer *toolbar_right;
-
- Label *tile_info;
- MenuButton *options;
-
- Button *paint_button;
- Button *line_button;
- Button *rectangle_button;
- Button *bucket_fill_button;
- Button *picker_button;
- Button *select_button;
-
- Button *flip_horizontal_button;
- Button *flip_vertical_button;
- Button *rotate_left_button;
- Button *rotate_right_button;
- Button *clear_transform_button;
-
- CheckBox *manual_button;
- CheckBox *priority_button;
-
- Tool tool;
- Tool last_tool;
-
- bool selection_active;
- bool mouse_over;
- bool mouse_down;
-
- bool flip_h;
- bool flip_v;
- bool transpose;
- Point2i autotile_coord;
-
- Point2i rectangle_begin;
- Rect2i rectangle;
-
- Point2i over_tile;
- bool refocus_over_tile = false;
-
- bool *bucket_cache_visited;
- Rect2i bucket_cache_rect;
- int bucket_cache_tile;
- Vector<Vector2> bucket_cache;
- List<Point2i> bucket_queue;
-
- struct CellOp {
- int idx = TileMap::INVALID_CELL;
- bool xf = false;
- bool yf = false;
- bool tr = false;
- Vector2 ac;
- };
-
- Map<Point2i, CellOp> paint_undo;
-
- struct TileData {
- Point2i pos;
- int cell = TileMap::INVALID_CELL;
- bool flip_h = false;
- bool flip_v = false;
- bool transpose = false;
- Point2i autotile_coord;
- };
-
- List<TileData> copydata;
-
- Map<Point2i, CellOp> undo_data;
- Vector<int> invalid_cell;
-
- void _pick_tile(const Point2 &p_pos);
-
- Vector<Vector2> _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false);
-
- void _fill_points(const Vector<Vector2> &p_points, const Dictionary &p_op);
- void _erase_points(const Vector<Vector2> &p_points);
-
- void _select(const Point2i &p_from, const Point2i &p_to);
- void _erase_selection();
-
- void _draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform);
- void _draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform);
- void _clear_bucket_cache();
-
- void _update_copydata();
-
- Vector<int> get_selected_tiles() const;
- void set_selected_tiles(Vector<int> p_tile);
-
- void _manual_toggled(bool p_enabled);
- void _priority_toggled(bool p_enabled);
- void _text_entered(const String &p_text);
- void _text_changed(const String &p_text);
- void _sbox_input(const Ref<InputEvent> &p_ie);
- void _update_palette();
- void _update_button_tool();
- void _button_tool_select(int p_tool);
- void _menu_option(int p_option);
- void _palette_selected(int index);
- void _palette_multi_selected(int index, bool selected);
- void _palette_input(const Ref<InputEvent> &p_event);
-
- Dictionary _create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord);
- void _start_undo(const String &p_action);
- void _finish_undo();
- void _create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new);
- void _set_cell(const Point2i &p_pos, Vector<int> p_values, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, const Point2i &p_autotile_coord = Point2());
-
- void _canvas_mouse_enter();
- void _canvas_mouse_exit();
- void _tileset_settings_changed();
- void _icon_size_changed(float p_value);
-
- void _clear_transform();
- void _flip_horizontal();
- void _flip_vertical();
- void _rotate(int steps);
-
-protected:
- void _notification(int p_what);
- void _node_removed(Node *p_node);
- static void _bind_methods();
- CellOp _get_op_from_cell(const Point2i &p_pos);
-
-public:
- HBoxContainer *get_toolbar() const { return toolbar; }
- HBoxContainer *get_toolbar_right() const { return toolbar_right; }
- Label *get_tile_info() const { return tile_info; }
-
- bool forward_gui_input(const Ref<InputEvent> &p_event);
- void forward_canvas_draw_over_viewport(Control *p_overlay);
-
- void edit(Node *p_tile_map);
-
- TileMapEditor(EditorNode *p_editor);
- ~TileMapEditor();
-};
-
-class TileMapEditorPlugin : public EditorPlugin {
- GDCLASS(TileMapEditorPlugin, EditorPlugin);
-
- TileMapEditor *tile_map_editor;
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tile_map_editor->forward_gui_input(p_event); }
- virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tile_map_editor->forward_canvas_draw_over_viewport(p_overlay); }
-
- virtual String get_name() const override { return "TileMap"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- TileMapEditorPlugin(EditorNode *p_node);
- ~TileMapEditorPlugin();
-};
-
-#endif // TILE_MAP_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp
deleted file mode 100644
index f683c4b10d..0000000000
--- a/editor/plugins/tile_set_editor_plugin.cpp
+++ /dev/null
@@ -1,3680 +0,0 @@
-/*************************************************************************/
-/* tile_set_editor_plugin.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 "tile_set_editor_plugin.h"
-
-#include "core/input/input.h"
-#include "core/os/keyboard.h"
-#include "editor/editor_scale.h"
-#include "editor/plugins/canvas_item_editor_plugin.h"
-#include "scene/2d/physics_body_2d.h"
-#include "scene/2d/sprite_2d.h"
-
-void TileSetEditor::edit(const Ref<TileSet> &p_tileset) {
- tileset = p_tileset;
-
- texture_list->clear();
- texture_map.clear();
- update_texture_list();
-}
-
-void TileSetEditor::_import_node(Node *p_node, Ref<TileSet> p_library) {
- for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *child = p_node->get_child(i);
-
- if (!Object::cast_to<Sprite2D>(child)) {
- if (child->get_child_count() > 0) {
- _import_node(child, p_library);
- }
-
- continue;
- }
-
- Sprite2D *mi = Object::cast_to<Sprite2D>(child);
- Ref<Texture2D> texture = mi->get_texture();
- Ref<ShaderMaterial> material = mi->get_material();
-
- if (texture.is_null()) {
- continue;
- }
-
- int id = p_library->find_tile_by_name(mi->get_name());
- if (id < 0) {
- id = p_library->get_last_unused_tile_id();
- p_library->create_tile(id);
- p_library->tile_set_name(id, mi->get_name());
- }
-
- p_library->tile_set_texture(id, texture);
- p_library->tile_set_material(id, material);
-
- p_library->tile_set_modulate(id, mi->get_modulate());
-
- Vector2 phys_offset;
- Size2 s;
-
- if (mi->is_region_enabled()) {
- s = mi->get_region_rect().size;
- p_library->tile_set_region(id, mi->get_region_rect());
- } else {
- const int frame = mi->get_frame();
- const int hframes = mi->get_hframes();
- s = texture->get_size() / Size2(hframes, mi->get_vframes());
- p_library->tile_set_region(id, Rect2(Vector2(frame % hframes, frame / hframes) * s, s));
- }
-
- if (mi->is_centered()) {
- phys_offset += -s / 2;
- }
-
- Vector<TileSet::ShapeData> collisions;
- Ref<NavigationPolygon> nav_poly;
- Ref<OccluderPolygon2D> occluder;
- bool found_collisions = false;
-
- for (int j = 0; j < mi->get_child_count(); j++) {
- Node *child2 = mi->get_child(j);
-
- if (Object::cast_to<NavigationRegion2D>(child2)) {
- nav_poly = Object::cast_to<NavigationRegion2D>(child2)->get_navigation_polygon();
- }
-
- if (Object::cast_to<LightOccluder2D>(child2)) {
- occluder = Object::cast_to<LightOccluder2D>(child2)->get_occluder_polygon();
- }
-
- if (!Object::cast_to<StaticBody2D>(child2)) {
- continue;
- }
-
- found_collisions = true;
-
- StaticBody2D *sb = Object::cast_to<StaticBody2D>(child2);
-
- List<uint32_t> shapes;
- sb->get_shape_owners(&shapes);
-
- for (List<uint32_t>::Element *E = shapes.front(); E; E = E->next()) {
- if (sb->is_shape_owner_disabled(E->get())) {
- continue;
- }
-
- Transform2D shape_transform = sb->get_transform() * sb->shape_owner_get_transform(E->get());
- bool one_way = sb->is_shape_owner_one_way_collision_enabled(E->get());
-
- shape_transform[2] -= phys_offset;
-
- for (int k = 0; k < sb->shape_owner_get_shape_count(E->get()); k++) {
- Ref<Shape2D> shape = sb->shape_owner_get_shape(E->get(), k);
- TileSet::ShapeData shape_data;
- shape_data.shape = shape;
- shape_data.shape_transform = shape_transform;
- shape_data.one_way_collision = one_way;
- collisions.push_back(shape_data);
- }
- }
- }
-
- if (found_collisions) {
- p_library->tile_set_shapes(id, collisions);
- }
-
- p_library->tile_set_texture_offset(id, mi->get_offset());
- p_library->tile_set_navigation_polygon(id, nav_poly);
- p_library->tile_set_light_occluder(id, occluder);
- p_library->tile_set_occluder_offset(id, -phys_offset);
- p_library->tile_set_navigation_polygon_offset(id, -phys_offset);
- p_library->tile_set_z_index(id, mi->get_z_index());
- }
-}
-
-void TileSetEditor::_import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge) {
- if (!p_merge) {
- p_library->clear();
- }
-
- _import_node(p_scene, p_library);
-}
-
-void TileSetEditor::_undo_redo_import_scene(Node *p_scene, bool p_merge) {
- _import_scene(p_scene, tileset, p_merge);
-}
-
-Error TileSetEditor::update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge) {
- _import_scene(p_base_scene, ml, p_merge);
- return OK;
-}
-
-Variant TileSetEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
- return false;
-}
-
-bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
- Dictionary d = p_data;
-
- if (!d.has("type")) {
- return false;
- }
-
- if (d.has("from") && (Object *)(d["from"]) == texture_list) {
- return false;
- }
-
- if (String(d["type"]) == "resource" && d.has("resource")) {
- RES r = d["resource"];
-
- Ref<Texture2D> texture = r;
-
- if (texture.is_valid()) {
- return true;
- }
- }
-
- if (String(d["type"]) == "files") {
- Vector<String> files = d["files"];
-
- if (files.size() == 0) {
- return false;
- }
-
- for (int i = 0; i < files.size(); i++) {
- String file = files[i];
- String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
-
- if (!ClassDB::is_parent_class(ftype, "Texture")) {
- return false;
- }
- }
-
- return true;
- }
- return false;
-}
-
-void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
- if (!can_drop_data_fw(p_point, p_data, p_from)) {
- return;
- }
-
- Dictionary d = p_data;
-
- if (!d.has("type")) {
- return;
- }
-
- if (String(d["type"]) == "resource" && d.has("resource")) {
- RES r = d["resource"];
-
- Ref<Texture2D> texture = r;
-
- if (texture.is_valid()) {
- add_texture(texture);
- }
-
- if (texture_list->get_item_count() > 0) {
- update_texture_list_icon();
- texture_list->select(texture_list->get_item_count() - 1);
- _on_texture_list_selected(texture_list->get_item_count() - 1);
- }
- }
-
- if (String(d["type"]) == "files") {
- Vector<String> files = d["files"];
-
- _on_textures_added(files);
- }
-}
-
-void TileSetEditor::_bind_methods() {
- ClassDB::bind_method("_undo_redo_import_scene", &TileSetEditor::_undo_redo_import_scene);
- ClassDB::bind_method("_on_workspace_process", &TileSetEditor::_on_workspace_process); // Still used by some connect_compat.
- ClassDB::bind_method("_set_snap_step", &TileSetEditor::_set_snap_step);
- ClassDB::bind_method("_set_snap_off", &TileSetEditor::_set_snap_off);
- ClassDB::bind_method("_set_snap_sep", &TileSetEditor::_set_snap_sep);
- ClassDB::bind_method("_validate_current_tile_id", &TileSetEditor::_validate_current_tile_id);
- ClassDB::bind_method("_select_edited_shape_coord", &TileSetEditor::_select_edited_shape_coord);
- ClassDB::bind_method("_sort_tiles", &TileSetEditor::_sort_tiles);
-
- ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &TileSetEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetEditor::drop_data_fw);
-
- ClassDB::bind_method("edit", &TileSetEditor::edit);
- ClassDB::bind_method("add_texture", &TileSetEditor::add_texture);
- ClassDB::bind_method("remove_texture", &TileSetEditor::remove_texture);
- ClassDB::bind_method("update_texture_list_icon", &TileSetEditor::update_texture_list_icon);
- ClassDB::bind_method("update_workspace_minsize", &TileSetEditor::update_workspace_minsize);
-}
-
-void TileSetEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_READY: {
- add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up.
- } break;
- case NOTIFICATION_TRANSLATION_CHANGED:
- case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
- case NOTIFICATION_ENTER_TREE:
- case NOTIFICATION_THEME_CHANGED: {
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_icon(get_theme_icon("ToolAddNode", "EditorIcons"));
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_icon(get_theme_icon("Remove", "EditorIcons"));
- tileset_toolbar_tools->set_icon(get_theme_icon("Tools", "EditorIcons"));
-
- tool_workspacemode[WORKSPACE_EDIT]->set_icon(get_theme_icon("Edit", "EditorIcons"));
- tool_workspacemode[WORKSPACE_CREATE_SINGLE]->set_icon(get_theme_icon("AddSingleTile", "EditorIcons"));
- tool_workspacemode[WORKSPACE_CREATE_AUTOTILE]->set_icon(get_theme_icon("AddAutotile", "EditorIcons"));
- tool_workspacemode[WORKSPACE_CREATE_ATLAS]->set_icon(get_theme_icon("AddAtlasTile", "EditorIcons"));
-
- tools[TOOL_SELECT]->set_icon(get_theme_icon("ToolSelect", "EditorIcons"));
- tools[BITMASK_COPY]->set_icon(get_theme_icon("Duplicate", "EditorIcons"));
- tools[BITMASK_PASTE]->set_icon(get_theme_icon("Override", "EditorIcons"));
- tools[BITMASK_CLEAR]->set_icon(get_theme_icon("Clear", "EditorIcons"));
- tools[SHAPE_NEW_POLYGON]->set_icon(get_theme_icon("CollisionPolygon2D", "EditorIcons"));
- tools[SHAPE_NEW_RECTANGLE]->set_icon(get_theme_icon("CollisionShape2D", "EditorIcons"));
- if (is_layout_rtl()) {
- tools[SELECT_PREVIOUS]->set_icon(get_theme_icon("ArrowLeft", "EditorIcons"));
- tools[SELECT_NEXT]->set_icon(get_theme_icon("ArrowRight", "EditorIcons"));
- } else {
- tools[SELECT_PREVIOUS]->set_icon(get_theme_icon("ArrowRight", "EditorIcons"));
- tools[SELECT_NEXT]->set_icon(get_theme_icon("ArrowLeft", "EditorIcons"));
- }
- tools[SHAPE_DELETE]->set_icon(get_theme_icon("Remove", "EditorIcons"));
- tools[SHAPE_KEEP_INSIDE_TILE]->set_icon(get_theme_icon("Snap", "EditorIcons"));
- tools[TOOL_GRID_SNAP]->set_icon(get_theme_icon("SnapGrid", "EditorIcons"));
- tools[ZOOM_OUT]->set_icon(get_theme_icon("ZoomLess", "EditorIcons"));
- tools[ZOOM_1]->set_icon(get_theme_icon("ZoomReset", "EditorIcons"));
- tools[ZOOM_IN]->set_icon(get_theme_icon("ZoomMore", "EditorIcons"));
- tools[VISIBLE_INFO]->set_icon(get_theme_icon("InformationSign", "EditorIcons"));
- _update_toggle_shape_button();
-
- tool_editmode[EDITMODE_REGION]->set_icon(get_theme_icon("RegionEdit", "EditorIcons"));
- tool_editmode[EDITMODE_COLLISION]->set_icon(get_theme_icon("StaticBody2D", "EditorIcons"));
- tool_editmode[EDITMODE_OCCLUSION]->set_icon(get_theme_icon("LightOccluder2D", "EditorIcons"));
- tool_editmode[EDITMODE_NAVIGATION]->set_icon(get_theme_icon("Navigation2D", "EditorIcons"));
- tool_editmode[EDITMODE_BITMASK]->set_icon(get_theme_icon("PackedDataContainer", "EditorIcons"));
- tool_editmode[EDITMODE_PRIORITY]->set_icon(get_theme_icon("MaterialPreviewLight1", "EditorIcons"));
- tool_editmode[EDITMODE_ICON]->set_icon(get_theme_icon("Image", "EditorIcons"));
- tool_editmode[EDITMODE_Z_INDEX]->set_icon(get_theme_icon("Sort", "EditorIcons"));
-
- scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree"));
- } break;
- }
-}
-
-TileSetEditor::TileSetEditor(EditorNode *p_editor) {
- editor = p_editor;
- undo_redo = EditorNode::get_undo_redo();
- current_tile = -1;
-
- VBoxContainer *left_container = memnew(VBoxContainer);
- add_child(left_container);
-
- texture_list = memnew(ItemList);
- left_container->add_child(texture_list);
- texture_list->set_v_size_flags(SIZE_EXPAND_FILL);
- texture_list->set_custom_minimum_size(Size2(200, 0));
- texture_list->connect("item_selected", callable_mp(this, &TileSetEditor::_on_texture_list_selected));
- texture_list->set_drag_forwarding(this);
-
- HBoxContainer *tileset_toolbar_container = memnew(HBoxContainer);
- left_container->add_child(tileset_toolbar_container);
-
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE] = memnew(Button);
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_flat(true);
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_ADD_TEXTURE));
- tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]);
- tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_tooltip(TTR("Add Texture(s) to TileSet."));
-
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE] = memnew(Button);
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_flat(true);
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_REMOVE_TEXTURE));
- tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]);
- tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_tooltip(TTR("Remove selected Texture from TileSet."));
-
- Control *toolbar_separator = memnew(Control);
- toolbar_separator->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- tileset_toolbar_container->add_child(toolbar_separator);
-
- tileset_toolbar_tools = memnew(MenuButton);
- tileset_toolbar_tools->set_text(TTR("Tools"));
- tileset_toolbar_tools->get_popup()->add_item(TTR("Create from Scene"), TOOL_TILESET_CREATE_SCENE);
- tileset_toolbar_tools->get_popup()->add_item(TTR("Merge from Scene"), TOOL_TILESET_MERGE_SCENE);
-
- tileset_toolbar_tools->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed));
- tileset_toolbar_container->add_child(tileset_toolbar_tools);
-
- //---------------
- VBoxContainer *right_container = memnew(VBoxContainer);
- right_container->set_v_size_flags(SIZE_EXPAND_FILL);
- add_child(right_container);
-
- dragging_point = -1;
- creating_shape = false;
- snap_step = Vector2(32, 32);
- snap_offset = WORKSPACE_MARGIN;
-
- set_custom_minimum_size(Size2(0, 150));
-
- VBoxContainer *main_vb = memnew(VBoxContainer);
- right_container->add_child(main_vb);
- main_vb->set_v_size_flags(SIZE_EXPAND_FILL);
-
- HBoxContainer *tool_hb = memnew(HBoxContainer);
- Ref<ButtonGroup> g(memnew(ButtonGroup));
-
- String workspace_label[WORKSPACE_MODE_MAX] = {
- TTR("Edit"),
- TTR("New Single Tile"),
- TTR("New Autotile"),
- TTR("New Atlas")
- };
- for (int i = 0; i < (int)WORKSPACE_MODE_MAX; i++) {
- tool_workspacemode[i] = memnew(Button);
- tool_workspacemode[i]->set_text(workspace_label[i]);
- tool_workspacemode[i]->set_toggle_mode(true);
- tool_workspacemode[i]->set_button_group(g);
- tool_workspacemode[i]->connect("pressed", callable_mp(this, &TileSetEditor::_on_workspace_mode_changed), varray(i));
- tool_hb->add_child(tool_workspacemode[i]);
- }
-
- Control *spacer = memnew(Control);
- spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- tool_hb->add_child(spacer);
- tool_hb->move_child(spacer, WORKSPACE_CREATE_SINGLE);
-
- tools[SELECT_NEXT] = memnew(Button);
- tool_hb->add_child(tools[SELECT_NEXT]);
- tool_hb->move_child(tools[SELECT_NEXT], WORKSPACE_CREATE_SINGLE);
- tools[SELECT_NEXT]->set_flat(true);
- tools[SELECT_NEXT]->set_shortcut(ED_SHORTCUT("tileset_editor/next_shape", TTR("Next Coordinate"), KEY_PAGEDOWN));
- tools[SELECT_NEXT]->set_shortcut_context(this);
- tools[SELECT_NEXT]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_NEXT));
- tools[SELECT_NEXT]->set_tooltip(TTR("Select the next shape, subtile, or Tile."));
- tools[SELECT_PREVIOUS] = memnew(Button);
- tool_hb->add_child(tools[SELECT_PREVIOUS]);
- tool_hb->move_child(tools[SELECT_PREVIOUS], WORKSPACE_CREATE_SINGLE);
- tools[SELECT_PREVIOUS]->set_flat(true);
- tools[SELECT_PREVIOUS]->set_shortcut(ED_SHORTCUT("tileset_editor/previous_shape", TTR("Previous Coordinate"), KEY_PAGEUP));
- tools[SELECT_PREVIOUS]->set_shortcut_context(this);
- tools[SELECT_PREVIOUS]->set_tooltip(TTR("Select the previous shape, subtile, or Tile."));
- tools[SELECT_PREVIOUS]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_PREVIOUS));
-
- VSeparator *separator_shape_selection = memnew(VSeparator);
- tool_hb->add_child(separator_shape_selection);
- tool_hb->move_child(separator_shape_selection, WORKSPACE_CREATE_SINGLE);
-
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- workspace_mode = WORKSPACE_EDIT;
-
- main_vb->add_child(tool_hb);
- main_vb->add_child(memnew(HSeparator));
-
- tool_hb = memnew(HBoxContainer);
-
- g = Ref<ButtonGroup>(memnew(ButtonGroup));
- String label[EDITMODE_MAX] = {
- TTR("Region"),
- TTR("Collision"),
- TTR("Occlusion"),
- TTR("Navigation"),
- TTR("Bitmask"),
- TTR("Priority"),
- TTR("Icon"),
- TTR("Z Index")
- };
- for (int i = 0; i < (int)EDITMODE_MAX; i++) {
- tool_editmode[i] = memnew(Button);
- tool_editmode[i]->set_text(label[i]);
- tool_editmode[i]->set_toggle_mode(true);
- tool_editmode[i]->set_button_group(g);
- tool_editmode[i]->connect("pressed", callable_mp(this, &TileSetEditor::_on_edit_mode_changed), varray(i));
- tool_hb->add_child(tool_editmode[i]);
- }
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
-
- tool_editmode[EDITMODE_REGION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_region", TTR("Region Mode"), KEY_1));
- tool_editmode[EDITMODE_COLLISION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_collision", TTR("Collision Mode"), KEY_2));
- tool_editmode[EDITMODE_OCCLUSION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_occlusion", TTR("Occlusion Mode"), KEY_3));
- tool_editmode[EDITMODE_NAVIGATION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_navigation", TTR("Navigation Mode"), KEY_4));
- tool_editmode[EDITMODE_BITMASK]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_bitmask", TTR("Bitmask Mode"), KEY_5));
- tool_editmode[EDITMODE_PRIORITY]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_priority", TTR("Priority Mode"), KEY_6));
- tool_editmode[EDITMODE_ICON]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_icon", TTR("Icon Mode"), KEY_7));
- tool_editmode[EDITMODE_Z_INDEX]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_z_index", TTR("Z Index Mode"), KEY_8));
-
- tool_editmode[EDITMODE_REGION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_REGION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_COLLISION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_OCCLUSION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_NAVIGATION]->set_shortcut_context(this);
- tool_editmode[EDITMODE_BITMASK]->set_shortcut_context(this);
- tool_editmode[EDITMODE_PRIORITY]->set_shortcut_context(this);
- tool_editmode[EDITMODE_ICON]->set_shortcut_context(this);
- tool_editmode[EDITMODE_Z_INDEX]->set_shortcut_context(this);
-
- main_vb->add_child(tool_hb);
- separator_editmode = memnew(HSeparator);
- main_vb->add_child(separator_editmode);
-
- toolbar = memnew(HBoxContainer);
- Ref<ButtonGroup> tg(memnew(ButtonGroup));
-
- tools[TOOL_SELECT] = memnew(Button);
- toolbar->add_child(tools[TOOL_SELECT]);
- tools[TOOL_SELECT]->set_flat(true);
- tools[TOOL_SELECT]->set_toggle_mode(true);
- tools[TOOL_SELECT]->set_button_group(tg);
- tools[TOOL_SELECT]->set_pressed(true);
- tools[TOOL_SELECT]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(TOOL_SELECT));
-
- separator_bitmask = memnew(VSeparator);
- toolbar->add_child(separator_bitmask);
- tools[BITMASK_COPY] = memnew(Button);
- tools[BITMASK_COPY]->set_flat(true);
- tools[BITMASK_COPY]->set_tooltip(TTR("Copy bitmask."));
- tools[BITMASK_COPY]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_COPY));
- toolbar->add_child(tools[BITMASK_COPY]);
- tools[BITMASK_PASTE] = memnew(Button);
- tools[BITMASK_PASTE]->set_flat(true);
- tools[BITMASK_PASTE]->set_tooltip(TTR("Paste bitmask."));
- tools[BITMASK_PASTE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_PASTE));
- toolbar->add_child(tools[BITMASK_PASTE]);
- tools[BITMASK_CLEAR] = memnew(Button);
- tools[BITMASK_CLEAR]->set_flat(true);
- tools[BITMASK_CLEAR]->set_tooltip(TTR("Erase bitmask."));
- tools[BITMASK_CLEAR]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_CLEAR));
- toolbar->add_child(tools[BITMASK_CLEAR]);
-
- tools[SHAPE_NEW_RECTANGLE] = memnew(Button);
- toolbar->add_child(tools[SHAPE_NEW_RECTANGLE]);
- tools[SHAPE_NEW_RECTANGLE]->set_flat(true);
- tools[SHAPE_NEW_RECTANGLE]->set_toggle_mode(true);
- tools[SHAPE_NEW_RECTANGLE]->set_button_group(tg);
- tools[SHAPE_NEW_RECTANGLE]->set_tooltip(TTR("Create a new rectangle."));
- tools[SHAPE_NEW_RECTANGLE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_NEW_RECTANGLE));
- tools[SHAPE_NEW_RECTANGLE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_rectangle", TTR("New Rectangle"), KEY_MASK_SHIFT | KEY_R));
-
- tools[SHAPE_NEW_POLYGON] = memnew(Button);
- toolbar->add_child(tools[SHAPE_NEW_POLYGON]);
- tools[SHAPE_NEW_POLYGON]->set_flat(true);
- tools[SHAPE_NEW_POLYGON]->set_toggle_mode(true);
- tools[SHAPE_NEW_POLYGON]->set_button_group(tg);
- tools[SHAPE_NEW_POLYGON]->set_tooltip(TTR("Create a new polygon."));
- tools[SHAPE_NEW_POLYGON]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_NEW_POLYGON));
- tools[SHAPE_NEW_POLYGON]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_polygon", TTR("New Polygon"), KEY_MASK_SHIFT | KEY_P));
-
- separator_shape_toggle = memnew(VSeparator);
- toolbar->add_child(separator_shape_toggle);
- tools[SHAPE_TOGGLE_TYPE] = memnew(Button);
- tools[SHAPE_TOGGLE_TYPE]->set_flat(true);
- tools[SHAPE_TOGGLE_TYPE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_TOGGLE_TYPE));
- toolbar->add_child(tools[SHAPE_TOGGLE_TYPE]);
-
- separator_delete = memnew(VSeparator);
- toolbar->add_child(separator_delete);
- tools[SHAPE_DELETE] = memnew(Button);
- tools[SHAPE_DELETE]->set_flat(true);
- tools[SHAPE_DELETE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_DELETE));
- tools[SHAPE_DELETE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_delete", TTR("Delete Selected Shape"), KEY_MASK_SHIFT | KEY_BACKSPACE));
- toolbar->add_child(tools[SHAPE_DELETE]);
-
- spin_priority = memnew(SpinBox);
- spin_priority->set_min(1);
- spin_priority->set_max(255);
- spin_priority->set_step(1);
- spin_priority->set_custom_minimum_size(Size2(100, 0));
- spin_priority->connect("value_changed", callable_mp(this, &TileSetEditor::_on_priority_changed));
- spin_priority->hide();
- toolbar->add_child(spin_priority);
-
- spin_z_index = memnew(SpinBox);
- spin_z_index->set_min(RS::CANVAS_ITEM_Z_MIN);
- spin_z_index->set_max(RS::CANVAS_ITEM_Z_MAX);
- spin_z_index->set_step(1);
- spin_z_index->set_custom_minimum_size(Size2(100, 0));
- spin_z_index->connect("value_changed", callable_mp(this, &TileSetEditor::_on_z_index_changed));
- spin_z_index->hide();
- toolbar->add_child(spin_z_index);
-
- separator_grid = memnew(VSeparator);
- toolbar->add_child(separator_grid);
- tools[SHAPE_KEEP_INSIDE_TILE] = memnew(Button);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_flat(true);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_toggle_mode(true);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_pressed(true);
- tools[SHAPE_KEEP_INSIDE_TILE]->set_tooltip(TTR("Keep polygon inside region Rect."));
- toolbar->add_child(tools[SHAPE_KEEP_INSIDE_TILE]);
- tools[TOOL_GRID_SNAP] = memnew(Button);
- tools[TOOL_GRID_SNAP]->set_flat(true);
- tools[TOOL_GRID_SNAP]->set_toggle_mode(true);
- tools[TOOL_GRID_SNAP]->set_tooltip(TTR("Enable snap and show grid (configurable via the Inspector)."));
- tools[TOOL_GRID_SNAP]->connect("toggled", callable_mp(this, &TileSetEditor::_on_grid_snap_toggled));
- toolbar->add_child(tools[TOOL_GRID_SNAP]);
-
- Control *separator = memnew(Control);
- separator->set_h_size_flags(SIZE_EXPAND_FILL);
- toolbar->add_child(separator);
-
- tools[ZOOM_OUT] = memnew(Button);
- tools[ZOOM_OUT]->set_flat(true);
- tools[ZOOM_OUT]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_out));
- toolbar->add_child(tools[ZOOM_OUT]);
- tools[ZOOM_OUT]->set_tooltip(TTR("Zoom Out"));
- tools[ZOOM_1] = memnew(Button);
- tools[ZOOM_1]->set_flat(true);
- tools[ZOOM_1]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_reset));
- toolbar->add_child(tools[ZOOM_1]);
- tools[ZOOM_1]->set_tooltip(TTR("Zoom Reset"));
- tools[ZOOM_IN] = memnew(Button);
- tools[ZOOM_IN]->set_flat(true);
- tools[ZOOM_IN]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_in));
- toolbar->add_child(tools[ZOOM_IN]);
- tools[ZOOM_IN]->set_tooltip(TTR("Zoom In"));
-
- tools[VISIBLE_INFO] = memnew(Button);
- tools[VISIBLE_INFO]->set_flat(true);
- tools[VISIBLE_INFO]->set_toggle_mode(true);
- tools[VISIBLE_INFO]->set_tooltip(TTR("Display Tile Names (Hold Alt Key)"));
- toolbar->add_child(tools[VISIBLE_INFO]);
-
- main_vb->add_child(toolbar);
-
- scroll = memnew(ScrollContainer);
- main_vb->add_child(scroll);
- scroll->set_v_size_flags(SIZE_EXPAND_FILL);
- scroll->connect("gui_input", callable_mp(this, &TileSetEditor::_on_scroll_container_input));
- scroll->set_clip_contents(true);
-
- empty_message = memnew(Label);
- empty_message->set_text(TTR("Add or select a texture on the left panel to edit the tiles bound to it."));
- empty_message->set_valign(Label::VALIGN_CENTER);
- empty_message->set_align(Label::ALIGN_CENTER);
- empty_message->set_autowrap(true);
- empty_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
- empty_message->set_v_size_flags(SIZE_EXPAND_FILL);
- main_vb->add_child(empty_message);
-
- workspace_container = memnew(Control);
- scroll->add_child(workspace_container);
-
- workspace_overlay = memnew(Control);
- workspace_overlay->connect("draw", callable_mp(this, &TileSetEditor::_on_workspace_overlay_draw));
- workspace_container->add_child(workspace_overlay);
-
- workspace = memnew(Control);
- workspace->set_focus_mode(FOCUS_ALL);
- workspace->connect("draw", callable_mp(this, &TileSetEditor::_on_workspace_draw));
- workspace->connect("gui_input", callable_mp(this, &TileSetEditor::_on_workspace_input));
- workspace->set_draw_behind_parent(true);
- workspace_overlay->add_child(workspace);
-
- preview = memnew(Sprite2D);
- workspace->add_child(preview);
- preview->set_centered(false);
- preview->set_draw_behind_parent(true);
- preview->set_position(WORKSPACE_MARGIN);
-
- //---------------
- cd = memnew(ConfirmationDialog);
- add_child(cd);
- cd->connect("confirmed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_confirm));
-
- //---------------
- err_dialog = memnew(AcceptDialog);
- add_child(err_dialog);
-
- //---------------
- texture_dialog = memnew(EditorFileDialog);
- texture_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
- texture_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
- texture_dialog->clear_filters();
- List<String> extensions;
-
- ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);
- for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
- texture_dialog->add_filter("*." + E->get() + " ; " + E->get().to_upper());
- }
- add_child(texture_dialog);
- texture_dialog->connect("files_selected", callable_mp(this, &TileSetEditor::_on_textures_added));
-
- //---------------
- helper = memnew(TilesetEditorContext(this));
- tile_names_visible = false;
-
- // Config scale.
- max_scale = 16.0f;
- min_scale = 0.01f;
- scale_ratio = 1.2f;
-}
-
-TileSetEditor::~TileSetEditor() {
- if (helper) {
- memdelete(helper);
- }
-}
-
-void TileSetEditor::_on_tileset_toolbar_button_pressed(int p_index) {
- option = p_index;
- switch (option) {
- case TOOL_TILESET_ADD_TEXTURE: {
- texture_dialog->popup_file_dialog();
- } break;
- case TOOL_TILESET_REMOVE_TEXTURE: {
- if (get_current_texture().is_valid()) {
- cd->set_text(TTR("Remove selected texture? This will remove all tiles which use it."));
- cd->popup_centered(Size2(300, 60));
- } else {
- err_dialog->set_text(TTR("You haven't selected a texture to remove."));
- err_dialog->popup_centered(Size2(300, 60));
- }
- } break;
- case TOOL_TILESET_CREATE_SCENE: {
- cd->set_text(TTR("Create from scene? This will overwrite all current tiles."));
- cd->popup_centered(Size2(300, 60));
- } break;
- case TOOL_TILESET_MERGE_SCENE: {
- cd->set_text(TTR("Merge from scene?"));
- cd->popup_centered(Size2(300, 60));
- } break;
- }
-}
-
-void TileSetEditor::_on_tileset_toolbar_confirm() {
- switch (option) {
- case TOOL_TILESET_REMOVE_TEXTURE: {
- RID current_rid = get_current_texture()->get_rid();
- List<int> ids;
- tileset->get_tile_list(&ids);
-
- undo_redo->create_action(TTR("Remove Texture"));
- for (List<int>::Element *E = ids.front(); E; E = E->next()) {
- if (tileset->tile_get_texture(E->get())->get_rid() == current_rid) {
- undo_redo->add_do_method(tileset.ptr(), "remove_tile", E->get());
- _undo_tile_removal(E->get());
- }
- }
- undo_redo->add_do_method(this, "remove_texture", get_current_texture());
- undo_redo->add_undo_method(this, "add_texture", get_current_texture());
- undo_redo->add_undo_method(this, "update_texture_list_icon");
- undo_redo->commit_action();
- } break;
- case TOOL_TILESET_MERGE_SCENE:
- case TOOL_TILESET_CREATE_SCENE: {
- EditorNode *en = editor;
- Node *scene = en->get_edited_scene();
- if (!scene) {
- break;
- }
-
- List<int> ids;
- tileset->get_tile_list(&ids);
-
- undo_redo->create_action(option == TOOL_TILESET_MERGE_SCENE ? TTR("Merge Tileset from Scene") : TTR("Create Tileset from Scene"));
- undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE);
- undo_redo->add_undo_method(tileset.ptr(), "clear");
- for (List<int>::Element *E = ids.front(); E; E = E->next()) {
- _undo_tile_removal(E->get());
- }
- undo_redo->add_do_method(this, "edit", tileset);
- undo_redo->add_undo_method(this, "edit", tileset);
- undo_redo->commit_action();
- } break;
- }
-}
-
-void TileSetEditor::_on_texture_list_selected(int p_index) {
- if (get_current_texture().is_valid()) {
- current_item_index = p_index;
- preview->set_texture(get_current_texture());
- update_workspace_tile_mode();
- update_workspace_minsize();
- } else {
- current_item_index = -1;
- preview->set_texture(nullptr);
- workspace->set_custom_minimum_size(Size2i());
- update_workspace_tile_mode();
- }
-
- set_current_tile(-1);
- workspace->update();
-}
-
-void TileSetEditor::_on_textures_added(const PackedStringArray &p_paths) {
- int invalid_count = 0;
- for (int i = 0; i < p_paths.size(); i++) {
- Ref<Texture2D> t = Ref<Texture2D>(ResourceLoader::load(p_paths[i]));
-
- ERR_CONTINUE_MSG(!t.is_valid(), "'" + p_paths[i] + "' is not a valid texture.");
-
- if (texture_map.has(t->get_rid())) {
- invalid_count++;
- } else {
- add_texture(t);
- }
- }
-
- if (texture_list->get_item_count() > 0) {
- update_texture_list_icon();
- texture_list->select(texture_list->get_item_count() - 1);
- _on_texture_list_selected(texture_list->get_item_count() - 1);
- }
-
- if (invalid_count > 0) {
- err_dialog->set_text(vformat(TTR("%s file(s) were not added because was already on the list."), String::num(invalid_count, 0)));
- err_dialog->popup_centered(Size2(300, 60));
- }
-}
-
-void TileSetEditor::_on_edit_mode_changed(int p_edit_mode) {
- draw_handles = false;
- creating_shape = false;
- edit_mode = (EditMode)p_edit_mode;
- switch (edit_mode) {
- case EDITMODE_REGION: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->hide();
- tools[BITMASK_COPY]->hide();
- tools[BITMASK_PASTE]->hide();
- tools[BITMASK_CLEAR]->hide();
- tools[SHAPE_NEW_POLYGON]->hide();
- tools[SHAPE_NEW_RECTANGLE]->hide();
-
- if (workspace_mode == WORKSPACE_EDIT) {
- separator_delete->show();
- tools[SHAPE_DELETE]->show();
- } else {
- separator_delete->hide();
- tools[SHAPE_DELETE]->hide();
- }
-
- separator_grid->show();
- tools[SHAPE_KEEP_INSIDE_TILE]->hide();
- tools[TOOL_GRID_SNAP]->show();
-
- tools[TOOL_SELECT]->set_pressed(true);
- tools[TOOL_SELECT]->set_tooltip(TTR("Drag handles to edit Rect.\nClick on another Tile to edit it."));
- tools[SHAPE_DELETE]->set_tooltip(TTR("Delete selected Rect."));
- spin_priority->hide();
- spin_z_index->hide();
- } break;
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->hide();
- tools[BITMASK_COPY]->hide();
- tools[BITMASK_PASTE]->hide();
- tools[BITMASK_CLEAR]->hide();
- tools[SHAPE_NEW_POLYGON]->show();
- tools[SHAPE_NEW_RECTANGLE]->show();
-
- separator_delete->show();
- tools[SHAPE_DELETE]->show();
-
- separator_grid->show();
- tools[SHAPE_KEEP_INSIDE_TILE]->show();
- tools[TOOL_GRID_SNAP]->show();
-
- tools[TOOL_SELECT]->set_tooltip(TTR("Select current edited sub-tile.\nClick on another Tile to edit it."));
- tools[SHAPE_DELETE]->set_tooltip(TTR("Delete polygon."));
- spin_priority->hide();
- spin_z_index->hide();
-
- _select_edited_shape_coord();
- } break;
- case EDITMODE_BITMASK: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->show();
- tools[BITMASK_COPY]->show();
- tools[BITMASK_PASTE]->show();
- tools[BITMASK_CLEAR]->show();
- tools[SHAPE_NEW_POLYGON]->hide();
- tools[SHAPE_NEW_RECTANGLE]->hide();
-
- separator_delete->hide();
- tools[SHAPE_DELETE]->hide();
-
- tools[SHAPE_KEEP_INSIDE_TILE]->hide();
-
- tools[TOOL_SELECT]->set_pressed(true);
- tools[TOOL_SELECT]->set_tooltip(TTR("LMB: Set bit on.\nRMB: Set bit off.\nShift+LMB: Set wildcard bit.\nClick on another Tile to edit it."));
- spin_priority->hide();
- } break;
- case EDITMODE_Z_INDEX:
- case EDITMODE_PRIORITY:
- case EDITMODE_ICON: {
- tools[TOOL_SELECT]->show();
-
- separator_bitmask->hide();
- tools[BITMASK_COPY]->hide();
- tools[BITMASK_PASTE]->hide();
- tools[BITMASK_CLEAR]->hide();
- tools[SHAPE_NEW_POLYGON]->hide();
- tools[SHAPE_NEW_RECTANGLE]->hide();
-
- separator_delete->hide();
- tools[SHAPE_DELETE]->hide();
-
- separator_grid->show();
- tools[SHAPE_KEEP_INSIDE_TILE]->hide();
- tools[TOOL_GRID_SNAP]->show();
-
- if (edit_mode == EDITMODE_ICON) {
- tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to use as icon, this will be also used on invalid autotile bindings.\nClick on another Tile to edit it."));
- spin_priority->hide();
- spin_z_index->hide();
- } else if (edit_mode == EDITMODE_PRIORITY) {
- tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its priority.\nClick on another Tile to edit it."));
- spin_priority->show();
- spin_z_index->hide();
- } else {
- tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its z index.\nClick on another Tile to edit it."));
- spin_priority->hide();
- spin_z_index->show();
- }
- } break;
- default: {
- }
- }
- _update_toggle_shape_button();
- workspace->update();
-}
-
-void TileSetEditor::_on_workspace_mode_changed(int p_workspace_mode) {
- workspace_mode = (WorkspaceMode)p_workspace_mode;
- if (p_workspace_mode == WORKSPACE_EDIT) {
- update_workspace_tile_mode();
- } else {
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->hide();
- }
- tool_editmode[EDITMODE_REGION]->show();
- tool_editmode[EDITMODE_REGION]->set_pressed(true);
- _on_edit_mode_changed(EDITMODE_REGION);
- separator_editmode->show();
- }
-}
-
-void TileSetEditor::_on_workspace_draw() {
- if (tileset.is_null() || !get_current_texture().is_valid()) {
- return;
- }
-
- const Color COLOR_AUTOTILE = Color(0.3, 0.6, 1);
- const Color COLOR_SINGLE = Color(1, 1, 0.3);
- const Color COLOR_ATLAS = Color(0.8, 0.8, 0.8);
- const Color COLOR_SUBDIVISION = Color(0.3, 0.7, 0.6);
-
- draw_handles = false;
-
- draw_highlight_current_tile();
-
- draw_grid_snap();
- if (get_current_tile() >= 0) {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Rect2i region = tileset->tile_get_region(get_current_tile());
-
- switch (edit_mode) {
- case EDITMODE_ICON: {
- Vector2 coord = tileset->autotile_get_icon_coordinate(get_current_tile());
- draw_highlight_subtile(coord);
- } break;
- case EDITMODE_BITMASK: {
- Color c(1, 0, 0, 0.5);
- Color ci(0.3, 0.6, 1, 0.5);
- for (int x = 0; x < region.size.x / (spacing + size.x); x++) {
- for (int y = 0; y < region.size.y / (spacing + size.y); y++) {
- Vector2 coord(x, y);
- Point2 anchor(coord.x * (spacing + size.x), coord.y * (spacing + size.y));
- anchor += WORKSPACE_MARGIN;
- anchor += region.position;
- uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
- if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) {
- if (mask & TileSet::BIND_IGNORE_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 4), ci);
- workspace->draw_rect(Rect2(anchor + size / 4, size / 4), ci);
- } else if (mask & TileSet::BIND_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 2), c);
- }
- if (mask & TileSet::BIND_IGNORE_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 4), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 3 / 4, size.y / 4), size / 4), ci);
- } else if (mask & TileSet::BIND_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 2), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 4), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 4, size.y * 3 / 4), size / 4), ci);
- } else if (mask & TileSet::BIND_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 2), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + size / 2, size / 4), ci);
- workspace->draw_rect(Rect2(anchor + size * 3 / 4, size / 4), ci);
- } else if (mask & TileSet::BIND_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + size / 2, size / 2), c);
- }
- } else {
- if (mask & TileSet::BIND_IGNORE_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 6), ci);
- workspace->draw_rect(Rect2(anchor + size / 6, size / 6), ci);
- } else if (mask & TileSet::BIND_TOPLEFT) {
- workspace->draw_rect(Rect2(anchor, size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_TOP) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_TOP) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, 0), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_TOPRIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, 0), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_LEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y / 2), size / 6), ci);
- } else if (mask & TileSet::BIND_LEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_CENTER) {
- workspace->draw_rect(Rect2(anchor + size / 3, size / 6), ci);
- workspace->draw_rect(Rect2(anchor + size / 2, size / 6), ci);
- } else if (mask & TileSet::BIND_CENTER) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y / 3), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_RIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, size.y / 3), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 2), size / 6), ci);
- } else if (mask & TileSet::BIND_RIGHT) {
- workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, size.y / 3), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, size.y * 4 / 6), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y * 5 / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_BOTTOMLEFT) {
- workspace->draw_rect(Rect2(anchor + Vector2(0, (size.y / 3) * 2), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOM) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y * 4 / 6), size / 6), ci);
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y * 5 / 6), size / 6), ci);
- } else if (mask & TileSet::BIND_BOTTOM) {
- workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, (size.y / 3) * 2), size / 3), c);
- }
- if (mask & TileSet::BIND_IGNORE_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + size * 4 / 6, size / 6), ci);
- workspace->draw_rect(Rect2(anchor + size * 5 / 6, size / 6), ci);
- } else if (mask & TileSet::BIND_BOTTOMRIGHT) {
- workspace->draw_rect(Rect2(anchor + (size / 3) * 2, size / 3), c);
- }
- }
- }
- }
- } break;
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION: {
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- draw_highlight_subtile(edited_shape_coord);
- }
- draw_polygon_shapes();
- draw_grid_snap();
- } break;
- case EDITMODE_PRIORITY: {
- spin_priority->set_value(tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord));
- uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), edited_shape_coord);
- Vector<Vector2> queue_others;
- int total = 0;
- for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
- if (E->value() == mask) {
- total += tileset->autotile_get_subtile_priority(get_current_tile(), E->key());
- if (E->key() != edited_shape_coord) {
- queue_others.push_back(E->key());
- }
- }
- }
- spin_priority->set_suffix(" / " + String::num(total, 0));
- draw_highlight_subtile(edited_shape_coord, queue_others);
- } break;
- case EDITMODE_Z_INDEX: {
- spin_z_index->set_value(tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord));
- draw_highlight_subtile(edited_shape_coord);
- } break;
- default: {
- }
- }
- }
-
- RID current_texture_rid = get_current_texture()->get_rid();
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- int t_id = E->get();
- if (tileset->tile_get_texture(t_id)->get_rid() == current_texture_rid && (t_id != get_current_tile() || edit_mode != EDITMODE_REGION || workspace_mode != WORKSPACE_EDIT)) {
- Rect2i region = tileset->tile_get_region(t_id);
- region.position += WORKSPACE_MARGIN;
- Color c;
- if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) {
- c = COLOR_SINGLE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) {
- c = COLOR_AUTOTILE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) {
- c = COLOR_ATLAS;
- }
- draw_tile_subdivision(t_id, COLOR_SUBDIVISION);
- workspace->draw_rect(region, c, false);
- }
- }
- delete tiles;
-
- if (edit_mode == EDITMODE_REGION) {
- if (workspace_mode != WORKSPACE_EDIT) {
- Rect2i region = edited_region;
- Color c;
- if (workspace_mode == WORKSPACE_CREATE_SINGLE) {
- c = COLOR_SINGLE;
- } else if (workspace_mode == WORKSPACE_CREATE_AUTOTILE) {
- c = COLOR_AUTOTILE;
- } else if (workspace_mode == WORKSPACE_CREATE_ATLAS) {
- c = COLOR_ATLAS;
- }
- workspace->draw_rect(region, c, false);
- draw_edited_region_subdivision();
- } else {
- int t_id = get_current_tile();
- if (t_id < 0) {
- return;
- }
-
- Rect2i region;
- if (draw_edited_region) {
- region = edited_region;
- } else {
- region = tileset->tile_get_region(t_id);
- region.position += WORKSPACE_MARGIN;
- }
-
- if (draw_edited_region) {
- draw_edited_region_subdivision();
- } else {
- draw_tile_subdivision(t_id, COLOR_SUBDIVISION);
- }
-
- Color c;
- if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) {
- c = COLOR_SINGLE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) {
- c = COLOR_AUTOTILE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) {
- c = COLOR_ATLAS;
- }
- workspace->draw_rect(region, c, false);
- }
- }
-
- workspace_overlay->update();
-}
-
-void TileSetEditor::_on_workspace_process() {
- if (Input::get_singleton()->is_key_pressed(KEY_ALT) || tools[VISIBLE_INFO]->is_pressed()) {
- if (!tile_names_visible) {
- tile_names_visible = true;
- workspace_overlay->update();
- }
- } else if (tile_names_visible) {
- tile_names_visible = false;
- workspace_overlay->update();
- }
-}
-
-void TileSetEditor::_on_workspace_overlay_draw() {
- if (!tileset.is_valid() || !get_current_texture().is_valid()) {
- return;
- }
-
- const Color COLOR_AUTOTILE = Color(0.266373, 0.565288, 0.988281);
- const Color COLOR_SINGLE = Color(0.988281, 0.909323, 0.266373);
- const Color COLOR_ATLAS = Color(0.78653, 0.812835, 0.832031);
-
- if (tile_names_visible) {
- RID current_texture_rid = get_current_texture()->get_rid();
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- int t_id = E->get();
- if (tileset->tile_get_texture(t_id)->get_rid() != current_texture_rid) {
- continue;
- }
-
- Rect2 region = tileset->tile_get_region(t_id);
- region.position += WORKSPACE_MARGIN;
- region.position *= workspace->get_scale().x;
- Color c;
- if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) {
- c = COLOR_SINGLE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) {
- c = COLOR_AUTOTILE;
- } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) {
- c = COLOR_ATLAS;
- }
- String tile_id_name = String::num(t_id, 0) + ": " + tileset->tile_get_name(t_id);
- Ref<Font> font = get_theme_font("font", "Label");
- int font_size = get_theme_font_size("font_size", "Label");
- region.set_size(font->get_string_size(tile_id_name, font_size));
- workspace_overlay->draw_rect(region, c);
- region.position.y += region.size.y - 2;
- c = Color(0.1, 0.1, 0.1);
- workspace_overlay->draw_string(font, region.position, tile_id_name, HALIGN_LEFT, -1, font_size, c);
- }
- delete tiles;
- }
-
- int t_id = get_current_tile();
- if (t_id < 0) {
- return;
- }
-
- Ref<Texture2D> handle = get_theme_icon("EditorHandle", "EditorIcons");
- if (draw_handles) {
- for (int i = 0; i < current_shape.size(); i++) {
- workspace_overlay->draw_texture(handle, current_shape[i] * workspace->get_scale().x - handle->get_size() * 0.5);
- }
- }
-}
-
-int TileSetEditor::get_grabbed_point(const Vector2 &p_mouse_pos, real_t p_grab_threshold) {
- Transform2D xform = workspace->get_transform();
-
- int grabbed_point = -1;
- real_t min_distance = 1e10;
-
- for (int i = 0; i < current_shape.size(); i++) {
- const real_t distance = xform.xform(current_shape[i]).distance_to(xform.xform(p_mouse_pos));
- if (distance < p_grab_threshold && distance < min_distance) {
- min_distance = distance;
- grabbed_point = i;
- }
- }
-
- return grabbed_point;
-}
-
-bool TileSetEditor::is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold) {
- Transform2D xform = workspace->get_transform();
-
- const real_t distance = xform.xform(current_shape[0]).distance_to(xform.xform(p_pos));
-
- return distance < p_grab_threshold;
-}
-
-void TileSetEditor::_on_scroll_container_input(const Ref<InputEvent> &p_event) {
- const Ref<InputEventMouseButton> mb = p_event;
-
- if (mb.is_valid()) {
- // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer
- // to allow performing this action anywhere, even if the cursor isn't
- // hovering the texture in the workspace.
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) {
- print_line("zooming in");
- _zoom_in();
- // Don't scroll up after zooming in.
- accept_event();
- } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) {
- print_line("zooming out");
- _zoom_out();
- // Don't scroll down after zooming out.
- accept_event();
- }
- }
-}
-
-void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) {
- if (tileset.is_null() || !get_current_texture().is_valid()) {
- return;
- }
-
- static bool dragging;
- static bool erasing;
- static bool alternative;
- draw_edited_region = false;
-
- Rect2 current_tile_region = Rect2();
- if (get_current_tile() >= 0) {
- current_tile_region = tileset->tile_get_region(get_current_tile());
- }
- current_tile_region.position += WORKSPACE_MARGIN;
-
- const Ref<InputEventMouseButton> mb = p_ie;
- const Ref<InputEventMouseMotion> mm = p_ie;
-
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !creating_shape) {
- if (!current_tile_region.has_point(mb->get_position())) {
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- int t_id = E->get();
- if (get_current_texture()->get_rid() == tileset->tile_get_texture(t_id)->get_rid()) {
- Rect2 r = tileset->tile_get_region(t_id);
- r.position += WORKSPACE_MARGIN;
- if (r.has_point(mb->get_position())) {
- set_current_tile(t_id);
- workspace->update();
- workspace_overlay->update();
- delete tiles;
- return;
- }
- }
- }
- delete tiles;
- }
- }
- }
- // Drag Middle Mouse
- if (mm.is_valid()) {
- if (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) {
- Vector2 dragged(mm->get_relative().x, mm->get_relative().y);
- scroll->set_h_scroll(scroll->get_h_scroll() - dragged.x * workspace->get_scale().x);
- scroll->set_v_scroll(scroll->get_v_scroll() - dragged.y * workspace->get_scale().x);
- }
- }
-
- if (edit_mode == EDITMODE_REGION) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (get_current_tile() >= 0 || workspace_mode != WORKSPACE_EDIT) {
- dragging = true;
- region_from = mb->get_position();
- edited_region = Rect2(region_from, Size2());
- workspace->update();
- workspace_overlay->update();
- return;
- }
- } else if (dragging && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- dragging = false;
- edited_region = Rect2();
- workspace->update();
- workspace_overlay->update();
- return;
- } else if (dragging && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- dragging = false;
- update_edited_region(mb->get_position());
- edited_region.position -= WORKSPACE_MARGIN;
- if (!edited_region.has_no_area()) {
- if (get_current_tile() >= 0 && workspace_mode == WORKSPACE_EDIT) {
- undo_redo->create_action(TTR("Set Tile Region"));
- undo_redo->add_do_method(tileset.ptr(), "tile_set_region", get_current_tile(), edited_region);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", get_current_tile(), tileset->tile_get_region(get_current_tile()));
-
- Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2;
- Size2 workspace_minsize = workspace->get_custom_minimum_size();
- // If the new region is bigger, just directly change the workspace size to avoid checking all other tiles.
- if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) {
- Size2 max_workspace_size = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y));
- undo_redo->add_do_method(workspace, "set_custom_minimum_size", max_workspace_size);
- undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", max_workspace_size);
- undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", max_workspace_size);
- undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize);
- } else if (workspace_minsize.x > get_current_texture()->get_size().x + WORKSPACE_MARGIN.x * 2 || workspace_minsize.y > get_current_texture()->get_size().y + WORKSPACE_MARGIN.y * 2) {
- undo_redo->add_do_method(this, "update_workspace_minsize");
- undo_redo->add_undo_method(this, "update_workspace_minsize");
- }
-
- edited_region = Rect2();
-
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->add_do_method(workspace_overlay, "update");
- undo_redo->add_undo_method(workspace_overlay, "update");
- undo_redo->commit_action();
- } else {
- int t_id = tileset->get_last_unused_tile_id();
- undo_redo->create_action(TTR("Create Tile"));
- undo_redo->add_do_method(tileset.ptr(), "create_tile", t_id);
- undo_redo->add_undo_method(tileset.ptr(), "remove_tile", t_id);
- undo_redo->add_undo_method(this, "_validate_current_tile_id");
- undo_redo->add_do_method(tileset.ptr(), "tile_set_texture", t_id, get_current_texture());
- undo_redo->add_do_method(tileset.ptr(), "tile_set_region", t_id, edited_region);
- undo_redo->add_do_method(tileset.ptr(), "tile_set_name", t_id, get_current_texture()->get_path().get_file() + " " + String::num(t_id, 0));
- if (workspace_mode != WORKSPACE_CREATE_SINGLE) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_size", t_id, snap_step);
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_spacing", t_id, snap_separation.x);
- undo_redo->add_do_method(tileset.ptr(), "tile_set_tile_mode", t_id, workspace_mode == WORKSPACE_CREATE_AUTOTILE ? TileSet::AUTO_TILE : TileSet::ATLAS_TILE);
- }
-
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
-
- Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2;
- Size2 workspace_minsize = workspace->get_custom_minimum_size();
- if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) {
- Size2 new_workspace_minsize = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y));
- undo_redo->add_do_method(workspace, "set_custom_minimum_size", new_workspace_minsize);
- undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", new_workspace_minsize);
- undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize);
- undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", new_workspace_minsize);
- undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize);
- }
-
- edited_region = Rect2();
-
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->add_do_method(workspace_overlay, "update");
- undo_redo->add_undo_method(workspace_overlay, "update");
- undo_redo->commit_action();
-
- set_current_tile(t_id);
- _on_workspace_mode_changed(WORKSPACE_EDIT);
- }
- } else {
- edited_region = Rect2();
- workspace->update();
- workspace_overlay->update();
- }
- return;
- }
- } else if (mm.is_valid()) {
- if (dragging) {
- update_edited_region(mm->get_position());
- draw_edited_region = true;
- workspace->update();
- workspace_overlay->update();
- return;
- }
- }
- }
-
- if (workspace_mode == WORKSPACE_EDIT) {
- if (get_current_tile() >= 0) {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- switch (edit_mode) {
- case EDITMODE_ICON: {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && current_tile_region.has_point(mb->get_position())) {
- Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- undo_redo->create_action(TTR("Set Tile Icon"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), tileset->autotile_get_icon_coordinate(get_current_tile()));
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- }
- }
- } break;
- case EDITMODE_BITMASK: {
- if (mb.is_valid()) {
- if (mb->is_pressed()) {
- if (dragging) {
- return;
- }
- if ((mb->get_button_index() == MOUSE_BUTTON_RIGHT || mb->get_button_index() == MOUSE_BUTTON_LEFT) && current_tile_region.has_point(mb->get_position())) {
- dragging = true;
- erasing = (mb->get_button_index() == MOUSE_BUTTON_RIGHT);
- alternative = Input::get_singleton()->is_key_pressed(KEY_SHIFT);
- Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y));
- pos = mb->get_position() - (pos + current_tile_region.position);
- uint32_t bit = 0;
- if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) {
- if (pos.x < size.x / 2) {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPLEFT;
- } else {
- bit = TileSet::BIND_BOTTOMLEFT;
- }
- } else {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPRIGHT;
- } else {
- bit = TileSet::BIND_BOTTOMRIGHT;
- }
- }
- } else {
- if (pos.x < size.x / 3) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPLEFT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMLEFT;
- } else {
- bit = TileSet::BIND_LEFT;
- }
- } else if (pos.x > (size.x / 3) * 2) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPRIGHT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMRIGHT;
- } else {
- bit = TileSet::BIND_RIGHT;
- }
- } else {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOP;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOM;
- } else {
- bit = TileSet::BIND_CENTER;
- }
- }
- }
-
- uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
- uint32_t new_mask = old_mask;
- if (alternative) {
- new_mask &= ~bit;
- new_mask |= (bit << 16);
- } else if (erasing) {
- new_mask &= ~bit;
- new_mask &= ~(bit << 16);
- } else {
- new_mask |= bit;
- new_mask &= ~(bit << 16);
- }
-
- if (old_mask != new_mask) {
- undo_redo->create_action(TTR("Edit Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask);
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- }
- }
- } else {
- if ((erasing && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (!erasing && mb->get_button_index() == MOUSE_BUTTON_LEFT)) {
- dragging = false;
- erasing = false;
- alternative = false;
- }
- }
- }
- if (mm.is_valid()) {
- if (dragging && current_tile_region.has_point(mm->get_position())) {
- Vector2 coord((int)((mm->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mm->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y));
- pos = mm->get_position() - (pos + current_tile_region.position);
- uint32_t bit = 0;
- if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) {
- if (pos.x < size.x / 2) {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPLEFT;
- } else {
- bit = TileSet::BIND_BOTTOMLEFT;
- }
- } else {
- if (pos.y < size.y / 2) {
- bit = TileSet::BIND_TOPRIGHT;
- } else {
- bit = TileSet::BIND_BOTTOMRIGHT;
- }
- }
- } else {
- if (pos.x < size.x / 3) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPLEFT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMLEFT;
- } else {
- bit = TileSet::BIND_LEFT;
- }
- } else if (pos.x > (size.x / 3) * 2) {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOPRIGHT;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOMRIGHT;
- } else {
- bit = TileSet::BIND_RIGHT;
- }
- } else {
- if (pos.y < size.y / 3) {
- bit = TileSet::BIND_TOP;
- } else if (pos.y > (size.y / 3) * 2) {
- bit = TileSet::BIND_BOTTOM;
- } else {
- bit = TileSet::BIND_CENTER;
- }
- }
- }
-
- uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord);
- uint32_t new_mask = old_mask;
- if (alternative) {
- new_mask &= ~bit;
- new_mask |= (bit << 16);
- } else if (erasing) {
- new_mask &= ~bit;
- new_mask &= ~(bit << 16);
- } else {
- new_mask |= bit;
- new_mask &= ~(bit << 16);
- }
- if (old_mask != new_mask) {
- undo_redo->create_action(TTR("Edit Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask);
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- }
- }
- }
- } break;
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION:
- case EDITMODE_PRIORITY:
- case EDITMODE_Z_INDEX: {
- Vector2 shape_anchor = Vector2(0, 0);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- shape_anchor = edited_shape_coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- }
-
- const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
- shape_anchor += current_tile_region.position;
- if (tools[TOOL_SELECT]->is_pressed()) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (edit_mode != EDITMODE_PRIORITY && current_shape.size() > 0) {
- int grabbed_point = get_grabbed_point(mb->get_position(), grab_threshold);
-
- if (grabbed_point >= 0) {
- dragging_point = grabbed_point;
- workspace->update();
- return;
- }
- }
- if ((tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) && current_tile_region.has_point(mb->get_position())) {
- Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y)));
- if (edited_shape_coord != coord) {
- edited_shape_coord = coord;
- _select_edited_shape_coord();
- }
- }
- workspace->update();
- } else if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (edit_mode == EDITMODE_COLLISION) {
- if (dragging_point >= 0) {
- dragging_point = -1;
-
- Vector<Vector2> points;
-
- for (int i = 0; i < current_shape.size(); i++) {
- Vector2 p = current_shape[i];
- if (tools[TOOL_GRID_SNAP]->is_pressed() || tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) {
- p = snap_point(p);
- }
- points.push_back(p - shape_anchor);
- }
-
- undo_redo->create_action(TTR("Edit Collision Polygon"));
- _set_edited_shape_points(points);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- if (dragging_point >= 0) {
- dragging_point = -1;
-
- Vector<Vector2> polygon;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- }
-
- undo_redo->create_action(TTR("Edit Occlusion Polygon"));
- undo_redo->add_do_method(edited_occlusion_shape.ptr(), "set_polygon", polygon);
- undo_redo->add_undo_method(edited_occlusion_shape.ptr(), "set_polygon", edited_occlusion_shape->get_polygon());
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- if (dragging_point >= 0) {
- dragging_point = -1;
-
- Vector<Vector2> polygon;
- Vector<int> indices;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- indices.push_back(i);
- }
-
- undo_redo->create_action(TTR("Edit Navigation Polygon"));
- undo_redo->add_do_method(edited_navigation_shape.ptr(), "set_vertices", polygon);
- undo_redo->add_undo_method(edited_navigation_shape.ptr(), "set_vertices", edited_navigation_shape->get_vertices());
- undo_redo->add_do_method(edited_navigation_shape.ptr(), "clear_polygons");
- undo_redo->add_undo_method(edited_navigation_shape.ptr(), "clear_polygons");
- undo_redo->add_do_method(edited_navigation_shape.ptr(), "add_polygon", indices);
- undo_redo->add_undo_method(edited_navigation_shape.ptr(), "add_polygon", edited_navigation_shape->get_polygon(0));
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- }
- }
- } else if (mm.is_valid()) {
- if (dragging_point >= 0) {
- current_shape.set(dragging_point, snap_point(mm->get_position()));
- workspace->update();
- }
- }
- } else if (tools[SHAPE_NEW_POLYGON]->is_pressed()) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- Vector2 pos = mb->get_position();
- pos = snap_point(pos);
- if (creating_shape) {
- if (current_shape.size() > 2) {
- if (is_within_grabbing_distance_of_first_point(mb->get_position(), grab_threshold)) {
- close_shape(shape_anchor);
- workspace->update();
- return;
- }
- }
- current_shape.push_back(pos);
- workspace->update();
- } else {
- creating_shape = true;
- _set_edited_collision_shape(Ref<ConvexPolygonShape2D>());
- current_shape.resize(0);
- current_shape.push_back(snap_point(pos));
- workspace->update();
- }
- } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- if (creating_shape) {
- creating_shape = false;
- _select_edited_shape_coord();
- workspace->update();
- }
- }
- } else if (mm.is_valid()) {
- if (creating_shape) {
- workspace->update();
- }
- }
- } else if (tools[SHAPE_NEW_RECTANGLE]->is_pressed()) {
- if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- _set_edited_collision_shape(Ref<ConvexPolygonShape2D>());
- current_shape.resize(0);
- Vector2 pos = mb->get_position();
- pos = snap_point(pos);
- current_shape.push_back(pos);
- current_shape.push_back(pos);
- current_shape.push_back(pos);
- current_shape.push_back(pos);
- creating_shape = true;
- workspace->update();
- return;
- } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
- if (creating_shape) {
- creating_shape = false;
- _select_edited_shape_coord();
- workspace->update();
- }
- } else if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (creating_shape) {
- // if the first two corners are within grabbing distance of one another, expand the rect to fill the tile
- if (is_within_grabbing_distance_of_first_point(current_shape[1], grab_threshold)) {
- current_shape.set(0, snap_point(shape_anchor));
- current_shape.set(1, snap_point(shape_anchor + Vector2(current_tile_region.size.x, 0)));
- current_shape.set(2, snap_point(shape_anchor + current_tile_region.size));
- current_shape.set(3, snap_point(shape_anchor + Vector2(0, current_tile_region.size.y)));
- }
-
- close_shape(shape_anchor);
- workspace->update();
- return;
- }
- }
- } else if (mm.is_valid()) {
- if (creating_shape) {
- Vector2 pos = mm->get_position();
- pos = snap_point(pos);
- Vector2 p = current_shape[2];
- current_shape.set(3, snap_point(Vector2(pos.x, p.y)));
- current_shape.set(0, snap_point(pos));
- current_shape.set(1, snap_point(Vector2(p.x, pos.y)));
- workspace->update();
- }
- }
- }
- } break;
- default: {
- }
- }
- }
- }
-}
-
-void TileSetEditor::_on_tool_clicked(int p_tool) {
- if (p_tool == BITMASK_COPY) {
- bitmask_map_copy = tileset->autotile_get_bitmask_map(get_current_tile());
- } else if (p_tool == BITMASK_PASTE) {
- undo_redo->create_action(TTR("Paste Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
- undo_redo->add_undo_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
- for (Map<Vector2, uint32_t>::Element *E = bitmask_map_copy.front(); E; E = E->next()) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
- }
- for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
- }
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- } else if (p_tool == BITMASK_CLEAR) {
- undo_redo->create_action(TTR("Clear Tile Bitmask"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile());
- for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value());
- }
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
- } else if (p_tool == SHAPE_TOGGLE_TYPE) {
- if (edited_collision_shape.is_valid()) {
- Ref<ConvexPolygonShape2D> convex = edited_collision_shape;
- Ref<ConcavePolygonShape2D> concave = edited_collision_shape;
- Ref<Shape2D> previous_shape = edited_collision_shape;
- Array sd = tileset->call("tile_get_shapes", get_current_tile());
-
- if (convex.is_valid()) {
- // Make concave.
- undo_redo->create_action(TTR("Make Polygon Concave"));
- Ref<ConcavePolygonShape2D> _concave = memnew(ConcavePolygonShape2D);
- edited_collision_shape = _concave;
- _set_edited_shape_points(_get_collision_shape_points(convex));
- } else if (concave.is_valid()) {
- // Make convex.
- undo_redo->create_action(TTR("Make Polygon Convex"));
- Ref<ConvexPolygonShape2D> _convex = memnew(ConvexPolygonShape2D);
- edited_collision_shape = _convex;
- _set_edited_shape_points(_get_collision_shape_points(concave));
- }
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].get("shape") == previous_shape) {
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
- sd.remove(i);
- break;
- }
- }
-
- undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D(), false, edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D());
- }
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
-
- _update_toggle_shape_button();
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
- }
- } else if (p_tool == SELECT_NEXT) {
- _select_next_shape();
- } else if (p_tool == SELECT_PREVIOUS) {
- _select_previous_shape();
- } else if (p_tool == SHAPE_DELETE) {
- if (creating_shape) {
- creating_shape = false;
- current_shape.resize(0);
- workspace->update();
- } else {
- switch (edit_mode) {
- case EDITMODE_REGION: {
- int t_id = get_current_tile();
- if (workspace_mode == WORKSPACE_EDIT && t_id >= 0) {
- undo_redo->create_action(TTR("Remove Tile"));
- undo_redo->add_do_method(tileset.ptr(), "remove_tile", t_id);
- _undo_tile_removal(t_id);
- undo_redo->add_do_method(this, "_validate_current_tile_id");
-
- Rect2 tile_region = tileset->tile_get_region(get_current_tile());
- Size2 tile_workspace_size = tile_region.position + tile_region.size;
- if (tile_workspace_size.x > get_current_texture()->get_size().x || tile_workspace_size.y > get_current_texture()->get_size().y) {
- undo_redo->add_do_method(this, "update_workspace_minsize");
- undo_redo->add_undo_method(this, "update_workspace_minsize");
- }
-
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->add_do_method(workspace_overlay, "update");
- undo_redo->add_undo_method(workspace_overlay, "update");
- undo_redo->commit_action();
- }
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- workspace_mode = WORKSPACE_EDIT;
- update_workspace_tile_mode();
- } break;
- case EDITMODE_COLLISION: {
- if (!edited_collision_shape.is_null()) {
- // Necessary to get the version that returns a Array instead of a Vector.
- Array sd = tileset->call("tile_get_shapes", get_current_tile());
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].get("shape") == edited_collision_shape) {
- undo_redo->create_action(TTR("Remove Collision Polygon"));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
- sd.remove(i);
- undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- break;
- }
- }
- }
- } break;
- case EDITMODE_OCCLUSION: {
- if (!edited_occlusion_shape.is_null()) {
- undo_redo->create_action(TTR("Remove Occlusion Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>());
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile()));
- } else {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>(), edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord);
- }
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } break;
- case EDITMODE_NAVIGATION: {
- if (!edited_navigation_shape.is_null()) {
- undo_redo->create_action(TTR("Remove Navigation Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>());
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile()));
- } else {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>(), edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord);
- }
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- } break;
- default: {
- }
- }
- }
- } else if (p_tool == TOOL_SELECT || p_tool == SHAPE_NEW_POLYGON || p_tool == SHAPE_NEW_RECTANGLE) {
- if (creating_shape) {
- // Cancel Creation
- creating_shape = false;
- current_shape.resize(0);
- workspace->update();
- }
- }
-}
-
-void TileSetEditor::_on_priority_changed(float val) {
- if ((int)val == tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)) {
- return;
- }
-
- undo_redo->create_action(TTR("Edit Tile Priority"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, (int)val);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord));
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
-}
-
-void TileSetEditor::_on_z_index_changed(float val) {
- if ((int)val == tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)) {
- return;
- }
-
- undo_redo->create_action(TTR("Edit Tile Z Index"));
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, (int)val);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord));
- undo_redo->add_do_method(workspace, "update");
- undo_redo->add_undo_method(workspace, "update");
- undo_redo->commit_action();
-}
-
-void TileSetEditor::_on_grid_snap_toggled(bool p_val) {
- helper->set_snap_options_visible(p_val);
- workspace->update();
-}
-
-Vector<Vector2> TileSetEditor::_get_collision_shape_points(const Ref<Shape2D> &p_shape) {
- Ref<ConvexPolygonShape2D> convex = p_shape;
- Ref<ConcavePolygonShape2D> concave = p_shape;
- if (convex.is_valid()) {
- return convex->get_points();
- } else if (concave.is_valid()) {
- Vector<Vector2> points;
- for (int i = 0; i < concave->get_segments().size(); i += 2) {
- points.push_back(concave->get_segments()[i]);
- }
- return points;
- } else {
- return Vector<Vector2>();
- }
-}
-
-Vector<Vector2> TileSetEditor::_get_edited_shape_points() {
- return _get_collision_shape_points(edited_collision_shape);
-}
-
-void TileSetEditor::_set_edited_shape_points(const Vector<Vector2> &points) {
- Ref<ConvexPolygonShape2D> convex = edited_collision_shape;
- Ref<ConcavePolygonShape2D> concave = edited_collision_shape;
- if (convex.is_valid()) {
- undo_redo->add_do_method(convex.ptr(), "set_points", points);
- undo_redo->add_undo_method(convex.ptr(), "set_points", _get_edited_shape_points());
- } else if (concave.is_valid() && points.size() > 1) {
- PackedVector2Array segments;
- for (int i = 0; i < points.size() - 1; i++) {
- segments.push_back(points[i]);
- segments.push_back(points[i + 1]);
- }
- segments.push_back(points[points.size() - 1]);
- segments.push_back(points[0]);
- undo_redo->add_do_method(concave.ptr(), "set_segments", segments);
- undo_redo->add_undo_method(concave.ptr(), "set_segments", concave->get_segments());
- }
-}
-
-void TileSetEditor::_update_tile_data() {
- current_tile_data.clear();
- if (get_current_tile() < 0) {
- return;
- }
-
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile());
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- SubtileData data;
- for (int i = 0; i < sd.size(); i++) {
- data.collisions.push_back(sd[i].shape);
- }
- data.navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile());
- data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile());
- current_tile_data[Vector2i()] = data;
- } else {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- for (int y = 0; y < cell_count.y; y++) {
- for (int x = 0; x < cell_count.x; x++) {
- SubtileData data;
- Vector2i coord(x, y);
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].autotile_coord == coord) {
- data.collisions.push_back(sd[i].shape);
- }
- }
- data.navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord);
- data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile());
- current_tile_data[coord] = data;
- }
- }
- }
-}
-
-void TileSetEditor::_update_toggle_shape_button() {
- Ref<ConvexPolygonShape2D> convex = edited_collision_shape;
- Ref<ConcavePolygonShape2D> concave = edited_collision_shape;
- separator_shape_toggle->show();
- tools[SHAPE_TOGGLE_TYPE]->show();
- if (edit_mode != EDITMODE_COLLISION || !edited_collision_shape.is_valid()) {
- separator_shape_toggle->hide();
- tools[SHAPE_TOGGLE_TYPE]->hide();
- } else if (concave.is_valid()) {
- tools[SHAPE_TOGGLE_TYPE]->set_icon(get_theme_icon("ConvexPolygonShape2D", "EditorIcons"));
- tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Convex"));
- } else if (convex.is_valid()) {
- tools[SHAPE_TOGGLE_TYPE]->set_icon(get_theme_icon("ConcavePolygonShape2D", "EditorIcons"));
- tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Concave"));
- } else {
- // Shouldn't happen
- separator_shape_toggle->hide();
- tools[SHAPE_TOGGLE_TYPE]->hide();
- }
-}
-
-void TileSetEditor::_select_next_tile() {
- Array tiles = _get_tiles_in_current_texture(true);
- if (tiles.size() == 0) {
- set_current_tile(-1);
- } else if (get_current_tile() == -1) {
- set_current_tile(tiles[0]);
- } else {
- int index = tiles.find(get_current_tile());
- if (index < 0) {
- set_current_tile(tiles[0]);
- } else if (index == tiles.size() - 1) {
- set_current_tile(tiles[0]);
- } else {
- set_current_tile(tiles[index + 1]);
- }
- }
- if (get_current_tile() == -1) {
- return;
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- return;
- } else {
- switch (edit_mode) {
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION:
- case EDITMODE_PRIORITY:
- case EDITMODE_Z_INDEX: {
- edited_shape_coord = Vector2();
- _select_edited_shape_coord();
- } break;
- default: {
- }
- }
- }
-}
-
-void TileSetEditor::_select_previous_tile() {
- Array tiles = _get_tiles_in_current_texture(true);
- if (tiles.size() == 0) {
- set_current_tile(-1);
- } else if (get_current_tile() == -1) {
- set_current_tile(tiles[tiles.size() - 1]);
- } else {
- int index = tiles.find(get_current_tile());
- if (index <= 0) {
- set_current_tile(tiles[tiles.size() - 1]);
- } else {
- set_current_tile(tiles[index - 1]);
- }
- }
- if (get_current_tile() == -1) {
- return;
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- return;
- } else {
- switch (edit_mode) {
- case EDITMODE_COLLISION:
- case EDITMODE_OCCLUSION:
- case EDITMODE_NAVIGATION:
- case EDITMODE_PRIORITY:
- case EDITMODE_Z_INDEX: {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- cell_count -= Vector2(1, 1);
- edited_shape_coord = cell_count;
- _select_edited_shape_coord();
- } break;
- default: {
- }
- }
- }
-}
-
-Array TileSetEditor::_get_tiles_in_current_texture(bool sorted) {
- Array a;
- List<int> all_tiles;
- if (!get_current_texture().is_valid()) {
- return a;
- }
- tileset->get_tile_list(&all_tiles);
- for (int i = 0; i < all_tiles.size(); i++) {
- if (tileset->tile_get_texture(all_tiles[i]) == get_current_texture()) {
- a.push_back(all_tiles[i]);
- }
- }
- if (sorted) {
- a.sort_custom(callable_mp(this, &TileSetEditor::_sort_tiles));
- }
- return a;
-}
-
-bool TileSetEditor::_sort_tiles(Variant p_a, Variant p_b) {
- int a = p_a;
- int b = p_b;
-
- Vector2 pos_a = tileset->tile_get_region(a).position;
- Vector2 pos_b = tileset->tile_get_region(b).position;
- if (pos_a.y < pos_b.y) {
- return true;
-
- } else if (pos_a.y == pos_b.y) {
- return (pos_a.x < pos_b.x);
- } else {
- return false;
- }
-}
-
-void TileSetEditor::_select_next_subtile() {
- if (get_current_tile() == -1) {
- _select_next_tile();
- return;
- }
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- _select_next_tile();
- } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) {
- _select_next_tile();
- } else {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- if (edited_shape_coord.x >= cell_count.x - 1 && edited_shape_coord.y >= cell_count.y - 1) {
- _select_next_tile();
- } else {
- edited_shape_coord.x++;
- if (edited_shape_coord.x >= cell_count.x) {
- edited_shape_coord.x = 0;
- edited_shape_coord.y++;
- }
- _select_edited_shape_coord();
- }
- }
-}
-
-void TileSetEditor::_select_previous_subtile() {
- if (get_current_tile() == -1) {
- _select_previous_tile();
- return;
- }
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- _select_previous_tile();
- } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) {
- _select_previous_tile();
- } else {
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->tile_get_region(get_current_tile()).size;
- Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor();
- if (edited_shape_coord.x <= 0 && edited_shape_coord.y <= 0) {
- _select_previous_tile();
- } else {
- edited_shape_coord.x--;
- if (edited_shape_coord.x == -1) {
- edited_shape_coord.x = cell_count.x - 1;
- edited_shape_coord.y--;
- }
- _select_edited_shape_coord();
- }
- }
-}
-
-void TileSetEditor::_select_next_shape() {
- if (get_current_tile() == -1) {
- _select_next_subtile();
- } else if (edit_mode != EDITMODE_COLLISION) {
- _select_next_subtile();
- } else {
- Vector2i edited_coord = Vector2i();
- if (tileset->tile_get_tile_mode(get_current_tile()) != TileSet::SINGLE_TILE) {
- edited_coord = Vector2i(edited_shape_coord);
- }
- SubtileData data = current_tile_data[edited_coord];
- if (data.collisions.size() == 0) {
- _select_next_subtile();
- } else {
- int index = data.collisions.find(edited_collision_shape);
- if (index < 0) {
- _set_edited_collision_shape(data.collisions[0]);
- } else if (index == data.collisions.size() - 1) {
- _select_next_subtile();
- } else {
- _set_edited_collision_shape(data.collisions[index + 1]);
- }
- }
- current_shape.resize(0);
- Rect2 current_tile_region = tileset->tile_get_region(get_current_tile());
- current_tile_region.position += WORKSPACE_MARGIN;
-
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Vector2 shape_anchor = edited_shape_coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- current_tile_region.position += shape_anchor;
-
- if (edited_collision_shape.is_valid()) {
- for (int i = 0; i < _get_edited_shape_points().size(); i++) {
- current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position);
- }
- }
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
- }
-}
-
-void TileSetEditor::_select_previous_shape() {
- if (get_current_tile() == -1) {
- _select_previous_subtile();
- if (get_current_tile() != -1 && edit_mode == EDITMODE_COLLISION) {
- SubtileData data = current_tile_data[Vector2i(edited_shape_coord)];
- if (data.collisions.size() > 1) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- }
- } else {
- return;
- }
- } else if (edit_mode != EDITMODE_COLLISION) {
- _select_previous_subtile();
- } else {
- Vector2i edited_coord = Vector2i();
- if (tileset->tile_get_tile_mode(get_current_tile()) != TileSet::SINGLE_TILE) {
- edited_coord = Vector2i(edited_shape_coord);
- }
- SubtileData data = current_tile_data[edited_coord];
- if (data.collisions.size() == 0) {
- _select_previous_subtile();
- data = current_tile_data[Vector2i(edited_shape_coord)];
- if (data.collisions.size() > 1) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- }
- } else {
- int index = data.collisions.find(edited_collision_shape);
- if (index < 0) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- } else if (index == 0) {
- _select_previous_subtile();
- data = current_tile_data[Vector2i(edited_shape_coord)];
- if (data.collisions.size() > 1) {
- _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]);
- }
- } else {
- _set_edited_collision_shape(data.collisions[index - 1]);
- }
- }
-
- current_shape.resize(0);
- Rect2 current_tile_region = tileset->tile_get_region(get_current_tile());
- current_tile_region.position += WORKSPACE_MARGIN;
-
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Vector2 shape_anchor = edited_shape_coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- current_tile_region.position += shape_anchor;
-
- if (edited_collision_shape.is_valid()) {
- for (int i = 0; i < _get_edited_shape_points().size(); i++) {
- current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position);
- }
- }
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
- }
-}
-
-void TileSetEditor::_set_edited_collision_shape(const Ref<Shape2D> &p_shape) {
- edited_collision_shape = p_shape;
- _update_toggle_shape_button();
-}
-
-void TileSetEditor::_set_snap_step(Vector2 p_val) {
- snap_step.x = CLAMP(p_val.x, 1, 256);
- snap_step.y = CLAMP(p_val.y, 1, 256);
- workspace->update();
-}
-
-void TileSetEditor::_set_snap_off(Vector2 p_val) {
- snap_offset.x = CLAMP(p_val.x, 0, 256 + WORKSPACE_MARGIN.x);
- snap_offset.y = CLAMP(p_val.y, 0, 256 + WORKSPACE_MARGIN.y);
- workspace->update();
-}
-
-void TileSetEditor::_set_snap_sep(Vector2 p_val) {
- snap_separation.x = CLAMP(p_val.x, 0, 256);
- snap_separation.y = CLAMP(p_val.y, 0, 256);
- workspace->update();
-}
-
-void TileSetEditor::_validate_current_tile_id() {
- if (get_current_tile() >= 0 && !tileset->has_tile(get_current_tile())) {
- set_current_tile(-1);
- }
-}
-
-void TileSetEditor::_select_edited_shape_coord() {
- select_coord(edited_shape_coord);
-}
-
-void TileSetEditor::_undo_tile_removal(int p_id) {
- undo_redo->add_undo_method(tileset.ptr(), "create_tile", p_id);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_name", p_id, tileset->tile_get_name(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture_offset", p_id, tileset->tile_get_texture_offset(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_material", p_id, tileset->tile_get_material(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_modulate", p_id, tileset->tile_get_modulate(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_occluder_offset", p_id, tileset->tile_get_occluder_offset(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon_offset", p_id, tileset->tile_get_navigation_polygon_offset(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_offset", p_id, 0, tileset->tile_get_shape_offset(p_id, 0));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_transform", p_id, 0, tileset->tile_get_shape_transform(p_id, 0));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_z_index", p_id, tileset->tile_get_z_index(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture", p_id, tileset->tile_get_texture(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", p_id, tileset->tile_get_region(p_id));
- // Necessary to get the version that returns a Array instead of a Vector.
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", p_id, tileset->call("tile_get_shapes", p_id));
- if (tileset->tile_get_tile_mode(p_id) == TileSet::SINGLE_TILE) {
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", p_id, tileset->tile_get_light_occluder(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", p_id, tileset->tile_get_navigation_polygon(p_id));
- } else {
- Map<Vector2, Ref<OccluderPolygon2D>> oclusion_map = tileset->autotile_get_light_oclusion_map(p_id);
- for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = oclusion_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", p_id, E->value(), E->key());
- }
- Map<Vector2, Ref<NavigationPolygon>> navigation_map = tileset->autotile_get_navigation_map(p_id);
- for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = navigation_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", p_id, E->value(), E->key());
- }
- Map<Vector2, uint32_t> bitmask_map = tileset->autotile_get_bitmask_map(p_id);
- for (Map<Vector2, uint32_t>::Element *E = bitmask_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", p_id, E->key(), E->value());
- }
- Map<Vector2, int> priority_map = tileset->autotile_get_priority_map(p_id);
- for (Map<Vector2, int>::Element *E = priority_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", p_id, E->key(), E->value());
- }
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", p_id, tileset->autotile_get_icon_coordinate(p_id));
- Map<Vector2, int> z_map = tileset->autotile_get_z_index_map(p_id);
- for (Map<Vector2, int>::Element *E = z_map.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", p_id, E->key(), E->value());
- }
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_tile_mode", p_id, tileset->tile_get_tile_mode(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_size", p_id, tileset->autotile_get_size(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_spacing", p_id, tileset->autotile_get_spacing(p_id));
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask_mode", p_id, tileset->autotile_get_bitmask_mode(p_id));
- }
-}
-
-void TileSetEditor::_zoom_in() {
- float scale = workspace->get_scale().x;
- if (scale < max_scale) {
- scale *= scale_ratio;
- workspace->set_scale(Vector2(scale, scale));
- workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale);
- workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale);
- }
-}
-
-void TileSetEditor::_zoom_out() {
- float scale = workspace->get_scale().x;
- if (scale > min_scale) {
- scale /= scale_ratio;
- workspace->set_scale(Vector2(scale, scale));
- workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale);
- workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale);
- }
-}
-
-void TileSetEditor::_zoom_reset() {
- workspace->set_scale(Vector2(1, 1));
- workspace_container->set_custom_minimum_size(workspace->get_rect().size);
- workspace_overlay->set_custom_minimum_size(workspace->get_rect().size);
-}
-
-void TileSetEditor::draw_highlight_current_tile() {
- Color shadow_color = Color(0.3, 0.3, 0.3, 0.3);
- if ((workspace_mode == WORKSPACE_EDIT && get_current_tile() >= 0) || !edited_region.has_no_area()) {
- Rect2 region;
- if (edited_region.has_no_area()) {
- region = tileset->tile_get_region(get_current_tile());
- region.position += WORKSPACE_MARGIN;
- } else {
- region = edited_region;
- }
-
- if (region.position.y >= 0) {
- workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, region.position.y), shadow_color);
- }
- if (region.position.x >= 0) {
- workspace->draw_rect(Rect2(0, MAX(0, region.position.y), region.position.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color);
- }
- if (region.position.x + region.size.x <= workspace->get_rect().size.x) {
- workspace->draw_rect(Rect2(region.position.x + region.size.x, MAX(0, region.position.y), workspace->get_rect().size.x - region.position.x - region.size.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color);
- }
- if (region.position.y + region.size.y <= workspace->get_rect().size.y) {
- workspace->draw_rect(Rect2(0, region.position.y + region.size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - region.size.y - region.position.y), shadow_color);
- }
- } else {
- workspace->draw_rect(Rect2(Point2(0, 0), workspace->get_rect().size), shadow_color);
- }
-}
-
-void TileSetEditor::draw_highlight_subtile(Vector2 coord, const Vector<Vector2> &other_highlighted) {
- Color shadow_color = Color(0.3, 0.3, 0.3, 0.3);
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Rect2 region = tileset->tile_get_region(get_current_tile());
- coord.x *= (size.x + spacing);
- coord.y *= (size.y + spacing);
- coord += region.position;
- coord += WORKSPACE_MARGIN;
-
- if (coord.y >= 0) {
- workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, coord.y), shadow_color);
- }
- if (coord.x >= 0) {
- workspace->draw_rect(Rect2(0, MAX(0, coord.y), coord.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color);
- }
- if (coord.x + size.x <= workspace->get_rect().size.x) {
- workspace->draw_rect(Rect2(coord.x + size.x, MAX(0, coord.y), workspace->get_rect().size.x - coord.x - size.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color);
- }
- if (coord.y + size.y <= workspace->get_rect().size.y) {
- workspace->draw_rect(Rect2(0, coord.y + size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - size.y - coord.y), shadow_color);
- }
-
- coord += Vector2(1, 1) / workspace->get_scale().x;
- workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0, 0), false);
- for (int i = 0; i < other_highlighted.size(); i++) {
- coord = other_highlighted[i];
- coord.x *= (size.x + spacing);
- coord.y *= (size.y + spacing);
- coord += region.position;
- coord += WORKSPACE_MARGIN;
- coord += Vector2(1, 1) / workspace->get_scale().x;
- workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0.5, 0.5), false);
- }
-}
-
-void TileSetEditor::draw_tile_subdivision(int p_id, Color p_color) const {
- Color c = p_color;
- if (tileset->tile_get_tile_mode(p_id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == TileSet::ATLAS_TILE) {
- Rect2 region = tileset->tile_get_region(p_id);
- Size2 size = tileset->autotile_get_size(p_id);
- int spacing = tileset->autotile_get_spacing(p_id);
- float j = size.x;
-
- while (j < region.size.x) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(j, 0), region.position + WORKSPACE_MARGIN + Point2(j, region.size.y), c);
- } else {
- workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(j, 0), Size2(spacing, region.size.y)), c);
- }
- j += spacing + size.x;
- }
- j = size.y;
- while (j < region.size.y) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(0, j), region.position + WORKSPACE_MARGIN + Point2(region.size.x, j), c);
- } else {
- workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(0, j), Size2(region.size.x, spacing)), c);
- }
- j += spacing + size.y;
- }
- }
-}
-
-void TileSetEditor::draw_edited_region_subdivision() const {
- Color c = Color(0.3, 0.7, 0.6);
- Rect2 region = edited_region;
- Size2 size;
- int spacing;
- bool draw;
-
- if (workspace_mode == WORKSPACE_EDIT) {
- int p_id = get_current_tile();
- size = tileset->autotile_get_size(p_id);
- spacing = tileset->autotile_get_spacing(p_id);
- draw = tileset->tile_get_tile_mode(p_id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == TileSet::ATLAS_TILE;
- } else {
- size = snap_step;
- spacing = snap_separation.x;
- draw = workspace_mode != WORKSPACE_CREATE_SINGLE;
- }
-
- if (draw) {
- float j = size.x;
- while (j < region.size.x) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + Point2(j, 0), region.position + Point2(j, region.size.y), c);
- } else {
- workspace->draw_rect(Rect2(region.position + Point2(j, 0), Size2(spacing, region.size.y)), c);
- }
- j += spacing + size.x;
- }
- j = size.y;
- while (j < region.size.y) {
- if (spacing <= 0) {
- workspace->draw_line(region.position + Point2(0, j), region.position + Point2(region.size.x, j), c);
- } else {
- workspace->draw_rect(Rect2(region.position + Point2(0, j), Size2(region.size.x, spacing)), c);
- }
- j += spacing + size.y;
- }
- }
-}
-
-void TileSetEditor::draw_grid_snap() {
- if (tools[TOOL_GRID_SNAP]->is_pressed()) {
- Color grid_color = Color(0.4, 0, 1);
- Size2 s = workspace->get_size();
-
- int width_count = Math::floor((s.width - WORKSPACE_MARGIN.x) / (snap_step.x + snap_separation.x));
- int height_count = Math::floor((s.height - WORKSPACE_MARGIN.y) / (snap_step.y + snap_separation.y));
-
- int last_p = 0;
- if (snap_step.x != 0) {
- for (int i = 0; i <= width_count; i++) {
- if (i == 0 && snap_offset.x != 0) {
- last_p = snap_offset.x;
- }
- if (snap_separation.x != 0) {
- if (i != 0) {
- workspace->draw_rect(Rect2(last_p, 0, snap_separation.x, s.height), grid_color);
- last_p += snap_separation.x;
- } else {
- workspace->draw_rect(Rect2(last_p, 0, -snap_separation.x, s.height), grid_color);
- }
- } else {
- workspace->draw_line(Point2(last_p, 0), Point2(last_p, s.height), grid_color);
- }
- last_p += snap_step.x;
- }
- }
- last_p = 0;
- if (snap_step.y != 0) {
- for (int i = 0; i <= height_count; i++) {
- if (i == 0 && snap_offset.y != 0) {
- last_p = snap_offset.y;
- }
- if (snap_separation.y != 0) {
- if (i != 0) {
- workspace->draw_rect(Rect2(0, last_p, s.width, snap_separation.y), grid_color);
- last_p += snap_separation.y;
- } else {
- workspace->draw_rect(Rect2(0, last_p, s.width, -snap_separation.y), grid_color);
- }
- } else {
- workspace->draw_line(Point2(0, last_p), Point2(s.width, last_p), grid_color);
- }
- last_p += snap_step.y;
- }
- }
- }
-}
-
-void TileSetEditor::draw_polygon_shapes() {
- int t_id = get_current_tile();
- if (t_id < 0) {
- return;
- }
-
- switch (edit_mode) {
- case EDITMODE_COLLISION: {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(t_id);
- for (int i = 0; i < sd.size(); i++) {
- Vector2 coord = Vector2(0, 0);
- Vector2 anchor = Vector2(0, 0);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- coord = sd[i].autotile_coord;
- anchor = tileset->autotile_get_size(t_id);
- anchor.x += tileset->autotile_get_spacing(t_id);
- anchor.y += tileset->autotile_get_spacing(t_id);
- anchor.x *= coord.x;
- anchor.y *= coord.y;
- }
- anchor += WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(t_id).position;
- Ref<Shape2D> shape = sd[i].shape;
- if (shape.is_valid()) {
- Color c_bg;
- Color c_border;
- Ref<ConvexPolygonShape2D> convex = shape;
- bool is_convex = convex.is_valid();
- if ((tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE || coord == edited_shape_coord) && sd[i].shape == edited_collision_shape) {
- if (is_convex) {
- c_bg = Color(0, 1, 1, 0.5);
- c_border = Color(0, 1, 1);
- } else {
- c_bg = Color(0.8, 0, 1, 0.5);
- c_border = Color(0.8, 0, 1);
- }
- } else {
- if (is_convex) {
- c_bg = Color(0.9, 0.7, 0.07, 0.5);
- c_border = Color(0.9, 0.7, 0.07, 1);
-
- } else {
- c_bg = Color(0.9, 0.45, 0.075, 0.5);
- c_border = Color(0.9, 0.45, 0.075);
- }
- }
- Vector<Vector2> polygon;
- Vector<Color> colors;
- if (!creating_shape && shape == edited_collision_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- for (int j = 0; j < _get_collision_shape_points(shape).size(); j++) {
- polygon.push_back(_get_collision_shape_points(shape)[j] + anchor);
- colors.push_back(c_bg);
- }
- }
-
- if (polygon.size() < 3) {
- continue;
- }
-
- workspace->draw_polygon(polygon, colors);
-
- if (coord == edited_shape_coord || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_collision_shape) {
- draw_handles = true;
- }
- }
- }
- }
- } break;
- case EDITMODE_OCCLUSION: {
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- Ref<OccluderPolygon2D> shape = edited_occlusion_shape;
- if (shape.is_valid()) {
- Color c_bg = Color(0, 1, 1, 0.5);
- Color c_border = Color(0, 1, 1);
-
- Vector<Vector2> polygon;
- Vector<Color> colors;
- Vector2 anchor = WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(get_current_tile()).position;
- if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- for (int j = 0; j < shape->get_polygon().size(); j++) {
- polygon.push_back(shape->get_polygon()[j] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_occlusion_shape) {
- draw_handles = true;
- }
- }
- } else {
- Map<Vector2, Ref<OccluderPolygon2D>> map = tileset->autotile_get_light_oclusion_map(t_id);
- for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = map.front(); E; E = E->next()) {
- Vector2 coord = E->key();
- Vector2 anchor = tileset->autotile_get_size(t_id);
- anchor.x += tileset->autotile_get_spacing(t_id);
- anchor.y += tileset->autotile_get_spacing(t_id);
- anchor.x *= coord.x;
- anchor.y *= coord.y;
- anchor += WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(t_id).position;
- Ref<OccluderPolygon2D> shape = E->value();
- if (shape.is_valid()) {
- Color c_bg;
- Color c_border;
- if (coord == edited_shape_coord && shape == edited_occlusion_shape) {
- c_bg = Color(0, 1, 1, 0.5);
- c_border = Color(0, 1, 1);
- } else {
- c_bg = Color(0.9, 0.7, 0.07, 0.5);
- c_border = Color(0.9, 0.7, 0.07, 1);
- }
- Vector<Vector2> polygon;
- Vector<Color> colors;
- if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- for (int j = 0; j < shape->get_polygon().size(); j++) {
- polygon.push_back(shape->get_polygon()[j] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (coord == edited_shape_coord) {
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_occlusion_shape) {
- draw_handles = true;
- }
- }
- }
- }
- }
- } break;
- case EDITMODE_NAVIGATION: {
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- Ref<NavigationPolygon> shape = edited_navigation_shape;
-
- if (shape.is_valid()) {
- Color c_bg = Color(0, 1, 1, 0.5);
- Color c_border = Color(0, 1, 1);
-
- Vector<Vector2> polygon;
- Vector<Color> colors;
- Vector2 anchor = WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(get_current_tile()).position;
- if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- Vector<Vector2> vertices = shape->get_vertices();
- for (int j = 0; j < shape->get_polygon(0).size(); j++) {
- polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_navigation_shape) {
- draw_handles = true;
- }
- }
- } else {
- Map<Vector2, Ref<NavigationPolygon>> map = tileset->autotile_get_navigation_map(t_id);
- for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = map.front(); E; E = E->next()) {
- Vector2 coord = E->key();
- Vector2 anchor = tileset->autotile_get_size(t_id);
- anchor.x += tileset->autotile_get_spacing(t_id);
- anchor.y += tileset->autotile_get_spacing(t_id);
- anchor.x *= coord.x;
- anchor.y *= coord.y;
- anchor += WORKSPACE_MARGIN;
- anchor += tileset->tile_get_region(t_id).position;
- Ref<NavigationPolygon> shape = E->value();
- if (shape.is_valid()) {
- Color c_bg;
- Color c_border;
- if (coord == edited_shape_coord && shape == edited_navigation_shape) {
- c_bg = Color(0, 1, 1, 0.5);
- c_border = Color(0, 1, 1);
- } else {
- c_bg = Color(0.9, 0.7, 0.07, 0.5);
- c_border = Color(0.9, 0.7, 0.07, 1);
- }
- Vector<Vector2> polygon;
- Vector<Color> colors;
- if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) {
- for (int j = 0; j < current_shape.size(); j++) {
- polygon.push_back(current_shape[j]);
- colors.push_back(c_bg);
- }
- } else {
- Vector<Vector2> vertices = shape->get_vertices();
- for (int j = 0; j < shape->get_polygon(0).size(); j++) {
- polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor);
- colors.push_back(c_bg);
- }
- }
- workspace->draw_polygon(polygon, colors);
-
- if (coord == edited_shape_coord) {
- if (!creating_shape && polygon.size() > 1) {
- for (int j = 0; j < polygon.size() - 1; j++) {
- workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1);
- }
- workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1);
- }
- if (shape == edited_navigation_shape) {
- draw_handles = true;
- }
- }
- }
- }
- }
- } break;
- default: {
- }
- }
-
- if (creating_shape && current_shape.size() > 1) {
- for (int j = 0; j < current_shape.size() - 1; j++) {
- workspace->draw_line(current_shape[j], current_shape[j + 1], Color(0, 1, 1), 1);
- }
- workspace->draw_line(current_shape[current_shape.size() - 1], snap_point(workspace->get_local_mouse_position()), Color(0, 1, 1), 1);
- draw_handles = true;
- }
-}
-
-void TileSetEditor::close_shape(const Vector2 &shape_anchor) {
- creating_shape = false;
-
- if (edit_mode == EDITMODE_COLLISION) {
- if (current_shape.size() >= 3) {
- Ref<ConvexPolygonShape2D> shape = memnew(ConvexPolygonShape2D);
-
- Vector<Vector2> points;
- float p_total = 0;
-
- for (int i = 0; i < current_shape.size(); i++) {
- points.push_back(current_shape[i] - shape_anchor);
-
- if (i != current_shape.size() - 1) {
- p_total += ((current_shape[i + 1].x - current_shape[i].x) * (-current_shape[i + 1].y + (-current_shape[i].y)));
- } else {
- p_total += ((current_shape[0].x - current_shape[i].x) * (-current_shape[0].y + (-current_shape[i].y)));
- }
- }
-
- if (p_total < 0) {
- points.reverse();
- }
-
- shape->set_points(points);
-
- undo_redo->create_action(TTR("Create Collision Polygon"));
- // Necessary to get the version that returns a Array instead of a Vector.
- Array sd = tileset->call("tile_get_shapes", get_current_tile());
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate());
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].get("shape") == edited_collision_shape) {
- sd.remove(i);
- break;
- }
- }
- undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D(), false, edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D());
- }
- tools[TOOL_SELECT]->set_pressed(true);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- } else {
- tools[TOOL_SELECT]->set_pressed(true);
- workspace->update();
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- Ref<OccluderPolygon2D> shape = memnew(OccluderPolygon2D);
-
- Vector<Vector2> polygon;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- }
-
- shape->set_polygon(polygon);
-
- undo_redo->create_action(TTR("Create Occlusion Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), shape, edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), shape);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile()));
- }
- tools[TOOL_SELECT]->set_pressed(true);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- Ref<NavigationPolygon> shape = memnew(NavigationPolygon);
-
- Vector<Vector2> polygon;
- Vector<int> indices;
- polygon.resize(current_shape.size());
- Vector2 *w = polygon.ptrw();
-
- for (int i = 0; i < current_shape.size(); i++) {
- w[i] = current_shape[i] - shape_anchor;
- indices.push_back(i);
- }
-
- shape->set_vertices(polygon);
- shape->add_polygon(indices);
-
- undo_redo->create_action(TTR("Create Navigation Polygon"));
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), shape, edited_shape_coord);
- undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord);
- } else {
- undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), shape);
- undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile()));
- }
- tools[TOOL_SELECT]->set_pressed(true);
- undo_redo->add_do_method(this, "_select_edited_shape_coord");
- undo_redo->add_undo_method(this, "_select_edited_shape_coord");
- undo_redo->commit_action();
- }
- tileset->notify_property_list_changed();
-}
-
-void TileSetEditor::select_coord(const Vector2 &coord) {
- _update_tile_data();
- current_shape = PackedVector2Array();
- if (get_current_tile() == -1) {
- return;
- }
- Rect2 current_tile_region = tileset->tile_get_region(get_current_tile());
- current_tile_region.position += WORKSPACE_MARGIN;
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- if (edited_collision_shape != tileset->tile_get_shape(get_current_tile(), 0)) {
- _set_edited_collision_shape(tileset->tile_get_shape(get_current_tile(), 0));
- }
- if (edited_occlusion_shape != tileset->tile_get_light_occluder(get_current_tile())) {
- edited_occlusion_shape = tileset->tile_get_light_occluder(get_current_tile());
- }
- if (edited_navigation_shape != tileset->tile_get_navigation_polygon(get_current_tile())) {
- edited_navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile());
- }
-
- if (edit_mode == EDITMODE_COLLISION) {
- current_shape.resize(0);
- if (edited_collision_shape.is_valid()) {
- for (int i = 0; i < _get_edited_shape_points().size(); i++) {
- current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position);
- }
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- current_shape.resize(0);
- if (edited_occlusion_shape.is_valid()) {
- for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) {
- current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + current_tile_region.position);
- }
- }
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- current_shape.resize(0);
- if (edited_navigation_shape.is_valid()) {
- if (edited_navigation_shape->get_polygon_count() > 0) {
- Vector<Vector2> vertices = edited_navigation_shape->get_vertices();
- for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) {
- current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + current_tile_region.position);
- }
- }
- }
- }
- } else {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile());
- bool found_collision_shape = false;
- for (int i = 0; i < sd.size(); i++) {
- if (sd[i].autotile_coord == coord) {
- if (edited_collision_shape != sd[i].shape) {
- _set_edited_collision_shape(sd[i].shape);
- }
- found_collision_shape = true;
- break;
- }
- }
- if (!found_collision_shape) {
- _set_edited_collision_shape(Ref<ConvexPolygonShape2D>(nullptr));
- }
- if (edited_occlusion_shape != tileset->autotile_get_light_occluder(get_current_tile(), coord)) {
- edited_occlusion_shape = tileset->autotile_get_light_occluder(get_current_tile(), coord);
- }
- if (edited_navigation_shape != tileset->autotile_get_navigation_polygon(get_current_tile(), coord)) {
- edited_navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord);
- }
-
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 size = tileset->autotile_get_size(get_current_tile());
- Vector2 shape_anchor = coord;
- shape_anchor.x *= (size.x + spacing);
- shape_anchor.y *= (size.y + spacing);
- shape_anchor += current_tile_region.position;
- if (edit_mode == EDITMODE_COLLISION) {
- current_shape.resize(0);
- if (edited_collision_shape.is_valid()) {
- for (int j = 0; j < _get_edited_shape_points().size(); j++) {
- current_shape.push_back(_get_edited_shape_points()[j] + shape_anchor);
- }
- }
- } else if (edit_mode == EDITMODE_OCCLUSION) {
- current_shape.resize(0);
- if (edited_occlusion_shape.is_valid()) {
- for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) {
- current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + shape_anchor);
- }
- }
- } else if (edit_mode == EDITMODE_NAVIGATION) {
- current_shape.resize(0);
- if (edited_navigation_shape.is_valid()) {
- if (edited_navigation_shape->get_polygon_count() > 0) {
- Vector<Vector2> vertices = edited_navigation_shape->get_vertices();
- for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) {
- current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + shape_anchor);
- }
- }
- }
- }
- }
- workspace->update();
- workspace_container->update();
- helper->notify_property_list_changed();
-}
-
-Vector2 TileSetEditor::snap_point(const Vector2 &point) {
- Vector2 p = point;
- Vector2 coord = edited_shape_coord;
- Vector2 tile_size = tileset->autotile_get_size(get_current_tile());
- int spacing = tileset->autotile_get_spacing(get_current_tile());
- Vector2 anchor = coord;
- anchor.x *= (tile_size.x + spacing);
- anchor.y *= (tile_size.y + spacing);
- anchor += tileset->tile_get_region(get_current_tile()).position;
- anchor += WORKSPACE_MARGIN;
- Rect2 region(anchor, tile_size);
- Rect2 tile_region(tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN, tileset->tile_get_region(get_current_tile()).size);
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- region.position = tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN;
- region.size = tileset->tile_get_region(get_current_tile()).size;
- }
-
- if (tools[TOOL_GRID_SNAP]->is_pressed()) {
- p.x = Math::snap_scalar_separation(snap_offset.x, snap_step.x, p.x, snap_separation.x);
- p.y = Math::snap_scalar_separation(snap_offset.y, snap_step.y, p.y, snap_separation.y);
- }
-
- if (tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) {
- if (p.x < region.position.x) {
- p.x = region.position.x;
- }
- if (p.y < region.position.y) {
- p.y = region.position.y;
- }
- if (p.x > region.position.x + region.size.x) {
- p.x = region.position.x + region.size.x;
- }
- if (p.y > region.position.y + region.size.y) {
- p.y = region.position.y + region.size.y;
- }
- }
-
- if (p.x < tile_region.position.x) {
- p.x = tile_region.position.x;
- }
- if (p.y < tile_region.position.y) {
- p.y = tile_region.position.y;
- }
- if (p.x > (tile_region.position.x + tile_region.size.x)) {
- p.x = (tile_region.position.x + tile_region.size.x);
- }
- if (p.y > (tile_region.position.y + tile_region.size.y)) {
- p.y = (tile_region.position.y + tile_region.size.y);
- }
-
- return p;
-}
-
-void TileSetEditor::add_texture(Ref<Texture2D> p_texture) {
- texture_list->add_item(p_texture->get_path().get_file());
- texture_map.insert(p_texture->get_rid(), p_texture);
- texture_list->set_item_metadata(texture_list->get_item_count() - 1, p_texture->get_rid());
-}
-
-void TileSetEditor::remove_texture(Ref<Texture2D> p_texture) {
- texture_list->remove_item(texture_list->find_metadata(p_texture->get_rid()));
- texture_map.erase(p_texture->get_rid());
-
- _validate_current_tile_id();
-
- if (!get_current_texture().is_valid()) {
- _on_texture_list_selected(-1);
- workspace_overlay->update();
- }
-}
-
-void TileSetEditor::update_texture_list() {
- Ref<Texture2D> selected_texture = get_current_texture();
-
- helper->set_tileset(tileset);
-
- List<int> ids;
- tileset->get_tile_list(&ids);
- Vector<int> ids_to_remove;
- for (List<int>::Element *E = ids.front(); E; E = E->next()) {
- // Clear tiles referencing gone textures (user has been already given the chance to fix broken deps)
- if (!tileset->tile_get_texture(E->get()).is_valid()) {
- ids_to_remove.push_back(E->get());
- ERR_CONTINUE(!tileset->tile_get_texture(E->get()).is_valid());
- }
-
- if (!texture_map.has(tileset->tile_get_texture(E->get())->get_rid())) {
- add_texture(tileset->tile_get_texture(E->get()));
- }
- }
- for (int i = 0; i < ids_to_remove.size(); i++) {
- tileset->remove_tile(ids_to_remove[i]);
- }
-
- if (texture_list->get_item_count() > 0 && selected_texture.is_valid()) {
- texture_list->select(texture_list->find_metadata(selected_texture->get_rid()));
- if (texture_list->get_selected_items().size() > 0) {
- _on_texture_list_selected(texture_list->get_selected_items()[0]);
- }
- } else if (get_current_texture().is_valid()) {
- _on_texture_list_selected(texture_list->find_metadata(get_current_texture()->get_rid()));
- } else {
- _validate_current_tile_id();
- _on_texture_list_selected(-1);
- workspace_overlay->update();
- }
- update_texture_list_icon();
- helper->notify_property_list_changed();
-}
-
-void TileSetEditor::update_texture_list_icon() {
- for (int current_idx = 0; current_idx < texture_list->get_item_count(); current_idx++) {
- RID rid = texture_list->get_item_metadata(current_idx);
- texture_list->set_item_icon(current_idx, texture_map[rid]);
- Size2 texture_size = texture_map[rid]->get_size();
- texture_list->set_item_icon_region(current_idx, Rect2(0, 0, MIN(texture_size.x, 150), MIN(texture_size.y, 100)));
- }
- texture_list->update();
-}
-
-void TileSetEditor::update_workspace_tile_mode() {
- if (!get_current_texture().is_valid()) {
- tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true);
- workspace_mode = WORKSPACE_EDIT;
- for (int i = 1; i < WORKSPACE_MODE_MAX; i++) {
- tool_workspacemode[i]->set_disabled(true);
- }
- tools[SELECT_NEXT]->set_disabled(true);
- tools[SELECT_PREVIOUS]->set_disabled(true);
-
- tools[ZOOM_OUT]->hide();
- tools[ZOOM_1]->hide();
- tools[ZOOM_IN]->hide();
- tools[VISIBLE_INFO]->hide();
-
- scroll->hide();
- empty_message->show();
- } else {
- for (int i = 1; i < WORKSPACE_MODE_MAX; i++) {
- tool_workspacemode[i]->set_disabled(false);
- }
- tools[SELECT_NEXT]->set_disabled(false);
- tools[SELECT_PREVIOUS]->set_disabled(false);
-
- tools[ZOOM_OUT]->show();
- tools[ZOOM_1]->show();
- tools[ZOOM_IN]->show();
- tools[VISIBLE_INFO]->show();
-
- scroll->show();
- empty_message->hide();
- }
-
- if (workspace_mode != WORKSPACE_EDIT) {
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->hide();
- }
- tool_editmode[EDITMODE_REGION]->show();
- tool_editmode[EDITMODE_REGION]->set_pressed(true);
- _on_edit_mode_changed(EDITMODE_REGION);
- separator_editmode->show();
- return;
- }
-
- if (get_current_tile() < 0) {
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->hide();
- }
- for (int i = TOOL_SELECT; i < ZOOM_OUT; i++) {
- tools[i]->hide();
- }
-
- separator_editmode->hide();
- separator_bitmask->hide();
- separator_delete->hide();
- separator_grid->hide();
- return;
- }
-
- for (int i = 0; i < EDITMODE_MAX; i++) {
- tool_editmode[i]->show();
- }
- separator_editmode->show();
-
- if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) {
- if (tool_editmode[EDITMODE_ICON]->is_pressed() || tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed() || tool_editmode[EDITMODE_Z_INDEX]->is_pressed()) {
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
- }
- select_coord(Vector2(0, 0));
-
- tool_editmode[EDITMODE_ICON]->hide();
- tool_editmode[EDITMODE_BITMASK]->hide();
- tool_editmode[EDITMODE_PRIORITY]->hide();
- tool_editmode[EDITMODE_Z_INDEX]->hide();
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE) {
- if (edit_mode == EDITMODE_ICON) {
- select_coord(tileset->autotile_get_icon_coordinate(get_current_tile()));
- } else {
- _select_edited_shape_coord();
- }
- } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) {
- if (tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed()) {
- tool_editmode[EDITMODE_COLLISION]->set_pressed(true);
- edit_mode = EDITMODE_COLLISION;
- }
- if (edit_mode == EDITMODE_ICON) {
- select_coord(tileset->autotile_get_icon_coordinate(get_current_tile()));
- } else {
- _select_edited_shape_coord();
- }
-
- tool_editmode[EDITMODE_BITMASK]->hide();
- }
- _on_edit_mode_changed(edit_mode);
-}
-
-void TileSetEditor::update_workspace_minsize() {
- Size2 workspace_min_size = get_current_texture()->get_size();
- RID current_texture_rid = get_current_texture()->get_rid();
- List<int> *tiles = new List<int>();
- tileset->get_tile_list(tiles);
- for (List<int>::Element *E = tiles->front(); E; E = E->next()) {
- if (tileset->tile_get_texture(E->get())->get_rid() != current_texture_rid) {
- continue;
- }
-
- Rect2i region = tileset->tile_get_region(E->get());
- if (region.position.x + region.size.x > workspace_min_size.x) {
- workspace_min_size.x = region.position.x + region.size.x;
- }
- if (region.position.y + region.size.y > workspace_min_size.y) {
- workspace_min_size.y = region.position.y + region.size.y;
- }
- }
- delete tiles;
-
- workspace->set_custom_minimum_size(workspace_min_size + WORKSPACE_MARGIN * 2);
- workspace_container->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2);
- workspace_overlay->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2);
-}
-
-void TileSetEditor::update_edited_region(const Vector2 &end_point) {
- edited_region = Rect2(region_from, Size2());
- if (tools[TOOL_GRID_SNAP]->is_pressed()) {
- Vector2 grid_coord;
- grid_coord = ((region_from - snap_offset) / (snap_step + snap_separation)).floor();
- grid_coord *= (snap_step + snap_separation);
- grid_coord += snap_offset;
- edited_region.expand_to(grid_coord);
- grid_coord += snap_step;
- edited_region.expand_to(grid_coord);
-
- grid_coord = ((end_point - snap_offset) / (snap_step + snap_separation)).floor();
- grid_coord *= (snap_step + snap_separation);
- grid_coord += snap_offset;
- edited_region.expand_to(grid_coord);
- grid_coord += snap_step;
- edited_region.expand_to(grid_coord);
- } else {
- edited_region.expand_to(end_point);
- }
-}
-
-int TileSetEditor::get_current_tile() const {
- return current_tile;
-}
-
-void TileSetEditor::set_current_tile(int p_id) {
- if (current_tile != p_id) {
- current_tile = p_id;
- helper->notify_property_list_changed();
- select_coord(Vector2(0, 0));
- update_workspace_tile_mode();
- if (p_id == -1) {
- editor->get_inspector()->edit(tileset.ptr());
- } else {
- editor->get_inspector()->edit(helper);
- }
- }
-}
-
-Ref<Texture2D> TileSetEditor::get_current_texture() {
- if (texture_list->get_selected_items().size() == 0) {
- return Ref<Texture2D>();
- } else {
- return texture_map[texture_list->get_item_metadata(texture_list->get_selected_items()[0])];
- }
-}
-
-void TilesetEditorContext::set_tileset(const Ref<TileSet> &p_tileset) {
- tileset = p_tileset;
-}
-
-void TilesetEditorContext::set_snap_options_visible(bool p_visible) {
- snap_options_visible = p_visible;
- notify_property_list_changed();
-}
-
-bool TilesetEditorContext::_set(const StringName &p_name, const Variant &p_value) {
- String name = p_name.operator String();
-
- if (name == "options_offset") {
- Vector2 snap = p_value;
- tileset_editor->_set_snap_off(snap + WORKSPACE_MARGIN);
- return true;
- } else if (name == "options_step") {
- Vector2 snap = p_value;
- tileset_editor->_set_snap_step(snap);
- return true;
- } else if (name == "options_separation") {
- Vector2 snap = p_value;
- tileset_editor->_set_snap_sep(snap);
- return true;
- } else if (p_name.operator String().left(5) == "tile_") {
- String name2 = p_name.operator String().right(5);
- bool v = false;
-
- if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) {
- return false;
- }
-
- if (name2 == "autotile_bitmask_mode") {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", p_value, &v);
- } else if (name2 == "subtile_size") {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", p_value, &v);
- } else if (name2 == "subtile_spacing") {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", p_value, &v);
- } else {
- tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/" + name2, p_value, &v);
- }
- if (v) {
- tileset->notify_property_list_changed();
- tileset_editor->workspace->update();
- tileset_editor->workspace_overlay->update();
- }
- return v;
- } else if (name == "tileset_script") {
- tileset->set_script(p_value);
- return true;
- } else if (name == "selected_collision_one_way") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- tileset->tile_set_shape_one_way(tileset_editor->get_current_tile(), index, p_value);
- return true;
- }
- }
- return false;
- } else if (name == "selected_collision_one_way_margin") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- tileset->tile_set_shape_one_way_margin(tileset_editor->get_current_tile(), index, p_value);
- return true;
- }
- }
- return false;
- }
-
- tileset_editor->err_dialog->set_text(TTR("This property can't be changed."));
- tileset_editor->err_dialog->popup_centered(Size2(300, 60));
- return false;
-}
-
-bool TilesetEditorContext::_get(const StringName &p_name, Variant &r_ret) const {
- String name = p_name.operator String();
- bool v = false;
-
- if (name == "options_offset") {
- r_ret = tileset_editor->snap_offset - WORKSPACE_MARGIN;
- v = true;
- } else if (name == "options_step") {
- r_ret = tileset_editor->snap_step;
- v = true;
- } else if (name == "options_separation") {
- r_ret = tileset_editor->snap_separation;
- v = true;
- } else if (name.left(5) == "tile_") {
- name = name.right(5);
-
- if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) {
- return false;
- }
- if (!tileset->has_tile(tileset_editor->get_current_tile())) {
- return false;
- }
-
- if (name == "autotile_bitmask_mode") {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", &v);
- } else if (name == "subtile_size") {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", &v);
- } else if (name == "subtile_spacing") {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", &v);
- } else {
- r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/" + name, &v);
- }
- return v;
- } else if (name == "selected_collision") {
- r_ret = tileset_editor->edited_collision_shape;
- v = true;
- } else if (name == "selected_collision_one_way") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- r_ret = sd[index].one_way_collision;
- v = true;
- break;
- }
- }
- } else if (name == "selected_collision_one_way_margin") {
- Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile());
- for (int index = 0; index < sd.size(); index++) {
- if (sd[index].shape == tileset_editor->edited_collision_shape) {
- r_ret = sd[index].one_way_collision_margin;
- v = true;
- break;
- }
- }
- } else if (name == "selected_navigation") {
- r_ret = tileset_editor->edited_navigation_shape;
- v = true;
- } else if (name == "selected_occlusion") {
- r_ret = tileset_editor->edited_occlusion_shape;
- v = true;
- } else if (name == "tileset_script") {
- r_ret = tileset->get_script();
- v = true;
- }
- return v;
-}
-
-void TilesetEditorContext::_get_property_list(List<PropertyInfo> *p_list) const {
- if (snap_options_visible) {
- p_list->push_back(PropertyInfo(Variant::NIL, "Snap Options", PROPERTY_HINT_NONE, "options_", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_offset"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_step"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_separation"));
- }
- if (tileset_editor->get_current_tile() >= 0 && !tileset.is_null()) {
- int id = tileset_editor->get_current_tile();
- p_list->push_back(PropertyInfo(Variant::NIL, "Selected Tile", PROPERTY_HINT_NONE, "tile_", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::STRING, "tile_name"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_tex_offset"));
- p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial"));
- p_list->push_back(PropertyInfo(Variant::COLOR, "tile_modulate"));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE,ATLAS_TILE"));
- if (tileset->tile_get_tile_mode(id) == TileSet::AUTO_TILE) {
- p_list->push_back(PropertyInfo(Variant::INT, "tile_autotile_bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size"));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 256, 1"));
- } else if (tileset->tile_get_tile_mode(id) == TileSet::ATLAS_TILE) {
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size"));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 256, 1"));
- }
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_occluder_offset"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_navigation_offset"));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
- p_list->push_back(PropertyInfo(Variant::INT, "tile_z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1"));
- }
- if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_COLLISION && tileset_editor->edited_collision_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_collision", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_collision_shape->get_class()));
- if (tileset_editor->edited_collision_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::BOOL, "selected_collision_one_way", PROPERTY_HINT_NONE));
- p_list->push_back(PropertyInfo(Variant::FLOAT, "selected_collision_one_way_margin", PROPERTY_HINT_NONE));
- }
- }
- if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_NAVIGATION && tileset_editor->edited_navigation_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_navigation", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_navigation_shape->get_class()));
- }
- if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_OCCLUSION && tileset_editor->edited_occlusion_shape.is_valid()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_occlusion", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_occlusion_shape->get_class()));
- }
- if (!tileset.is_null()) {
- p_list->push_back(PropertyInfo(Variant::OBJECT, "tileset_script", PROPERTY_HINT_RESOURCE_TYPE, "Script"));
- }
-}
-
-void TilesetEditorContext::_bind_methods() {
- ClassDB::bind_method("_hide_script_from_inspector", &TilesetEditorContext::_hide_script_from_inspector);
-}
-
-TilesetEditorContext::TilesetEditorContext(TileSetEditor *p_tileset_editor) {
- tileset_editor = p_tileset_editor;
- snap_options_visible = false;
-}
-
-void TileSetEditorPlugin::edit(Object *p_node) {
- if (Object::cast_to<TileSet>(p_node)) {
- tileset_editor->edit(Object::cast_to<TileSet>(p_node));
- }
-}
-
-bool TileSetEditorPlugin::handles(Object *p_node) const {
- return p_node->is_class("TileSet") || p_node->is_class("TilesetEditorContext");
-}
-
-void TileSetEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- tileset_editor_button->show();
- editor->make_bottom_panel_item_visible(tileset_editor);
- get_tree()->connect("idle_frame", Callable(tileset_editor, "_on_workspace_process"));
- } else {
- editor->hide_bottom_panel();
- tileset_editor_button->hide();
- get_tree()->disconnect("idle_frame", Callable(tileset_editor, "_on_workspace_process"));
- }
-}
-
-Dictionary TileSetEditorPlugin::get_state() const {
- Dictionary state;
- state["snap_offset"] = tileset_editor->snap_offset;
- state["snap_step"] = tileset_editor->snap_step;
- state["snap_separation"] = tileset_editor->snap_separation;
- state["snap_enabled"] = tileset_editor->tools[TileSetEditor::TOOL_GRID_SNAP]->is_pressed();
- state["keep_inside_tile"] = tileset_editor->tools[TileSetEditor::SHAPE_KEEP_INSIDE_TILE]->is_pressed();
- state["show_information"] = tileset_editor->tools[TileSetEditor::VISIBLE_INFO]->is_pressed();
- return state;
-}
-
-void TileSetEditorPlugin::set_state(const Dictionary &p_state) {
- Dictionary state = p_state;
- if (state.has("snap_step")) {
- tileset_editor->_set_snap_step(state["snap_step"]);
- }
-
- if (state.has("snap_offset")) {
- tileset_editor->_set_snap_off(state["snap_offset"]);
- }
-
- if (state.has("snap_separation")) {
- tileset_editor->_set_snap_sep(state["snap_separation"]);
- }
-
- if (state.has("snap_enabled")) {
- tileset_editor->tools[TileSetEditor::TOOL_GRID_SNAP]->set_pressed(state["snap_enabled"]);
- if (tileset_editor->helper) {
- tileset_editor->_on_grid_snap_toggled(state["snap_enabled"]);
- }
- }
-
- if (state.has("keep_inside_tile")) {
- tileset_editor->tools[TileSetEditor::SHAPE_KEEP_INSIDE_TILE]->set_pressed(state["keep_inside_tile"]);
- }
-
- if (state.has("show_information")) {
- tileset_editor->tools[TileSetEditor::VISIBLE_INFO]->set_pressed(state["show_information"]);
- }
-}
-
-TileSetEditorPlugin::TileSetEditorPlugin(EditorNode *p_node) {
- editor = p_node;
- tileset_editor = memnew(TileSetEditor(p_node));
-
- tileset_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
- tileset_editor->hide();
-
- tileset_editor_button = p_node->add_bottom_panel_item(TTR("TileSet"), tileset_editor);
- tileset_editor_button->hide();
-}
diff --git a/editor/plugins/tile_set_editor_plugin.h b/editor/plugins/tile_set_editor_plugin.h
deleted file mode 100644
index e778c18f44..0000000000
--- a/editor/plugins/tile_set_editor_plugin.h
+++ /dev/null
@@ -1,298 +0,0 @@
-/*************************************************************************/
-/* tile_set_editor_plugin.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 TILE_SET_EDITOR_PLUGIN_H
-#define TILE_SET_EDITOR_PLUGIN_H
-
-#include "editor/editor_node.h"
-#include "scene/2d/sprite_2d.h"
-#include "scene/resources/concave_polygon_shape_2d.h"
-#include "scene/resources/convex_polygon_shape_2d.h"
-#include "scene/resources/tile_set.h"
-
-#define WORKSPACE_MARGIN Vector2(10, 10)
-class TilesetEditorContext;
-
-class TileSetEditor : public HSplitContainer {
- friend class TileSetEditorPlugin;
- friend class TilesetEditorContext;
-
- GDCLASS(TileSetEditor, HSplitContainer);
-
- enum TextureButtons {
- TOOL_TILESET_ADD_TEXTURE,
- TOOL_TILESET_REMOVE_TEXTURE,
- TOOL_TILESET_CREATE_SCENE,
- TOOL_TILESET_MERGE_SCENE,
- TOOL_TILESET_MAX
- };
-
- enum WorkspaceMode {
- WORKSPACE_EDIT,
- WORKSPACE_CREATE_SINGLE,
- WORKSPACE_CREATE_AUTOTILE,
- WORKSPACE_CREATE_ATLAS,
- WORKSPACE_MODE_MAX
- };
-
- enum EditMode {
- EDITMODE_REGION,
- EDITMODE_COLLISION,
- EDITMODE_OCCLUSION,
- EDITMODE_NAVIGATION,
- EDITMODE_BITMASK,
- EDITMODE_PRIORITY,
- EDITMODE_ICON,
- EDITMODE_Z_INDEX,
- EDITMODE_MAX
- };
-
- enum TileSetTools {
- SELECT_PREVIOUS,
- SELECT_NEXT,
- TOOL_SELECT,
- BITMASK_COPY,
- BITMASK_PASTE,
- BITMASK_CLEAR,
- SHAPE_NEW_POLYGON,
- SHAPE_NEW_RECTANGLE,
- SHAPE_TOGGLE_TYPE,
- SHAPE_DELETE,
- SHAPE_KEEP_INSIDE_TILE,
- TOOL_GRID_SNAP,
- ZOOM_OUT,
- ZOOM_1,
- ZOOM_IN,
- VISIBLE_INFO,
- TOOL_MAX
- };
-
- struct SubtileData {
- Array collisions;
- Ref<OccluderPolygon2D> occlusion_shape;
- Ref<NavigationPolygon> navigation_shape;
- };
-
- Ref<TileSet> tileset;
- TilesetEditorContext *helper;
- EditorNode *editor;
- UndoRedo *undo_redo;
-
- ConfirmationDialog *cd;
- AcceptDialog *err_dialog;
- EditorFileDialog *texture_dialog;
-
- ItemList *texture_list;
- int option;
- Button *tileset_toolbar_buttons[TOOL_TILESET_MAX];
- MenuButton *tileset_toolbar_tools;
- Map<RID, Ref<Texture2D>> texture_map;
-
- bool creating_shape;
- int dragging_point;
- bool tile_names_visible;
- Vector2 region_from;
- Rect2 edited_region;
- bool draw_edited_region;
- Vector2 edited_shape_coord;
- PackedVector2Array current_shape;
- Map<Vector2i, SubtileData> current_tile_data;
- Map<Vector2, uint32_t> bitmask_map_copy;
-
- Vector2 snap_step;
- Vector2 snap_offset;
- Vector2 snap_separation;
-
- Ref<Shape2D> edited_collision_shape;
- Ref<OccluderPolygon2D> edited_occlusion_shape;
- Ref<NavigationPolygon> edited_navigation_shape;
-
- int current_item_index;
- Sprite2D *preview;
- ScrollContainer *scroll;
- Label *empty_message;
- Control *workspace_container;
- bool draw_handles;
- Control *workspace_overlay;
- Control *workspace;
- Button *tool_workspacemode[WORKSPACE_MODE_MAX];
- Button *tool_editmode[EDITMODE_MAX];
- HSeparator *separator_editmode;
- HBoxContainer *toolbar;
- Button *tools[TOOL_MAX];
- VSeparator *separator_shape_toggle;
- VSeparator *separator_bitmask;
- VSeparator *separator_delete;
- VSeparator *separator_grid;
- SpinBox *spin_priority;
- SpinBox *spin_z_index;
- WorkspaceMode workspace_mode;
- EditMode edit_mode;
- int current_tile;
-
- float max_scale;
- float min_scale;
- float scale_ratio;
-
- void update_texture_list();
- void update_texture_list_icon();
-
- void add_texture(Ref<Texture2D> p_texture);
- void remove_texture(Ref<Texture2D> p_texture);
-
- Ref<Texture2D> get_current_texture();
-
- static void _import_node(Node *p_node, Ref<TileSet> p_library);
- static void _import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge);
- void _undo_redo_import_scene(Node *p_scene, bool p_merge);
-
- bool _is_drop_valid(const Dictionary &p_drag_data, const Dictionary &p_item_data) const;
- Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
- bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
- void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
- void _file_load_request(const Vector<String> &p_path, int p_at_pos = -1);
-
-protected:
- static void _bind_methods();
- void _notification(int p_what);
-
-public:
- void edit(const Ref<TileSet> &p_tileset);
- static Error update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge = true);
-
- TileSetEditor(EditorNode *p_editor);
- ~TileSetEditor();
-
-private:
- void _on_tileset_toolbar_button_pressed(int p_index);
- void _on_tileset_toolbar_confirm();
- void _on_texture_list_selected(int p_index);
- void _on_textures_added(const PackedStringArray &p_paths);
- void _on_edit_mode_changed(int p_edit_mode);
- void _on_workspace_mode_changed(int p_workspace_mode);
- void _on_workspace_overlay_draw();
- void _on_workspace_draw();
- void _on_workspace_process();
- void _on_scroll_container_input(const Ref<InputEvent> &p_event);
- void _on_workspace_input(const Ref<InputEvent> &p_ie);
- void _on_tool_clicked(int p_tool);
- void _on_priority_changed(float val);
- void _on_z_index_changed(float val);
- void _on_grid_snap_toggled(bool p_val);
- Vector<Vector2> _get_collision_shape_points(const Ref<Shape2D> &p_shape);
- Vector<Vector2> _get_edited_shape_points();
- void _set_edited_shape_points(const Vector<Vector2> &points);
- void _update_tile_data();
- void _update_toggle_shape_button();
- void _select_next_tile();
- void _select_previous_tile();
- Array _get_tiles_in_current_texture(bool sorted = false);
- bool _sort_tiles(Variant p_a, Variant p_b);
- void _select_next_subtile();
- void _select_previous_subtile();
- void _select_next_shape();
- void _select_previous_shape();
- void _set_edited_collision_shape(const Ref<Shape2D> &p_shape);
- void _set_snap_step(Vector2 p_val);
- void _set_snap_off(Vector2 p_val);
- void _set_snap_sep(Vector2 p_val);
-
- void _validate_current_tile_id();
- void _select_edited_shape_coord();
- void _undo_tile_removal(int p_id);
-
- void _zoom_in();
- void _zoom_out();
- void _zoom_reset();
-
- void draw_highlight_current_tile();
- void draw_highlight_subtile(Vector2 coord, const Vector<Vector2> &other_highlighted = Vector<Vector2>());
- void draw_tile_subdivision(int p_id, Color p_color) const;
- void draw_edited_region_subdivision() const;
- void draw_grid_snap();
- void draw_polygon_shapes();
- void close_shape(const Vector2 &shape_anchor);
- void select_coord(const Vector2 &coord);
- Vector2 snap_point(const Vector2 &point);
- void update_workspace_tile_mode();
- void update_workspace_minsize();
- void update_edited_region(const Vector2 &end_point);
- int get_grabbed_point(const Vector2 &p_mouse_pos, real_t grab_threshold);
- bool is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold);
-
- int get_current_tile() const;
- void set_current_tile(int p_id);
-};
-
-class TilesetEditorContext : public Object {
- friend class TileSetEditor;
- GDCLASS(TilesetEditorContext, Object);
-
- Ref<TileSet> tileset;
- TileSetEditor *tileset_editor;
- bool snap_options_visible;
-
-public:
- bool _hide_script_from_inspector() { return true; }
- void set_tileset(const Ref<TileSet> &p_tileset);
-
-private:
- void set_snap_options_visible(bool p_visible);
-
-protected:
- bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
- static void _bind_methods();
-
-public:
- TilesetEditorContext(TileSetEditor *p_tileset_editor);
-};
-
-class TileSetEditorPlugin : public EditorPlugin {
- GDCLASS(TileSetEditorPlugin, EditorPlugin);
-
- TileSetEditor *tileset_editor;
- Button *tileset_editor_button;
- EditorNode *editor;
-
-public:
- virtual String get_name() const override { return "TileSet"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_node) override;
- virtual bool handles(Object *p_node) const override;
- virtual void make_visible(bool p_visible) override;
- void set_state(const Dictionary &p_state) override;
- Dictionary get_state() const override;
-
- TileSetEditorPlugin(EditorNode *p_node);
-};
-
-#endif // TILE_SET_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/SCsub b/editor/plugins/tiles/SCsub
new file mode 100644
index 0000000000..359d04e5df
--- /dev/null
+++ b/editor/plugins/tiles/SCsub
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env.add_source_files(env.editor_sources, "*.cpp")
diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp
new file mode 100644
index 0000000000..374a255df3
--- /dev/null
+++ b/editor/plugins/tiles/tile_atlas_view.cpp
@@ -0,0 +1,680 @@
+/*************************************************************************/
+/* tile_atlas_view.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 "tile_atlas_view.h"
+
+#include "core/input/input.h"
+#include "core/os/keyboard.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/label.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/texture_rect.h"
+
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+
+void TileAtlasView::_gui_input(const Ref<InputEvent> &p_event) {
+ bool ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL);
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ drag_type = DRAG_TYPE_NONE;
+ if (ctrl && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {
+ // Zoom out
+ zoom_widget->set_zoom_by_increments(-2);
+ emit_signal("transform_changed", zoom_widget->get_zoom(), panning);
+ _update_zoom_and_panning(true);
+ accept_event();
+ }
+
+ if (ctrl && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {
+ // Zoom in
+ zoom_widget->set_zoom_by_increments(2);
+ emit_signal("transform_changed", zoom_widget->get_zoom(), panning);
+ _update_zoom_and_panning(true);
+ accept_event();
+ }
+
+ if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE || mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->is_pressed()) {
+ drag_type = DRAG_TYPE_PAN;
+ } else {
+ drag_type = DRAG_TYPE_NONE;
+ }
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ if (drag_type == DRAG_TYPE_PAN) {
+ panning += mm->get_relative();
+ _update_zoom_and_panning();
+ emit_signal("transform_changed", zoom_widget->get_zoom(), panning);
+ accept_event();
+ }
+ }
+}
+
+Size2i TileAtlasView::_compute_base_tiles_control_size() {
+ // Update the texture.
+ Vector2i size;
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ size = texture->get_size();
+ }
+
+ // Extend the size to all existing tiles.
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ grid_size = grid_size.max(tile_id + Vector2i(1, 1));
+ }
+ size = size.max(grid_size * (tile_set_atlas_source->get_texture_region_size() + tile_set_atlas_source->get_separation()) + tile_set_atlas_source->get_margins());
+
+ return size;
+}
+
+Size2i TileAtlasView::_compute_alternative_tiles_control_size() {
+ Vector2i size;
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
+ Vector2i line_size;
+ Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
+ for (int j = 1; j < alternatives_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
+ bool transposed = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id))->get_transpose();
+ line_size.x += transposed ? texture_region_size.y : texture_region_size.x;
+ line_size.y = MAX(line_size.y, transposed ? texture_region_size.x : texture_region_size.y);
+ }
+ size.x = MAX(size.x, line_size.x);
+ size.y += line_size.y;
+ }
+
+ return size;
+}
+
+void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) {
+ float zoom = zoom_widget->get_zoom();
+
+ // Compute the minimum sizes.
+ Size2i base_tiles_control_size = _compute_base_tiles_control_size();
+ base_tiles_root_control->set_custom_minimum_size(Vector2(base_tiles_control_size) * zoom);
+
+ Size2i alternative_tiles_control_size = _compute_alternative_tiles_control_size();
+ alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * zoom);
+
+ // Set the texture for the base tiles.
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+
+ // Set the scales.
+ if (base_tiles_control_size.x > 0 && base_tiles_control_size.y > 0) {
+ base_tiles_drawing_root->set_scale(Vector2(zoom, zoom));
+ } else {
+ base_tiles_drawing_root->set_scale(Vector2(1, 1));
+ }
+ if (alternative_tiles_control_size.x > 0 && alternative_tiles_control_size.y > 0) {
+ alternative_tiles_drawing_root->set_scale(Vector2(zoom, zoom));
+ } else {
+ alternative_tiles_drawing_root->set_scale(Vector2(1, 1));
+ }
+
+ // Update the margin container's margins.
+ const char *constants[] = { "margin_left", "margin_top", "margin_right", "margin_bottom" };
+ for (int i = 0; i < 4; i++) {
+ margin_container->add_theme_constant_override(constants[i], margin_container_paddings[i] * zoom);
+ }
+
+ // Update the backgrounds.
+ background_left->update();
+ background_right->update();
+
+ // Zoom on the position.
+ if (p_zoom_on_mouse_pos) {
+ // Offset the panning relative to the center of panel.
+ Vector2 relative_mpos = get_local_mouse_position() - get_size() / 2;
+ panning = (panning - relative_mpos) * zoom / previous_zoom + relative_mpos;
+ } else {
+ // Center of panel.
+ panning = panning * zoom / previous_zoom;
+ }
+ button_center_view->set_disabled(panning.is_equal_approx(Vector2()));
+
+ previous_zoom = zoom;
+
+ center_container->set_begin(panning - center_container->get_minimum_size() / 2);
+ center_container->set_size(center_container->get_minimum_size());
+}
+
+void TileAtlasView::_zoom_widget_changed() {
+ _update_zoom_and_panning();
+ emit_signal("transform_changed", zoom_widget->get_zoom(), panning);
+}
+
+void TileAtlasView::_center_view() {
+ panning = Vector2();
+ button_center_view->set_disabled(true);
+ _update_zoom_and_panning();
+ emit_signal("transform_changed", zoom_widget->get_zoom(), panning);
+}
+
+void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
+ base_tiles_root_control->set_tooltip("");
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ Transform2D xform = base_tiles_drawing_root->get_transform().affine_inverse();
+ Vector2i coords = get_atlas_tile_coords_at_pos(xform.xform(mm->get_position()));
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ base_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords));
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles() {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ // Draw the texture, square by square.
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+ for (int x = 0; x < grid_size.x; x++) {
+ for (int y = 0; y < grid_size.y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
+ Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size);
+ base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+ }
+ }
+ }
+
+ // Draw the texture around the grid.
+ Rect2i rect;
+ // Top.
+ rect.position = Vector2i();
+ rect.set_end(Vector2i(texture->get_size().x, margins.y));
+ base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+ // Bottom
+ int bottom_border = margins.y + (grid_size.y * texture_region_size.y);
+ if (bottom_border < texture->get_size().y) {
+ rect.position = Vector2i(0, bottom_border);
+ rect.set_end(texture->get_size());
+ base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+ }
+ // Left
+ rect.position = Vector2i(0, margins.y);
+ rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y)));
+ base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+ // Right.
+ int right_border = margins.x + (grid_size.x * texture_region_size.x);
+ if (right_border < texture->get_size().x) {
+ rect.position = Vector2i(right_border, margins.y);
+ rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y)));
+ base_tiles_draw->draw_texture_rect_region(texture, rect, rect);
+ }
+
+ // Draw actual tiles, using their properties (modulation, etc...)
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
+
+ // Update the y to max value.
+ Vector2i offset_pos = (margins + (atlas_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0));
+
+ // Draw the tile.
+ TileSetPluginAtlasRendering::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0);
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles_texture_grid() {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ // Draw each tile texture region.
+ for (int x = 0; x < grid_size.x; x++) {
+ for (int y = 0; y < grid_size.y; y++) {
+ Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation));
+ Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
+ if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ if (base_tile_coords == Vector2i(x, y)) {
+ // Draw existing tile.
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(base_tile_coords);
+ Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1));
+ base_tiles_texture_grid->draw_rect(Rect2i(origin, region_size), Color(1.0, 1.0, 1.0, 0.8), false);
+ }
+ } else {
+ // Draw the grid.
+ base_tiles_texture_grid->draw_rect(Rect2i(origin, texture_region_size), Color(0.7, 0.7, 0.7, 0.1), false);
+ }
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles_dark() {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ // Draw each tile texture region.
+ for (int x = 0; x < grid_size.x; x++) {
+ for (int y = 0; y < grid_size.y; y++) {
+ Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation));
+ Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
+
+ if (base_tile_coords == TileSetSource::INVALID_ATLAS_COORDS) {
+ // Draw the grid.
+ base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true);
+ }
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_base_tiles_shape_grid() {
+ // Draw the shapes.
+ Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color");
+ Vector2i tile_shape_size = tile_set->get_tile_size();
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ 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);
+ }
+}
+
+void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) {
+ alternative_tiles_root_control->set_tooltip("");
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ Transform2D xform = alternative_tiles_drawing_root->get_transform().affine_inverse();
+ Vector3i coords3 = get_alternative_tile_at_pos(xform.xform(mm->get_position()));
+ Vector2i coords = Vector2i(coords3.x, coords3.y);
+ int alternative_id = coords3.z;
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) {
+ alternative_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id));
+ }
+ }
+}
+
+void TileAtlasView::_draw_alternatives() {
+ // Draw the alternative tiles.
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2 current_pos;
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
+ current_pos.x = 0;
+ int y_increment = 0;
+ Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(atlas_coords);
+ int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords);
+ for (int j = 1; j < alternatives_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j);
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id));
+ bool transposed = tile_data->get_transpose();
+
+ // Update the y to max value.
+ Vector2i offset_pos = current_pos;
+ if (transposed) {
+ offset_pos = (current_pos + Vector2(texture_region.size.y, texture_region.size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
+ y_increment = MAX(y_increment, texture_region.size.x);
+ } else {
+ offset_pos = (current_pos + texture_region.size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id));
+ y_increment = MAX(y_increment, texture_region.size.y);
+ }
+
+ // Draw the tile.
+ TileSetPluginAtlasRendering::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
+
+ // Increment the x position.
+ current_pos.x += transposed ? texture_region.size.y : texture_region.size.x;
+ }
+ if (alternatives_count > 1) {
+ current_pos.y += y_increment;
+ }
+ }
+ }
+}
+
+void TileAtlasView::_draw_background_left() {
+ Ref<Texture2D> texture = get_theme_icon("Checkerboard", "EditorIcons");
+ background_left->set_size(base_tiles_root_control->get_custom_minimum_size());
+ background_left->draw_texture_rect(texture, Rect2(Vector2(), background_left->get_size()), true);
+}
+
+void TileAtlasView::_draw_background_right() {
+ Ref<Texture2D> texture = get_theme_icon("Checkerboard", "EditorIcons");
+ background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size());
+ background_right->draw_texture_rect(texture, Rect2(Vector2(), background_right->get_size()), true);
+}
+
+void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set);
+ ERR_FAIL_COND(!p_tile_set_atlas_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
+
+ tile_set = p_tile_set;
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ source_id = p_source_id;
+
+ // Show or hide the view.
+ bool valid = tile_set_atlas_source->get_texture().is_valid();
+ hbox->set_visible(valid);
+ missing_source_label->set_visible(!valid);
+
+ // Update the rect cache.
+ _update_alternative_tiles_rect_cache();
+
+ // Update everything.
+ _update_zoom_and_panning();
+
+ // Change children control size.
+ Size2i base_tiles_control_size = _compute_base_tiles_control_size();
+ for (int i = 0; i < base_tiles_drawing_root->get_child_count(); i++) {
+ Control *control = Object::cast_to<Control>(base_tiles_drawing_root->get_child(i));
+ if (control) {
+ control->set_size(base_tiles_control_size);
+ }
+ }
+
+ Size2i alternative_control_size = _compute_alternative_tiles_control_size();
+ for (int i = 0; i < alternative_tiles_drawing_root->get_child_count(); i++) {
+ Control *control = Object::cast_to<Control>(alternative_tiles_drawing_root->get_child(i));
+ if (control) {
+ control->set_size(alternative_control_size);
+ }
+ }
+
+ // Update.
+ base_tiles_draw->update();
+ base_tiles_texture_grid->update();
+ base_tiles_shape_grid->update();
+ base_tiles_dark->update();
+ alternatives_draw->update();
+ background_left->update();
+ background_right->update();
+}
+
+float TileAtlasView::get_zoom() const {
+ return zoom_widget->get_zoom();
+};
+
+void TileAtlasView::set_transform(float p_zoom, Vector2i p_panning) {
+ zoom_widget->set_zoom(p_zoom);
+ panning = p_panning;
+ _update_zoom_and_panning();
+};
+
+void TileAtlasView::set_padding(Side p_side, int p_padding) {
+ ERR_FAIL_COND(p_padding < 0);
+ margin_container_paddings[p_side] = p_padding;
+}
+
+Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos) const {
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+
+ // Compute index in atlas
+ Vector2 pos = p_pos - margins;
+ Vector2i ret = (pos / (texture_region_size + separation)).floor();
+
+ return ret;
+ }
+
+ return TileSetSource::INVALID_ATLAS_COORDS;
+}
+
+void TileAtlasView::_update_alternative_tiles_rect_cache() {
+ alternative_tiles_rect_cache.clear();
+
+ Rect2i current;
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
+ Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size;
+ int line_height = 0;
+ for (int j = 1; j < alternatives_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id));
+ bool transposed = tile_data->get_transpose();
+ current.size = transposed ? Vector2i(texture_region_size.y, texture_region_size.x) : texture_region_size;
+
+ // Update the rect.
+ if (!alternative_tiles_rect_cache.has(tile_id)) {
+ alternative_tiles_rect_cache[tile_id] = Map<int, Rect2i>();
+ }
+ alternative_tiles_rect_cache[tile_id][alternative_id] = current;
+
+ current.position.x += transposed ? texture_region_size.y : texture_region_size.x;
+ line_height = MAX(line_height, transposed ? texture_region_size.x : texture_region_size.y);
+ }
+
+ current.position.x = 0;
+ current.position.y += line_height;
+ }
+}
+
+Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const {
+ for (Map<Vector2, Map<int, Rect2i>>::Element *E_coords = alternative_tiles_rect_cache.front(); E_coords; E_coords = E_coords->next()) {
+ for (Map<int, Rect2i>::Element *E_alternative = E_coords->value().front(); E_alternative; E_alternative = E_alternative->next()) {
+ if (E_alternative->value().has_point(p_pos)) {
+ return Vector3i(E_coords->key().x, E_coords->key().y, E_alternative->key());
+ }
+ }
+ }
+
+ return Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
+}
+
+Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile) {
+ ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache.has(p_coords), Rect2i(), vformat("No cached rect for tile coords:%s", p_coords));
+ ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache[p_coords].has(p_alternative_tile), Rect2i(), vformat("No cached rect for tile coords:%s alternative_id:%d", p_coords, p_alternative_tile));
+
+ return alternative_tiles_rect_cache[p_coords][p_alternative_tile];
+}
+
+void TileAtlasView::update() {
+ base_tiles_draw->update();
+ base_tiles_texture_grid->update();
+ base_tiles_shape_grid->update();
+ base_tiles_dark->update();
+ alternatives_draw->update();
+ background_left->update();
+ background_right->update();
+}
+
+void TileAtlasView::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_READY:
+ button_center_view->set_icon(get_theme_icon("CenterView", "EditorIcons"));
+ break;
+ }
+}
+
+void TileAtlasView::_bind_methods() {
+ ClassDB::bind_method("_gui_input", &TileAtlasView::_gui_input);
+
+ ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll")));
+}
+
+TileAtlasView::TileAtlasView() {
+ set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+
+ Panel *panel = memnew(Panel);
+ panel->set_clip_contents(true);
+ panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ panel->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(panel);
+
+ // Scrollingsc
+ zoom_widget = memnew(EditorZoomWidget);
+ add_child(zoom_widget);
+ zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
+ zoom_widget->connect("zoom_changed", callable_mp(this, &TileAtlasView::_zoom_widget_changed).unbind(1));
+
+ button_center_view = memnew(Button);
+ button_center_view->set_icon(get_theme_icon("CenterView", "EditorIcons"));
+ button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5);
+ button_center_view->connect("pressed", callable_mp(this, &TileAtlasView::_center_view));
+ button_center_view->set_flat(true);
+ button_center_view->set_disabled(true);
+ add_child(button_center_view);
+
+ center_container = memnew(CenterContainer);
+ center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ center_container->set_anchors_preset(Control::PRESET_CENTER);
+ center_container->connect("gui_input", callable_mp(this, &TileAtlasView::_gui_input));
+ panel->add_child(center_container);
+
+ missing_source_label = memnew(Label);
+ missing_source_label->set_text(TTR("No atlas source with a valid texture selected."));
+ center_container->add_child(missing_source_label);
+
+ margin_container = memnew(MarginContainer);
+ margin_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ center_container->add_child(margin_container);
+
+ hbox = memnew(HBoxContainer);
+ hbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ hbox->add_theme_constant_override("separation", 10);
+ hbox->hide();
+ margin_container->add_child(hbox);
+
+ VBoxContainer *left_vbox = memnew(VBoxContainer);
+ left_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ hbox->add_child(left_vbox);
+
+ VBoxContainer *right_vbox = memnew(VBoxContainer);
+ right_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ hbox->add_child(right_vbox);
+
+ // Base tiles.
+ Label *base_tile_label = memnew(Label);
+ base_tile_label->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ base_tile_label->set_text(TTR("Base Tiles"));
+ base_tile_label->set_align(Label::ALIGN_CENTER);
+ left_vbox->add_child(base_tile_label);
+
+ base_tiles_root_control = memnew(Control);
+ base_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ base_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ base_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_base_tiles_root_control_gui_input));
+ left_vbox->add_child(base_tiles_root_control);
+
+ background_left = memnew(Control);
+ background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ background_left->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
+ background_left->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_left));
+ base_tiles_root_control->add_child(background_left);
+
+ base_tiles_drawing_root = memnew(Control);
+ base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_drawing_root->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
+ base_tiles_root_control->add_child(base_tiles_drawing_root);
+
+ base_tiles_draw = memnew(Control);
+ base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles));
+ base_tiles_drawing_root->add_child(base_tiles_draw);
+
+ base_tiles_texture_grid = memnew(Control);
+ base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_texture_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_texture_grid));
+ base_tiles_drawing_root->add_child(base_tiles_texture_grid);
+
+ base_tiles_shape_grid = memnew(Control);
+ base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid));
+ base_tiles_drawing_root->add_child(base_tiles_shape_grid);
+
+ base_tiles_dark = memnew(Control);
+ base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark));
+ base_tiles_drawing_root->add_child(base_tiles_dark);
+
+ // Alternative tiles.
+ Label *alternative_tiles_label = memnew(Label);
+ alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ alternative_tiles_label->set_text(TTR("Alternative Tiles"));
+ alternative_tiles_label->set_align(Label::ALIGN_CENTER);
+ right_vbox->add_child(alternative_tiles_label);
+
+ alternative_tiles_root_control = memnew(Control);
+ alternative_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ alternative_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_alternative_tiles_root_control_gui_input));
+ right_vbox->add_child(alternative_tiles_root_control);
+
+ background_right = memnew(Control);
+ background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ background_right->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED);
+ background_right->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_right));
+
+ alternative_tiles_root_control->add_child(background_right);
+
+ alternative_tiles_drawing_root = memnew(Control);
+ alternative_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST);
+ alternative_tiles_root_control->add_child(alternative_tiles_drawing_root);
+
+ alternatives_draw = memnew(Control);
+ alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives));
+ alternative_tiles_drawing_root->add_child(alternatives_draw);
+}
diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h
new file mode 100644
index 0000000000..bafc2b3985
--- /dev/null
+++ b/editor/plugins/tiles/tile_atlas_view.h
@@ -0,0 +1,160 @@
+/*************************************************************************/
+/* tile_atlas_view.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 TILE_ATLAS_VIEW_H
+#define TILE_ATLAS_VIEW_H
+
+#include "editor/editor_zoom_widget.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/center_container.h"
+#include "scene/gui/label.h"
+#include "scene/gui/margin_container.h"
+#include "scene/gui/scroll_container.h"
+#include "scene/gui/texture_rect.h"
+#include "scene/resources/tile_set.h"
+
+class TileAtlasView : public Control {
+ GDCLASS(TileAtlasView, Control);
+
+private:
+ TileSet *tile_set;
+ TileSetAtlasSource *tile_set_atlas_source;
+ int source_id = -1;
+
+ enum DragType {
+ DRAG_TYPE_NONE,
+ DRAG_TYPE_PAN,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ float previous_zoom = 1.0;
+ EditorZoomWidget *zoom_widget;
+ Button *button_center_view;
+ CenterContainer *center_container;
+ Vector2 panning;
+ void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false);
+ void _zoom_widget_changed();
+ void _center_view();
+ void _gui_input(const Ref<InputEvent> &p_event);
+
+ Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache;
+ void _update_alternative_tiles_rect_cache();
+
+ MarginContainer *margin_container;
+ int margin_container_paddings[4] = { 0, 0, 0, 0 };
+ HBoxContainer *hbox;
+ Label *missing_source_label;
+
+ // Background
+ Control *background_left;
+ void _draw_background_left();
+ Control *background_right;
+ void _draw_background_right();
+
+ // Left side.
+ Control *base_tiles_root_control;
+ void _base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
+
+ Control *base_tiles_drawing_root;
+
+ Control *base_tiles_draw;
+ void _draw_base_tiles();
+
+ Control *base_tiles_texture_grid;
+ void _draw_base_tiles_texture_grid();
+
+ Control *base_tiles_shape_grid;
+ void _draw_base_tiles_shape_grid();
+
+ Control *base_tiles_dark;
+ void _draw_base_tiles_dark();
+
+ Size2i _compute_base_tiles_control_size();
+
+ // Right side.
+ Control *alternative_tiles_root_control;
+ void _alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event);
+
+ Control *alternative_tiles_drawing_root;
+
+ Control *alternatives_draw;
+ void _draw_alternatives();
+
+ Size2i _compute_alternative_tiles_control_size();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ // Global.
+ void set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id);
+
+ float get_zoom() const;
+ void set_transform(float p_zoom, Vector2i p_panning);
+
+ void set_padding(Side p_side, int p_padding);
+
+ // Left side.
+ void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); };
+ void set_dark_visible(bool p_visible) { base_tiles_dark->set_visible(p_visible); };
+ void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); };
+
+ Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const;
+
+ void add_control_over_atlas_tiles(Control *p_control, bool scaled = true) {
+ if (scaled) {
+ base_tiles_drawing_root->add_child(p_control);
+ } else {
+ base_tiles_root_control->add_child(p_control);
+ }
+ p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ };
+
+ // Right side.
+ Vector3i get_alternative_tile_at_pos(const Vector2 p_pos) const;
+ Rect2i get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile);
+
+ void add_control_over_alternative_tiles(Control *p_control, bool scaled = true) {
+ if (scaled) {
+ alternative_tiles_drawing_root->add_child(p_control);
+ } else {
+ alternative_tiles_root_control->add_child(p_control);
+ }
+ p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ };
+
+ // Update everything.
+ void update();
+
+ TileAtlasView();
+};
+
+#endif // TILE_ATLAS_VIEW
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
new file mode 100644
index 0000000000..d9d0e48fb3
--- /dev/null
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -0,0 +1,2467 @@
+/*************************************************************************/
+/* tile_data_editors.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 "tile_data_editors.h"
+
+#include "tile_set_editor.h"
+
+#include "core/math/geometry_2d.h"
+#include "core/os/keyboard.h"
+
+#include "editor/editor_properties.h"
+#include "editor/editor_scale.h"
+
+void TileDataEditor::_call_tile_set_changed() {
+ _tile_set_changed();
+}
+
+TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) {
+ ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr);
+ ERR_FAIL_COND_V(!tile_set->has_source(p_cell.source_id), nullptr);
+
+ TileData *td = nullptr;
+ TileSetSource *source = *tile_set->get_source(p_cell.source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ ERR_FAIL_COND_V(!atlas_source->has_tile(p_cell.get_atlas_coords()), nullptr);
+ ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_cell.get_atlas_coords(), p_cell.alternative_tile), nullptr);
+ td = Object::cast_to<TileData>(atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile));
+ }
+
+ return td;
+}
+
+void TileDataEditor::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("needs_redraw"));
+}
+
+void TileDataEditor::set_tile_set(Ref<TileSet> p_tile_set) {
+ if (tile_set.is_valid()) {
+ tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed));
+ }
+ tile_set = p_tile_set;
+ if (tile_set.is_valid()) {
+ tile_set->connect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed));
+ }
+ _call_tile_set_changed();
+}
+
+bool DummyObject::_set(const StringName &p_name, const Variant &p_value) {
+ if (properties.has(p_name)) {
+ properties[p_name] = p_value;
+ return true;
+ }
+ return false;
+}
+
+bool DummyObject::_get(const StringName &p_name, Variant &r_ret) const {
+ if (properties.has(p_name)) {
+ r_ret = properties[p_name];
+ return true;
+ }
+ return false;
+}
+
+bool DummyObject::has_dummy_property(StringName p_name) {
+ return properties.has(p_name);
+}
+
+void DummyObject::add_dummy_property(StringName p_name) {
+ ERR_FAIL_COND(properties.has(p_name));
+ properties[p_name] = Variant();
+}
+
+void DummyObject::remove_dummy_property(StringName p_name) {
+ ERR_FAIL_COND(!properties.has(p_name));
+ properties.erase(p_name);
+}
+
+void DummyObject::clear_dummy_properties() {
+ properties.clear();
+}
+
+void GenericTilePolygonEditor::_base_control_draw() {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
+
+ Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color");
+ const Ref<Texture2D> handle = get_theme_icon("EditorPathSharpHandle", "EditorIcons");
+ const Ref<Texture2D> add_handle = get_theme_icon("EditorHandleAdd", "EditorIcons");
+
+ Size2 tile_size = tile_set->get_tile_size();
+
+ Transform2D xform;
+ xform.set_origin(base_control->get_size() / 2 + panning);
+ xform.set_scale(Vector2(editor_zoom_widget->get_zoom(), editor_zoom_widget->get_zoom()));
+ 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);
+
+ // Draw the background.
+ if (background_texture.is_valid()) {
+ base_control->draw_texture_rect_region(background_texture, Rect2(-background_region.size / 2 - background_offset, background_region.size), background_region, background_modulate, background_transpose);
+ }
+
+ // Draw the polygons.
+ for (unsigned int i = 0; i < polygons.size(); i++) {
+ const Vector<Vector2> &polygon = polygons[i];
+ Color color = polygon_color;
+ if (!in_creation_polygon.is_empty()) {
+ color = color.darkened(0.3);
+ }
+ color.a = 0.5;
+ Vector<Color> v_color;
+ v_color.push_back(color);
+ base_control->draw_polygon(polygon, v_color);
+
+ color.a = 0.7;
+ for (int j = 0; j < polygon.size(); j++) {
+ base_control->draw_line(polygon[j], polygon[(j + 1) % polygon.size()], color);
+ }
+ }
+
+ // Draw the polygon in creation.
+ if (!in_creation_polygon.is_empty()) {
+ for (int i = 0; i < in_creation_polygon.size() - 1; i++) {
+ base_control->draw_line(in_creation_polygon[i], in_creation_polygon[i + 1], Color(1.0, 1.0, 1.0));
+ }
+ }
+
+ Point2 in_creation_point = xform.affine_inverse().xform(base_control->get_local_mouse_position());
+ float in_creation_distance = grab_threshold * 2.0;
+ _snap_to_tile_shape(in_creation_point, in_creation_distance, grab_threshold / editor_zoom_widget->get_zoom());
+ if (button_pixel_snap->is_pressed()) {
+ _snap_to_half_pixel(in_creation_point);
+ }
+
+ if (drag_type == DRAG_TYPE_CREATE_POINT && !in_creation_polygon.is_empty()) {
+ base_control->draw_line(in_creation_polygon[in_creation_polygon.size() - 1], in_creation_point, Color(1.0, 1.0, 1.0));
+ }
+
+ // Draw the handles.
+ int tinted_polygon_index = -1;
+ int tinted_point_index = -1;
+ if (drag_type == DRAG_TYPE_DRAG_POINT) {
+ tinted_polygon_index = drag_polygon_index;
+ tinted_point_index = drag_point_index;
+ } else if (hovered_point_index >= 0) {
+ tinted_polygon_index = hovered_polygon_index;
+ tinted_point_index = hovered_point_index;
+ }
+
+ base_control->draw_set_transform_matrix(Transform2D());
+ if (!in_creation_polygon.is_empty()) {
+ for (int i = 0; i < in_creation_polygon.size(); i++) {
+ base_control->draw_texture(handle, xform.xform(in_creation_polygon[i]) - handle->get_size() / 2);
+ }
+ } else {
+ for (int i = 0; i < (int)polygons.size(); i++) {
+ const Vector<Vector2> &polygon = polygons[i];
+ for (int j = 0; j < polygon.size(); j++) {
+ const Color modulate = (tinted_polygon_index == i && tinted_point_index == j) ? Color(0.5, 1, 2) : Color(1, 1, 1);
+ base_control->draw_texture(handle, xform.xform(polygon[j]) - handle->get_size() / 2, modulate);
+ }
+ }
+ }
+
+ // Draw the text on top of the selected point.
+ if (tinted_polygon_index >= 0) {
+ Ref<Font> font = get_theme_font("font", "Label");
+ int font_size = get_theme_font_size("font_size", "Label");
+ String text = multiple_polygon_mode ? vformat("%d:%d", tinted_polygon_index, tinted_point_index) : vformat("%d", tinted_point_index);
+ Size2 text_size = font->get_string_size(text, font_size);
+ base_control->draw_string(font, xform.xform(polygons[tinted_polygon_index][tinted_point_index]) - text_size * 0.5, text, HALIGN_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5));
+ }
+
+ if (drag_type == DRAG_TYPE_CREATE_POINT) {
+ base_control->draw_texture(handle, xform.xform(in_creation_point) - handle->get_size() / 2, Color(0.5, 1, 2));
+ }
+
+ // Draw the point creation preview in edit mode.
+ if (hovered_segment_index >= 0) {
+ base_control->draw_texture(add_handle, xform.xform(hovered_segment_point) - add_handle->get_size() / 2);
+ }
+
+ // 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);
+ base_control->draw_set_transform_matrix(Transform2D());
+}
+
+void GenericTilePolygonEditor::_center_view() {
+ panning = Vector2();
+ base_control->update();
+ button_center_view->set_disabled(true);
+}
+
+void GenericTilePolygonEditor::_zoom_changed() {
+ base_control->update();
+}
+
+void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) {
+ switch (p_item_pressed) {
+ case RESET_TO_DEFAULT_TILE:
+ undo_redo->create_action(TTR("Edit Polygons"));
+ undo_redo->add_do_method(this, "clear_polygons");
+ undo_redo->add_do_method(this, "add_polygon", tile_set->get_tile_shape_polygon());
+ undo_redo->add_do_method(base_control, "update");
+ undo_redo->add_do_method(this, "emit_signal", "polygons_changed");
+ undo_redo->add_undo_method(this, "clear_polygons");
+ for (unsigned int i = 0; i < polygons.size(); i++) {
+ undo_redo->add_undo_method(this, "add_polygon", polygons[i]);
+ }
+ undo_redo->add_undo_method(base_control, "update");
+ undo_redo->add_undo_method(this, "emit_signal", "polygons_changed");
+ undo_redo->commit_action(true);
+ break;
+ case CLEAR_TILE:
+ undo_redo->create_action(TTR("Edit Polygons"));
+ undo_redo->add_do_method(this, "clear_polygons");
+ undo_redo->add_do_method(base_control, "update");
+ undo_redo->add_do_method(this, "emit_signal", "polygons_changed");
+ undo_redo->add_undo_method(this, "clear_polygons");
+ for (unsigned int i = 0; i < polygons.size(); i++) {
+ undo_redo->add_undo_method(this, "add_polygon", polygons[i]);
+ }
+ undo_redo->add_undo_method(base_control, "update");
+ undo_redo->add_undo_method(this, "emit_signal", "polygons_changed");
+ undo_redo->commit_action(true);
+ break;
+ default:
+ break;
+ }
+}
+
+void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index) {
+ const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
+ r_polygon_index = -1;
+ r_point_index = -1;
+ float closest_distance = grab_threshold + 1.0;
+ for (unsigned int i = 0; i < polygons.size(); i++) {
+ const Vector<Vector2> &polygon = polygons[i];
+ for (int j = 0; j < polygon.size(); j++) {
+ float distance = p_pos.distance_to(p_polygon_xform.xform(polygon[j]));
+ if (distance < grab_threshold && distance < closest_distance) {
+ r_polygon_index = i;
+ r_point_index = j;
+ closest_distance = distance;
+ }
+ }
+ }
+}
+
+void GenericTilePolygonEditor::_grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point) {
+ const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
+
+ Point2 point = p_polygon_xform.affine_inverse().xform(p_pos);
+ r_polygon_index = -1;
+ r_segment_index = -1;
+ float closest_distance = grab_threshold * 2.0;
+ for (unsigned int i = 0; i < polygons.size(); i++) {
+ const Vector<Vector2> &polygon = polygons[i];
+ for (int j = 0; j < polygon.size(); j++) {
+ Vector2 segment[2] = { polygon[j], polygon[(j + 1) % polygon.size()] };
+ Vector2 closest_point = Geometry2D::get_closest_point_to_segment(point, segment);
+ float distance = closest_point.distance_to(point);
+ if (distance < grab_threshold / editor_zoom_widget->get_zoom() && distance < closest_distance) {
+ r_polygon_index = i;
+ r_segment_index = j;
+ r_point = closest_point;
+ closest_distance = distance;
+ }
+ }
+ }
+}
+
+void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ Vector<Point2> polygon = tile_set->get_tile_shape_polygon();
+ Point2 snapped_point = r_point;
+
+ // Snap to polygon vertices.
+ bool snapped = false;
+ for (int i = 0; i < polygon.size(); i++) {
+ float distance = r_point.distance_to(polygon[i]);
+ if (distance < p_snap_dist && distance < r_current_snapped_dist) {
+ snapped_point = polygon[i];
+ r_current_snapped_dist = distance;
+ snapped = true;
+ }
+ }
+
+ // Snap to edges if we did not snap to vertices.
+ if (!snapped) {
+ for (int i = 0; i < polygon.size(); i++) {
+ Point2 segment[2] = { polygon[i], polygon[(i + 1) % polygon.size()] };
+ Point2 point = Geometry2D::get_closest_point_to_segment(r_point, segment);
+ float distance = r_point.distance_to(point);
+ if (distance < p_snap_dist && distance < r_current_snapped_dist) {
+ snapped_point = point;
+ r_current_snapped_dist = distance;
+ }
+ }
+ }
+
+ r_point = snapped_point;
+}
+
+void GenericTilePolygonEditor::_snap_to_half_pixel(Point2 &r_point) {
+ r_point = (r_point * 2).round() / 2.0;
+}
+
+void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) {
+ real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius");
+
+ hovered_polygon_index = -1;
+ hovered_point_index = -1;
+ hovered_segment_index = -1;
+ hovered_segment_point = Vector2();
+
+ Transform2D xform;
+ xform.set_origin(base_control->get_size() / 2 + panning);
+ xform.set_scale(Vector2(editor_zoom_widget->get_zoom(), editor_zoom_widget->get_zoom()));
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ if (drag_type == DRAG_TYPE_DRAG_POINT) {
+ ERR_FAIL_INDEX(drag_polygon_index, (int)polygons.size());
+ ERR_FAIL_INDEX(drag_point_index, polygons[drag_polygon_index].size());
+ Point2 point = xform.affine_inverse().xform(mm->get_position());
+ float distance = grab_threshold * 2.0;
+ _snap_to_tile_shape(point, distance, grab_threshold / editor_zoom_widget->get_zoom());
+ if (button_pixel_snap->is_pressed()) {
+ _snap_to_half_pixel(point);
+ }
+ polygons[drag_polygon_index].write[drag_point_index] = point;
+ } else if (drag_type == DRAG_TYPE_PAN) {
+ panning += mm->get_position() - drag_last_pos;
+ drag_last_pos = mm->get_position();
+ button_center_view->set_disabled(panning.is_equal_approx(Vector2()));
+ } else {
+ // Update hovered point.
+ _grab_polygon_point(mm->get_position(), xform, hovered_polygon_index, hovered_point_index);
+
+ // If we have no hovered point, check if we hover a segment.
+ if (hovered_point_index == -1) {
+ _grab_polygon_segment_point(mm->get_position(), xform, hovered_polygon_index, hovered_segment_index, hovered_segment_point);
+ }
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_ctrl_pressed()) {
+ editor_zoom_widget->set_zoom_by_increments(1);
+ _zoom_changed();
+ accept_event();
+ } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_ctrl_pressed()) {
+ editor_zoom_widget->set_zoom_by_increments(-1);
+ _zoom_changed();
+ accept_event();
+ } else if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ if (tools_button_group->get_pressed_button() != button_create) {
+ in_creation_polygon.clear();
+ }
+ if (tools_button_group->get_pressed_button() == button_create) {
+ // Create points.
+ if (in_creation_polygon.size() >= 3 && mb->get_position().distance_to(xform.xform(in_creation_polygon[0])) < grab_threshold) {
+ // Closes and create polygon.
+ if (!multiple_polygon_mode) {
+ clear_polygons();
+ }
+ int added = add_polygon(in_creation_polygon);
+
+ in_creation_polygon.clear();
+ button_edit->set_pressed(true);
+ undo_redo->create_action(TTR("Edit Polygons"));
+ if (!multiple_polygon_mode) {
+ undo_redo->add_do_method(this, "clear_polygons");
+ }
+ undo_redo->add_do_method(this, "add_polygon", in_creation_polygon);
+ undo_redo->add_do_method(base_control, "update");
+ undo_redo->add_undo_method(this, "remove_polygon", added);
+ undo_redo->add_undo_method(base_control, "update");
+ undo_redo->commit_action(false);
+ emit_signal("polygons_changed");
+ } else {
+ // Create a new point.
+ drag_type = DRAG_TYPE_CREATE_POINT;
+ }
+ } else if (tools_button_group->get_pressed_button() == button_edit) {
+ // Edit points.
+ int closest_polygon;
+ int closest_point;
+ _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point);
+ if (closest_polygon >= 0) {
+ drag_type = DRAG_TYPE_DRAG_POINT;
+ drag_polygon_index = closest_polygon;
+ drag_point_index = closest_point;
+ drag_old_polygon = polygons[drag_polygon_index];
+ } else {
+ // Create a point.
+ Vector2 point_to_create;
+ _grab_polygon_segment_point(mb->get_position(), xform, closest_polygon, closest_point, point_to_create);
+ if (closest_polygon >= 0) {
+ polygons[closest_polygon].insert(closest_point + 1, point_to_create);
+ drag_type = DRAG_TYPE_DRAG_POINT;
+ drag_polygon_index = closest_polygon;
+ drag_point_index = closest_point + 1;
+ drag_old_polygon = polygons[closest_polygon];
+ }
+ }
+ } else if (tools_button_group->get_pressed_button() == button_delete) {
+ // Remove point.
+ int closest_polygon;
+ int closest_point;
+ _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point);
+ if (closest_polygon >= 0) {
+ PackedVector2Array old_polygon = polygons[closest_polygon];
+ polygons[closest_polygon].remove(closest_point);
+ undo_redo->create_action(TTR("Edit Polygons"));
+ if (polygons[closest_polygon].size() < 3) {
+ remove_polygon(closest_polygon);
+ undo_redo->add_do_method(this, "remove_polygon", closest_polygon);
+ undo_redo->add_undo_method(this, "add_polygon", old_polygon, closest_polygon);
+ } else {
+ undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]);
+ undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon);
+ }
+ undo_redo->add_do_method(base_control, "update");
+ undo_redo->add_undo_method(base_control, "update");
+ undo_redo->commit_action(false);
+ emit_signal("polygons_changed");
+ }
+ }
+ } else {
+ if (drag_type == DRAG_TYPE_DRAG_POINT) {
+ undo_redo->create_action(TTR("Edit Polygons"));
+ undo_redo->add_do_method(this, "set_polygon", drag_polygon_index, polygons[drag_polygon_index]);
+ undo_redo->add_do_method(base_control, "update");
+ undo_redo->add_undo_method(this, "set_polygon", drag_polygon_index, drag_old_polygon);
+ undo_redo->add_undo_method(base_control, "update");
+ undo_redo->commit_action(false);
+ emit_signal("polygons_changed");
+ } else if (drag_type == DRAG_TYPE_CREATE_POINT) {
+ Point2 point = xform.affine_inverse().xform(mb->get_position());
+ float distance = grab_threshold * 2;
+ _snap_to_tile_shape(point, distance, grab_threshold / editor_zoom_widget->get_zoom());
+ if (button_pixel_snap->is_pressed()) {
+ _snap_to_half_pixel(point);
+ }
+ in_creation_polygon.push_back(point);
+ }
+ drag_type = DRAG_TYPE_NONE;
+ drag_point_index = -1;
+ }
+
+ } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->is_pressed()) {
+ if (tools_button_group->get_pressed_button() == button_edit) {
+ // Remove point or pan.
+ int closest_polygon;
+ int closest_point;
+ _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point);
+ if (closest_polygon >= 0) {
+ PackedVector2Array old_polygon = polygons[closest_polygon];
+ polygons[closest_polygon].remove(closest_point);
+ undo_redo->create_action(TTR("Edit Polygons"));
+ if (polygons[closest_polygon].size() < 3) {
+ remove_polygon(closest_polygon);
+ undo_redo->add_do_method(this, "remove_polygon", closest_polygon);
+ undo_redo->add_undo_method(this, "add_polygon", old_polygon, closest_polygon);
+ } else {
+ undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]);
+ undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon);
+ }
+ undo_redo->add_do_method(base_control, "update");
+ undo_redo->add_undo_method(base_control, "update");
+ undo_redo->commit_action(false);
+ emit_signal("polygons_changed");
+ } else {
+ drag_type = DRAG_TYPE_PAN;
+ drag_last_pos = mb->get_position();
+ }
+ } else {
+ drag_type = DRAG_TYPE_PAN;
+ drag_last_pos = mb->get_position();
+ }
+ } else {
+ drag_type = DRAG_TYPE_NONE;
+ }
+ } else if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE) {
+ if (mb->is_pressed()) {
+ drag_type = DRAG_TYPE_PAN;
+ drag_last_pos = mb->get_position();
+ } else {
+ drag_type = DRAG_TYPE_NONE;
+ }
+ }
+ }
+
+ base_control->update();
+}
+
+void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) {
+ if (tile_set != p_tile_set) {
+ // Set the default tile shape
+ clear_polygons();
+ if (p_tile_set.is_valid()) {
+ add_polygon(p_tile_set->get_tile_shape_polygon());
+ }
+ }
+ tile_set = p_tile_set;
+}
+
+void GenericTilePolygonEditor::set_background(Ref<Texture2D> p_texture, Rect2 p_region, Vector2 p_offset, bool p_flip_h, bool p_flip_v, bool p_transpose, Color p_modulate) {
+ background_texture = p_texture;
+ background_region = p_region;
+ background_offset = p_offset;
+ background_h_flip = p_flip_h;
+ background_v_flip = p_flip_v;
+ background_transpose = p_transpose;
+ background_modulate = p_modulate;
+ base_control->update();
+}
+
+int GenericTilePolygonEditor::get_polygon_count() {
+ return polygons.size();
+}
+
+int GenericTilePolygonEditor::add_polygon(Vector<Point2> p_polygon, int p_index) {
+ ERR_FAIL_COND_V(p_polygon.size() < 3, -1);
+ ERR_FAIL_COND_V(!multiple_polygon_mode && polygons.size() >= 1, -1);
+
+ if (p_index < 0) {
+ polygons.push_back(p_polygon);
+ base_control->update();
+ button_edit->set_pressed(true);
+ return polygons.size() - 1;
+ } else {
+ polygons.insert(p_index, p_polygon);
+ button_edit->set_pressed(true);
+ base_control->update();
+ return p_index;
+ }
+}
+
+void GenericTilePolygonEditor::remove_polygon(int p_index) {
+ ERR_FAIL_INDEX(p_index, (int)polygons.size());
+ polygons.remove(p_index);
+
+ if (polygons.size() == 0) {
+ button_create->set_pressed(true);
+ }
+ base_control->update();
+}
+
+void GenericTilePolygonEditor::clear_polygons() {
+ polygons.clear();
+ base_control->update();
+}
+
+void GenericTilePolygonEditor::set_polygon(int p_polygon_index, Vector<Point2> p_polygon) {
+ ERR_FAIL_INDEX(p_polygon_index, (int)polygons.size());
+ ERR_FAIL_COND(p_polygon.size() < 3);
+ polygons[p_polygon_index] = p_polygon;
+ button_edit->set_pressed(true);
+ base_control->update();
+}
+
+Vector<Point2> GenericTilePolygonEditor::get_polygon(int p_polygon_index) {
+ ERR_FAIL_INDEX_V(p_polygon_index, (int)polygons.size(), Vector<Point2>());
+ return polygons[p_polygon_index];
+}
+
+void GenericTilePolygonEditor::set_polygons_color(Color p_color) {
+ polygon_color = p_color;
+ base_control->update();
+}
+
+void GenericTilePolygonEditor::set_multiple_polygon_mode(bool p_multiple_polygon_mode) {
+ multiple_polygon_mode = p_multiple_polygon_mode;
+}
+
+void GenericTilePolygonEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_READY:
+ button_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCreate", "EditorIcons"));
+ button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveEdit", "EditorIcons"));
+ button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveDelete", "EditorIcons"));
+ button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CenterView", "EditorIcons"));
+ button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Snap", "EditorIcons"));
+ button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("GuiTabMenu", "EditorIcons"));
+ break;
+ }
+}
+
+void GenericTilePolygonEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_polygon_count"), &GenericTilePolygonEditor::get_polygon_count);
+ ClassDB::bind_method(D_METHOD("add_polygon", "polygon", "index"), &GenericTilePolygonEditor::add_polygon, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("remove_polygon", "index"), &GenericTilePolygonEditor::remove_polygon);
+ ClassDB::bind_method(D_METHOD("clear_polygons"), &GenericTilePolygonEditor::clear_polygons);
+ ClassDB::bind_method(D_METHOD("set_polygon", "index", "polygon"), &GenericTilePolygonEditor::set_polygon);
+ ClassDB::bind_method(D_METHOD("get_polygon", "index"), &GenericTilePolygonEditor::set_polygon);
+
+ ADD_SIGNAL(MethodInfo("polygons_changed"));
+}
+
+GenericTilePolygonEditor::GenericTilePolygonEditor() {
+ toolbar = memnew(HBoxContainer);
+ add_child(toolbar);
+
+ tools_button_group.instantiate();
+
+ button_create = memnew(Button);
+ button_create->set_flat(true);
+ button_create->set_toggle_mode(true);
+ button_create->set_button_group(tools_button_group);
+ button_create->set_pressed(true);
+ toolbar->add_child(button_create);
+
+ button_edit = memnew(Button);
+ button_edit->set_flat(true);
+ button_edit->set_toggle_mode(true);
+ button_edit->set_button_group(tools_button_group);
+ toolbar->add_child(button_edit);
+
+ button_delete = memnew(Button);
+ button_delete->set_flat(true);
+ button_delete->set_toggle_mode(true);
+ button_delete->set_button_group(tools_button_group);
+ toolbar->add_child(button_delete);
+
+ button_advanced_menu = memnew(MenuButton);
+ button_advanced_menu->set_flat(true);
+ button_advanced_menu->set_toggle_mode(true);
+ button_advanced_menu->get_popup()->add_item(TTR("Reset to default tile shape"), RESET_TO_DEFAULT_TILE);
+ button_advanced_menu->get_popup()->add_item(TTR("Clear"), CLEAR_TILE);
+ button_advanced_menu->get_popup()->connect("id_pressed", callable_mp(this, &GenericTilePolygonEditor::_advanced_menu_item_pressed));
+ toolbar->add_child(button_advanced_menu);
+
+ toolbar->add_child(memnew(VSeparator));
+
+ button_pixel_snap = memnew(Button);
+ button_pixel_snap->set_flat(true);
+ button_pixel_snap->set_toggle_mode(true);
+ button_pixel_snap->set_pressed(true);
+ toolbar->add_child(button_pixel_snap);
+
+ Control *root = memnew(Control);
+ root->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ root->set_custom_minimum_size(Size2(0, 200 * EDSCALE));
+ root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ add_child(root);
+
+ panel = memnew(Panel);
+ panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ root->add_child(panel);
+
+ base_control = memnew(Control);
+ base_control->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ base_control->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ base_control->connect("draw", callable_mp(this, &GenericTilePolygonEditor::_base_control_draw));
+ base_control->connect("gui_input", callable_mp(this, &GenericTilePolygonEditor::_base_control_gui_input));
+ base_control->set_clip_contents(true);
+ root->add_child(base_control);
+
+ editor_zoom_widget = memnew(EditorZoomWidget);
+ editor_zoom_widget->set_position(Vector2(5, 5));
+ editor_zoom_widget->connect("zoom_changed", callable_mp(this, &GenericTilePolygonEditor::_zoom_changed).unbind(1));
+ root->add_child(editor_zoom_widget);
+
+ button_center_view = memnew(Button);
+ button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CenterView", "EditorIcons"));
+ button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5);
+ button_center_view->connect("pressed", callable_mp(this, &GenericTilePolygonEditor::_center_view));
+ button_center_view->set_flat(true);
+ button_center_view->set_disabled(true);
+ root->add_child(button_center_view);
+}
+
+void TileDataDefaultEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) {
+ ERR_FAIL_COND(!dummy_object);
+ dummy_object->set(p_property, p_value);
+}
+
+Variant TileDataDefaultEditor::_get_painted_value() {
+ ERR_FAIL_COND_V(!dummy_object, Variant());
+ return dummy_object->get(property);
+}
+
+void TileDataDefaultEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+ Variant value = tile_data->get(property);
+ dummy_object->set(property, value);
+ if (property_editor) {
+ property_editor->update_property();
+ }
+}
+
+void TileDataDefaultEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+ tile_data->set(property, p_value);
+}
+
+Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND_V(!tile_data, Variant());
+ return tile_data->get(property);
+}
+
+void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) {
+ for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) {
+ Vector2i coords = E->key().get_atlas_coords();
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), E->get());
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), p_new_value);
+ }
+}
+
+void TileDataDefaultEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) {
+ if (drag_type == DRAG_TYPE_PAINT_RECT) {
+ 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);
+
+ p_canvas_item->draw_set_transform_matrix(p_transform);
+
+ Rect2i rect;
+ rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos));
+ rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position())));
+ rect = rect.abs();
+
+ Set<TileMapCell> edited;
+ for (int x = rect.get_position().x; x <= rect.get_end().x; x++) {
+ for (int y = rect.get_position().y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ edited.insert(cell);
+ }
+ }
+ }
+
+ for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) {
+ Vector2i coords = E->get().get_atlas_coords();
+ p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false);
+ }
+ p_canvas_item->draw_set_transform_matrix(Transform2D());
+ }
+};
+
+void TileDataDefaultEditor::forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){
+
+};
+
+void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ if (drag_type == DRAG_TYPE_PAINT) {
+ Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position()));
+ for (int i = 0; i < line.size(); i++) {
+ Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ if (!drag_modified.has(cell)) {
+ drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0);
+ }
+ _set_value(p_tile_set_atlas_source, coords, 0, drag_painted_value);
+ }
+ }
+ drag_last_pos = mm->get_position();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ if (picker_button->is_pressed()) {
+ Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position());
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ _set_painted_value(p_tile_set_atlas_source, coords, 0);
+ picker_button->set_pressed(false);
+ }
+ } else if (mb->is_ctrl_pressed()) {
+ drag_type = DRAG_TYPE_PAINT_RECT;
+ drag_modified.clear();
+ drag_painted_value = _get_painted_value();
+ drag_start_pos = mb->get_position();
+ } else {
+ drag_type = DRAG_TYPE_PAINT;
+ drag_modified.clear();
+ drag_painted_value = _get_painted_value();
+ Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position());
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0);
+ _set_value(p_tile_set_atlas_source, coords, 0, drag_painted_value);
+ }
+ drag_last_pos = mb->get_position();
+ }
+ } else {
+ if (drag_type == DRAG_TYPE_PAINT_RECT) {
+ Rect2i rect;
+ rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos));
+ rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()));
+ rect = rect.abs();
+
+ drag_modified.clear();
+ for (int x = rect.get_position().x; x <= rect.get_end().x; x++) {
+ for (int y = rect.get_position().y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0);
+ }
+ }
+ }
+ undo_redo->create_action(TTR("Painting Tiles Property"));
+ _setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value);
+ undo_redo->commit_action(true);
+ drag_type = DRAG_TYPE_NONE;
+ } else if (drag_type == DRAG_TYPE_PAINT) {
+ undo_redo->create_action(TTR("Painting Tiles Property"));
+ _setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value);
+ undo_redo->commit_action(false);
+ drag_type = DRAG_TYPE_NONE;
+ }
+ }
+ }
+ }
+}
+
+void TileDataDefaultEditor::forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ if (drag_type == DRAG_TYPE_PAINT) {
+ Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position());
+ Vector2i coords = Vector2i(tile.x, tile.y);
+ int alternative_tile = tile.z;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = alternative_tile;
+ if (!drag_modified.has(cell)) {
+ drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, alternative_tile);
+ }
+ _set_value(p_tile_set_atlas_source, coords, alternative_tile, drag_painted_value);
+ }
+
+ drag_last_pos = mm->get_position();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ if (picker_button->is_pressed()) {
+ Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position());
+ Vector2i coords = Vector2i(tile.x, tile.y);
+ int alternative_tile = tile.z;
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ _set_painted_value(p_tile_set_atlas_source, coords, alternative_tile);
+ picker_button->set_pressed(false);
+ }
+ } else {
+ drag_type = DRAG_TYPE_PAINT;
+ drag_modified.clear();
+ drag_painted_value = _get_painted_value();
+
+ Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position());
+ Vector2i coords = Vector2i(tile.x, tile.y);
+ int alternative_tile = tile.z;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = alternative_tile;
+ drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, alternative_tile);
+ _set_value(p_tile_set_atlas_source, coords, alternative_tile, drag_painted_value);
+ }
+ drag_last_pos = mb->get_position();
+ }
+ } else {
+ undo_redo->create_action(TTR("Painting Tiles Property"));
+ _setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value);
+ undo_redo->commit_action(false);
+ drag_type = DRAG_TYPE_NONE;
+ }
+ }
+ }
+}
+
+void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ ERR_FAIL_COND(!tile_data);
+
+ bool valid;
+ Variant value = tile_data->get(property, &valid);
+ if (!valid) {
+ return;
+ }
+
+ if (value.get_type() == Variant::BOOL) {
+ Ref<Texture2D> texture = (bool)value ? tile_bool_checked : tile_bool_unchecked;
+ int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3;
+ Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size)));
+ p_canvas_item->draw_texture_rect(texture, rect);
+ } else if (value.get_type() == Variant::COLOR) {
+ int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3;
+ Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size)));
+ p_canvas_item->draw_rect(rect, value);
+ } else {
+ Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts");
+ String text;
+ switch (value.get_type()) {
+ case Variant::INT:
+ text = vformat("%d", value);
+ break;
+ case Variant::FLOAT:
+ text = vformat("%.2f", value);
+ break;
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ text = value;
+ break;
+ default:
+ return;
+ break;
+ }
+
+ Color color = Color(1, 1, 1);
+ 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);
+ selection_color.set_v(0.9);
+ color = selection_color;
+ } else if (is_visible_in_tree()) {
+ Variant painted_value = _get_painted_value();
+ bool equal = (painted_value.get_type() == Variant::FLOAT && value.get_type() == Variant::FLOAT) ? Math::is_equal_approx(float(painted_value), float(value)) : painted_value == value;
+ if (equal) {
+ color = Color(0.7, 0.7, 0.7);
+ }
+ }
+
+ Vector2 string_size = font->get_string_size(text);
+ p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1));
+ }
+}
+
+void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p_property, String p_label, Variant p_default_value) {
+ ERR_FAIL_COND_MSG(!property.is_empty(), "Cannot setup TileDataDefaultEditor twice");
+ property = p_property;
+
+ // Update everything.
+ if (property_editor) {
+ property_editor->queue_delete();
+ }
+
+ // Update the dummy object.
+ dummy_object->add_dummy_property(p_property);
+
+ // Get the default value for the type.
+ if (p_default_value == Variant()) {
+ Callable::CallError error;
+ Variant painted_value;
+ Variant::construct(p_type, painted_value, nullptr, 0, error);
+ dummy_object->set(p_property, painted_value);
+ } else {
+ dummy_object->set(p_property, p_default_value);
+ }
+
+ // Create and setup the property editor.
+ property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, p_type, p_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT);
+ property_editor->set_object_and_property(dummy_object, p_property);
+ if (p_label.is_empty()) {
+ property_editor->set_label(p_property);
+ } else {
+ property_editor->set_label(p_label);
+ }
+ property_editor->connect("property_changed", callable_mp(this, &TileDataDefaultEditor::_property_value_changed).unbind(1));
+ property_editor->update_property();
+ add_child(property_editor);
+}
+
+void TileDataDefaultEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
+ tile_bool_checked = get_theme_icon("TileChecked", "EditorIcons");
+ tile_bool_unchecked = get_theme_icon("TileUnchecked", "EditorIcons");
+ break;
+ default:
+ break;
+ }
+}
+
+TileDataDefaultEditor::TileDataDefaultEditor() {
+ label = memnew(Label);
+ label->set_text("Painting:");
+ add_child(label);
+
+ toolbar->add_child(memnew(VSeparator));
+
+ picker_button = memnew(Button);
+ picker_button->set_flat(true);
+ picker_button->set_toggle_mode(true);
+ picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P));
+ toolbar->add_child(picker_button);
+}
+
+TileDataDefaultEditor::~TileDataDefaultEditor() {
+ toolbar->queue_delete();
+ memdelete(dummy_object);
+}
+
+void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ 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);
+}
+
+void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ ERR_FAIL_COND(!tile_data);
+
+ bool valid;
+ Variant value = tile_data->get(property, &valid);
+ if (!valid) {
+ return;
+ }
+ ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I && value.get_type() != Variant::VECTOR2);
+
+ Color color = Color(1.0, 1.0, 1.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;
+ }
+ Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons");
+ p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(value)) - position_icon->get_size() / 2, color);
+}
+
+void TileDataYSortEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ ERR_FAIL_COND(!tile_data);
+
+ Color color = Color(1.0, 1.0, 1.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;
+ }
+ Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons");
+ p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(0, tile_data->get_y_sort_origin())) - position_icon->get_size() / 2, color);
+}
+
+void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ ERR_FAIL_COND(!tile_data);
+
+ 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 color = grid_color.darkened(0.2);
+ if (p_selected) {
+ color = selection_color.darkened(0.2);
+ }
+ color.a *= 0.5;
+
+ Vector<Color> debug_occlusion_color;
+ debug_occlusion_color.push_back(color);
+
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
+ Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer);
+ if (occluder.is_valid() && occluder->get_polygon().size() >= 3) {
+ p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color);
+ }
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
+}
+
+Variant TileDataOcclusionShapeEditor::_get_painted_value() {
+ Ref<OccluderPolygon2D> occluder_polygon;
+ occluder_polygon.instantiate();
+ if (polygon_editor->get_polygon_count() >= 1) {
+ occluder_polygon->set_polygon(polygon_editor->get_polygon(0));
+ }
+ return occluder_polygon;
+}
+
+void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+
+ Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer);
+ polygon_editor->clear_polygons();
+ if (occluder_polygon.is_valid()) {
+ polygon_editor->add_polygon(occluder_polygon->get_polygon());
+ }
+ polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+}
+
+void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+ Ref<OccluderPolygon2D> occluder_polygon = p_value;
+ tile_data->set_occluder(occlusion_layer, occluder_polygon);
+
+ polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+}
+
+Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND_V(!tile_data, Variant());
+ return tile_data->get_occluder(occlusion_layer);
+}
+
+void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) {
+ for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) {
+ Vector2i coords = E->key().get_atlas_coords();
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), E->get());
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), p_new_value);
+ }
+}
+
+void TileDataOcclusionShapeEditor::_tile_set_changed() {
+ polygon_editor->set_tile_set(tile_set);
+}
+
+void TileDataOcclusionShapeEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color());
+ break;
+ default:
+ break;
+ }
+}
+
+TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() {
+ polygon_editor = memnew(GenericTilePolygonEditor);
+ add_child(polygon_editor);
+}
+
+void TileDataCollisionEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) {
+ dummy_object->set(p_property, p_value);
+}
+
+void TileDataCollisionEditor::_polygons_changed() {
+ // Update the dummy object properties and their editors.
+ for (int i = 0; i < polygon_editor->get_polygon_count(); i++) {
+ StringName one_way_property = vformat("polygon_%d_one_way", i);
+ StringName one_way_margin_property = vformat("polygon_%d_one_way_margin", i);
+
+ if (!dummy_object->has_dummy_property(one_way_property)) {
+ dummy_object->add_dummy_property(one_way_property);
+ dummy_object->set(one_way_property, false);
+ }
+
+ if (!dummy_object->has_dummy_property(one_way_margin_property)) {
+ dummy_object->add_dummy_property(one_way_margin_property);
+ dummy_object->set(one_way_margin_property, 1.0);
+ }
+
+ if (!property_editors.has(one_way_property)) {
+ EditorProperty *one_way_property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::BOOL, one_way_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT);
+ one_way_property_editor->set_object_and_property(dummy_object, one_way_property);
+ one_way_property_editor->set_label(one_way_property);
+ one_way_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1));
+ one_way_property_editor->update_property();
+ add_child(one_way_property_editor);
+ property_editors[one_way_property] = one_way_property_editor;
+ }
+
+ if (!property_editors.has(one_way_margin_property)) {
+ EditorProperty *one_way_margin_property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::FLOAT, one_way_margin_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT);
+ one_way_margin_property_editor->set_object_and_property(dummy_object, one_way_margin_property);
+ one_way_margin_property_editor->set_label(one_way_margin_property);
+ one_way_margin_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1));
+ one_way_margin_property_editor->update_property();
+ add_child(one_way_margin_property_editor);
+ property_editors[one_way_margin_property] = one_way_margin_property_editor;
+ }
+ }
+
+ // Remove uneeded properties and their editors.
+ for (int i = polygon_editor->get_polygon_count(); dummy_object->has_dummy_property(vformat("polygon_%d_one_way", i)); i++) {
+ dummy_object->remove_dummy_property(vformat("polygon_%d_one_way", i));
+ }
+ for (int i = polygon_editor->get_polygon_count(); dummy_object->has_dummy_property(vformat("polygon_%d_one_way_margin", i)); i++) {
+ dummy_object->remove_dummy_property(vformat("polygon_%d_one_way_margin", i));
+ }
+ for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way", i)); i++) {
+ property_editors[vformat("polygon_%d_one_way", i)]->queue_delete();
+ property_editors.erase(vformat("polygon_%d_one_way", i));
+ }
+ for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way_margin", i)); i++) {
+ property_editors[vformat("polygon_%d_one_way_margin", i)]->queue_delete();
+ property_editors.erase(vformat("polygon_%d_one_way_margin", i));
+ }
+}
+
+Variant TileDataCollisionEditor::_get_painted_value() {
+ Array array;
+ for (int i = 0; i < polygon_editor->get_polygon_count(); i++) {
+ ERR_FAIL_COND_V(polygon_editor->get_polygon(i).size() < 3, Variant());
+ Dictionary dict;
+ dict["points"] = polygon_editor->get_polygon(i);
+ dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i));
+ dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i));
+ array.push_back(dict);
+ }
+
+ return array;
+}
+
+void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+
+ polygon_editor->clear_polygons();
+ for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) {
+ Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i);
+ if (polygon.size() >= 3) {
+ polygon_editor->add_polygon(polygon);
+ }
+ }
+
+ _polygons_changed();
+ for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) {
+ dummy_object->set(vformat("polygon_%d_one_way", i), tile_data->is_collision_polygon_one_way(physics_layer, i));
+ dummy_object->set(vformat("polygon_%d_one_way_margin", i), tile_data->get_collision_polygon_one_way_margin(physics_layer, i));
+ }
+ for (Map<StringName, EditorProperty *>::Element *E = property_editors.front(); E; E = E->next()) {
+ E->get()->update_property();
+ }
+
+ polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+}
+
+void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+
+ Array array = p_value;
+ tile_data->set_collision_polygons_count(physics_layer, array.size());
+ for (int i = 0; i < array.size(); i++) {
+ Dictionary dict = array[i];
+ tile_data->set_collision_polygon_points(physics_layer, i, dict["points"]);
+ tile_data->set_collision_polygon_one_way(physics_layer, i, dict["one_way"]);
+ tile_data->set_collision_polygon_one_way_margin(physics_layer, i, dict["one_way_margin"]);
+ }
+
+ polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+}
+
+Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND_V(!tile_data, Variant());
+
+ Array array;
+ for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) {
+ Dictionary dict;
+ dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i);
+ dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i);
+ dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i);
+ array.push_back(dict);
+ }
+ return array;
+}
+
+void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) {
+ Array new_array = p_new_value;
+ for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) {
+ Array old_array = E->get();
+
+ Vector2i coords = E->key().get_atlas_coords();
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), old_array.size());
+ for (int i = 0; i < old_array.size(); i++) {
+ Dictionary dict = old_array[i];
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]);
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]);
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]);
+ }
+
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), new_array.size());
+ for (int i = 0; i < new_array.size(); i++) {
+ Dictionary dict = new_array[i];
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]);
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]);
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]);
+ }
+ }
+}
+
+void TileDataCollisionEditor::_tile_set_changed() {
+ polygon_editor->set_tile_set(tile_set);
+ _polygons_changed();
+}
+
+void TileDataCollisionEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color());
+ break;
+ default:
+ break;
+ }
+}
+
+TileDataCollisionEditor::TileDataCollisionEditor() {
+ polygon_editor = memnew(GenericTilePolygonEditor);
+ polygon_editor->set_multiple_polygon_mode(true);
+ polygon_editor->connect("polygons_changed", callable_mp(this, &TileDataCollisionEditor::_polygons_changed));
+ add_child(polygon_editor);
+
+ _polygons_changed();
+}
+
+TileDataCollisionEditor::~TileDataCollisionEditor() {
+ memdelete(dummy_object);
+}
+
+void TileDataCollisionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ ERR_FAIL_COND(!tile_data);
+
+ // Draw all shapes.
+ Vector<Color> color;
+ 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);
+ selection_color.a = 0.7;
+ color.push_back(selection_color);
+ } else {
+ Color debug_collision_color = p_canvas_item->get_tree()->get_debug_collisions_color();
+ color.push_back(debug_collision_color);
+ }
+
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
+ for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) {
+ Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i);
+ if (polygon.size() >= 3) {
+ p_canvas_item->draw_polygon(polygon, color);
+ }
+ }
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
+}
+
+void TileDataTerrainsEditor::_update_terrain_selector() {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Update the terrain set selector.
+ Vector<String> options;
+ options.push_back(String(TTR("No terrains")) + String(":-1"));
+ for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) {
+ options.push_back(vformat("Terrain Set %d", i));
+ }
+ terrain_set_property_editor->setup(options);
+ terrain_set_property_editor->update_property();
+
+ // Update the terrain selector.
+ int terrain_set = int(dummy_object->get("terrain_set"));
+ if (terrain_set == -1) {
+ terrain_property_editor->hide();
+ } else {
+ options.clear();
+ Vector<Vector<Ref<Texture2D>>> icons = tile_set->generate_terrains_icons(Size2(16, 16) * EDSCALE);
+ options.push_back(String(TTR("No terrain")) + String(":-1"));
+ for (int i = 0; i < tile_set->get_terrains_count(terrain_set); i++) {
+ String name = tile_set->get_terrain_name(terrain_set, i);
+ if (name.is_empty()) {
+ options.push_back(vformat("Terrain %d", i));
+ } else {
+ options.push_back(name);
+ }
+ }
+ terrain_property_editor->setup(options);
+ terrain_property_editor->update_property();
+
+ // Kind of a hack to set icons.
+ // We could provide a way to modify that in the EditorProperty.
+ OptionButton *option_button = Object::cast_to<OptionButton>(terrain_property_editor->get_child(0));
+ for (int terrain = 0; terrain < tile_set->get_terrains_count(terrain_set); terrain++) {
+ option_button->set_item_icon(terrain + 1, icons[terrain_set][terrain]);
+ }
+ terrain_property_editor->show();
+ }
+}
+
+void TileDataTerrainsEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) {
+ Variant old_value = dummy_object->get(p_property);
+ dummy_object->set(p_property, p_value);
+ if (p_property == "terrain_set") {
+ if (p_value != old_value) {
+ dummy_object->set("terrain", -1);
+ }
+ _update_terrain_selector();
+ }
+ emit_signal("needs_redraw");
+}
+
+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()) {
+ 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)) {
+ dummy_object->set("terrain", -1);
+ }
+ }
+
+ _update_terrain_selector();
+}
+
+void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Draw the hovered terrain bit, or the whole tile if it has the wrong terrain set.
+ Vector2i hovered_coords = TileSetSource::INVALID_ATLAS_COORDS;
+ if (drag_type == DRAG_TYPE_NONE) {
+ Vector2i mouse_pos = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position());
+ hovered_coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_pos);
+ hovered_coords = p_tile_set_atlas_source->get_tile_at_coords(hovered_coords);
+ if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, 0));
+ int terrain_set = tile_data->get_terrain_set();
+ Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(hovered_coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, 0);
+
+ if (terrain_set >= 0 && terrain_set == int(dummy_object->get("terrain_set"))) {
+ // Draw hovered bit.
+ Transform2D xform;
+ xform.set_origin(position);
+
+ Vector<Color> color;
+ color.push_back(Color(1.0, 1.0, 1.0, 0.5));
+
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) {
+ p_canvas_item->draw_set_transform_matrix(p_transform * xform);
+ p_canvas_item->draw_polygon(polygon, color);
+ }
+ }
+ }
+ } 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);
+ }
+ }
+ }
+
+ // Dim terrains with wrong terrain set.
+ Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts");
+ for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i coords = p_tile_set_atlas_source->get_tile_id(i);
+ if (coords != hovered_coords) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) {
+ // Dimming
+ p_canvas_item->draw_set_transform_matrix(p_transform);
+ Rect2i rect = p_tile_set_atlas_source->get_tile_texture_region(coords);
+ p_canvas_item->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.3));
+
+ // Text
+ p_canvas_item->draw_set_transform_matrix(Transform2D());
+ Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+
+ Color color = Color(1, 1, 1);
+ String text;
+ if (tile_data->get_terrain_set() >= 0) {
+ text = vformat("%d", tile_data->get_terrain_set());
+ } else {
+ text = "-";
+ }
+ Vector2 string_size = font->get_string_size(text);
+ p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1));
+ }
+ }
+ }
+ p_canvas_item->draw_set_transform_matrix(Transform2D());
+
+ if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) {
+ // Draw selection rectangle.
+ 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);
+
+ p_canvas_item->draw_set_transform_matrix(p_transform);
+
+ Rect2i rect;
+ rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos));
+ rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position())));
+ rect = rect.abs();
+
+ Set<TileMapCell> edited;
+ for (int x = rect.get_position().x; x <= rect.get_end().x; x++) {
+ for (int y = rect.get_position().y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ edited.insert(cell);
+ }
+ }
+ }
+
+ for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) {
+ Vector2i coords = E->get().get_atlas_coords();
+ p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false);
+ }
+ p_canvas_item->draw_set_transform_matrix(Transform2D());
+ } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) {
+ // Highlight selected peering bits.
+ Dictionary painted = Dictionary(drag_painted_value);
+ int terrain_set = int(painted["terrain_set"]);
+
+ Rect2i rect;
+ rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos));
+ rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position())));
+ rect = rect.abs();
+
+ Set<TileMapCell> edited;
+ for (int x = rect.get_position().x; x <= rect.get_end().x; x++) {
+ for (int y = rect.get_position().y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ if (tile_data->get_terrain_set() == terrain_set) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ edited.insert(cell);
+ }
+ }
+ }
+ }
+
+ Vector2 end = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position());
+ Vector<Point2> mouse_pos_rect_polygon;
+ mouse_pos_rect_polygon.push_back(drag_start_pos);
+ mouse_pos_rect_polygon.push_back(Vector2(end.x, drag_start_pos.y));
+ mouse_pos_rect_polygon.push_back(end);
+ mouse_pos_rect_polygon.push_back(Vector2(drag_start_pos.x, end.y));
+
+ Vector<Color> color;
+ color.push_back(Color(1.0, 1.0, 1.0, 0.5));
+
+ p_canvas_item->draw_set_transform_matrix(p_transform);
+
+ for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) {
+ Vector2i coords = E->get().get_atlas_coords();
+
+ Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ for (int j = 0; j < polygon.size(); j++) {
+ polygon.write[j] += position;
+ }
+ if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) {
+ // Draw bit.
+ p_canvas_item->draw_polygon(polygon, color);
+ }
+ }
+ }
+ }
+
+ p_canvas_item->draw_set_transform_matrix(Transform2D());
+ }
+}
+
+void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Draw the hovered terrain bit, or the whole tile if it has the wrong terrain set.
+ Vector2i hovered_coords = TileSetSource::INVALID_ATLAS_COORDS;
+ int hovered_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ if (drag_type == DRAG_TYPE_NONE) {
+ Vector2i mouse_pos = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position());
+ Vector3i hovered = p_tile_atlas_view->get_alternative_tile_at_pos(mouse_pos);
+ hovered_coords = Vector2i(hovered.x, hovered.y);
+ hovered_alternative = hovered.z;
+ if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, hovered_alternative));
+ int terrain_set = tile_data->get_terrain_set();
+ Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(hovered_coords, hovered_alternative);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, hovered_alternative);
+
+ if (terrain_set == int(dummy_object->get("terrain_set"))) {
+ // Draw hovered bit.
+ Transform2D xform;
+ xform.set_origin(position);
+
+ Vector<Color> color;
+ color.push_back(Color(1.0, 1.0, 1.0, 0.5));
+
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) {
+ p_canvas_item->draw_set_transform_matrix(p_transform * xform);
+ p_canvas_item->draw_polygon(polygon, color);
+ }
+ }
+ }
+ } 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);
+ }
+ }
+ }
+
+ // Dim terrains with wrong terrain set.
+ Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts");
+ for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i coords = p_tile_set_atlas_source->get_tile_id(i);
+ for (int j = 1; j < p_tile_set_atlas_source->get_alternative_tiles_count(coords); j++) {
+ int alternative_tile = p_tile_set_atlas_source->get_alternative_tile_id(coords, j);
+ if (coords != hovered_coords || alternative_tile != hovered_alternative) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile));
+ if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) {
+ // Dimming
+ p_canvas_item->draw_set_transform_matrix(p_transform);
+ Rect2i rect = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile);
+ p_canvas_item->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.3));
+
+ // Text
+ p_canvas_item->draw_set_transform_matrix(Transform2D());
+ Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+
+ Color color = Color(1, 1, 1);
+ String text;
+ if (tile_data->get_terrain_set() >= 0) {
+ text = vformat("%d", tile_data->get_terrain_set());
+ } else {
+ text = "-";
+ }
+ Vector2 string_size = font->get_string_size(text);
+ p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1));
+ }
+ }
+ }
+ }
+
+ p_canvas_item->draw_set_transform_matrix(Transform2D());
+}
+
+void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) {
+ Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position()));
+ for (int i = 0; i < line.size(); i++) {
+ Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ int terrain_set = drag_painted_value;
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+
+ // Save the old terrain_set and terrains bits.
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ if (!drag_modified.has(cell)) {
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+ }
+
+ // Set the terrain_set.
+ tile_data->set_terrain_set(terrain_set);
+ }
+ }
+ drag_last_pos = mm->get_position();
+ } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) {
+ int terrain_set = Dictionary(drag_painted_value)["terrain_set"];
+ int terrain = Dictionary(drag_painted_value)["terrain"];
+ Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position()));
+ for (int i = 0; i < line.size(); i++) {
+ Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ if (tile_data->get_terrain_set() == terrain_set) {
+ // Save the old terrain_set and terrains bits.
+ if (!drag_modified.has(cell)) {
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+ }
+
+ // Set the terrains bits.
+ Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ if (tile_data->is_valid_peering_bit_terrain(bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit);
+ if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) {
+ tile_data->set_peering_bit_terrain(bit, terrain);
+ }
+ }
+ }
+ }
+ }
+ }
+ drag_last_pos = mm->get_position();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ if (picker_button->is_pressed()) {
+ Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position());
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ int terrain_set = tile_data->get_terrain_set();
+ Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+ dummy_object->set("terrain_set", terrain_set);
+ dummy_object->set("terrain", -1);
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) {
+ dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit));
+ }
+ }
+ }
+ terrain_set_property_editor->update_property();
+ _update_terrain_selector();
+ picker_button->set_pressed(false);
+ }
+ } else {
+ Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position());
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ TileData *tile_data = nullptr;
+ if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) {
+ tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ }
+ int terrain_set = int(dummy_object->get("terrain_set"));
+ int terrain = int(dummy_object->get("terrain"));
+ if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) {
+ if (mb->is_ctrl_pressed()) {
+ // Paint terrain set with rect.
+ drag_type = DRAG_TYPE_PAINT_TERRAIN_SET_RECT;
+ drag_modified.clear();
+ drag_painted_value = terrain_set;
+ drag_start_pos = mb->get_position();
+ } else {
+ // Paint terrain set.
+ drag_type = DRAG_TYPE_PAINT_TERRAIN_SET;
+ drag_modified.clear();
+ drag_painted_value = terrain_set;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+
+ // Save the old terrain_set and terrains bits.
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+
+ // Set the terrain_set.
+ tile_data->set_terrain_set(terrain_set);
+ }
+ drag_last_pos = mb->get_position();
+ }
+ } else if (tile_data && tile_data->get_terrain_set() == terrain_set) {
+ if (mb->is_ctrl_pressed()) {
+ // Paint terrain set with rect.
+ drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS_RECT;
+ drag_modified.clear();
+ Dictionary painted_dict;
+ painted_dict["terrain_set"] = terrain_set;
+ painted_dict["terrain"] = terrain;
+ drag_painted_value = painted_dict;
+ drag_start_pos = mb->get_position();
+ } else {
+ // Paint terrain bits.
+ drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS;
+ drag_modified.clear();
+ Dictionary painted_dict;
+ painted_dict["terrain_set"] = terrain_set;
+ painted_dict["terrain"] = terrain;
+ drag_painted_value = painted_dict;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+
+ // Save the old terrain_set and terrains bits.
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+
+ // Set the terrain bit.
+ Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) {
+ tile_data->set_peering_bit_terrain(bit, terrain);
+ }
+ }
+ }
+ }
+ drag_last_pos = mb->get_position();
+ }
+ }
+ }
+ } else {
+ if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) {
+ Rect2i rect;
+ rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos));
+ rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()));
+ rect = rect.abs();
+
+ Set<TileMapCell> edited;
+ for (int x = rect.get_position().x; x <= rect.get_end().x; x++) {
+ for (int y = rect.get_position().y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ edited.insert(cell);
+ }
+ }
+ }
+ undo_redo->create_action(TTR("Painting Terrain Set"));
+ for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) {
+ Vector2i coords = E->get().get_atlas_coords();
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), tile_data->get_terrain_set());
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), drag_painted_value);
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_data->is_valid_peering_bit_terrain(bit)) {
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit));
+ }
+ }
+ }
+ undo_redo->commit_action(true);
+ drag_type = DRAG_TYPE_NONE;
+ } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) {
+ undo_redo->create_action(TTR("Painting Terrain Set"));
+ for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) {
+ Dictionary dict = E->get();
+ Vector2i coords = E->key().get_atlas_coords();
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value);
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]);
+ Array array = dict["terrain_peering_bits"];
+ for (int i = 0; i < array.size(); i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) {
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]);
+ }
+ }
+ }
+ undo_redo->commit_action(false);
+ drag_type = DRAG_TYPE_NONE;
+ } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) {
+ Dictionary painted = Dictionary(drag_painted_value);
+ int terrain_set = int(painted["terrain_set"]);
+ int terrain = int(painted["terrain"]);
+ undo_redo->create_action(TTR("Painting Terrain"));
+ for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) {
+ Dictionary dict = E->get();
+ Vector2i coords = E->key().get_atlas_coords();
+ Array array = dict["terrain_peering_bits"];
+ for (int i = 0; i < array.size(); i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain);
+ }
+ if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) {
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]);
+ }
+ }
+ }
+ undo_redo->commit_action(false);
+ drag_type = DRAG_TYPE_NONE;
+ } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) {
+ Dictionary painted = Dictionary(drag_painted_value);
+ int terrain_set = int(painted["terrain_set"]);
+ int terrain = int(painted["terrain"]);
+
+ Rect2i rect;
+ rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos));
+ rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()));
+ rect = rect.abs();
+
+ Set<TileMapCell> edited;
+ for (int x = rect.get_position().x; x <= rect.get_end().x; x++) {
+ for (int y = rect.get_position().y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ coords = p_tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+ if (tile_data->get_terrain_set() == terrain_set) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ edited.insert(cell);
+ }
+ }
+ }
+ }
+
+ Vector<Point2> mouse_pos_rect_polygon;
+ mouse_pos_rect_polygon.push_back(drag_start_pos);
+ mouse_pos_rect_polygon.push_back(Vector2(mb->get_position().x, drag_start_pos.y));
+ mouse_pos_rect_polygon.push_back(mb->get_position());
+ mouse_pos_rect_polygon.push_back(Vector2(drag_start_pos.x, mb->get_position().y));
+
+ undo_redo->create_action(TTR("Painting Terrain"));
+ for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) {
+ Vector2i coords = E->get().get_atlas_coords();
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0));
+
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ for (int j = 0; j < polygon.size(); j++) {
+ polygon.write[j] += position;
+ }
+ if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) {
+ // Draw bit.
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), terrain);
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit));
+ }
+ }
+ }
+ }
+ undo_redo->commit_action(true);
+ drag_type = DRAG_TYPE_NONE;
+ }
+ }
+ }
+ }
+}
+
+void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) {
+ Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position());
+ Vector2i coords = Vector2i(tile.x, tile.y);
+ int alternative_tile = tile.z;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = alternative_tile;
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile));
+ if (!drag_modified.has(cell)) {
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+ }
+ tile_data->set_terrain_set(drag_painted_value);
+ }
+
+ drag_last_pos = mm->get_position();
+ } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) {
+ Dictionary painted = Dictionary(drag_painted_value);
+ int terrain_set = int(painted["terrain_set"]);
+ int terrain = int(painted["terrain"]);
+
+ Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position());
+ Vector2i coords = Vector2i(tile.x, tile.y);
+ int alternative_tile = tile.z;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = alternative_tile;
+
+ // Save the old terrain_set and terrains bits.
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile));
+ if (tile_data->get_terrain_set() == terrain_set) {
+ if (!drag_modified.has(cell)) {
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+ }
+
+ // Set the terrains bits.
+ Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile);
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(j);
+ if (tile_data->is_valid_peering_bit_terrain(bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit);
+ if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) {
+ tile_data->set_peering_bit_terrain(bit, terrain);
+ }
+ }
+ }
+ }
+ }
+ drag_last_pos = mm->get_position();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ if (picker_button->is_pressed()) {
+ Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position());
+ Vector2i coords = Vector2i(tile.x, tile.y);
+ int alternative_tile = tile.z;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile));
+ int terrain_set = tile_data->get_terrain_set();
+ Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile);
+ dummy_object->set("terrain_set", terrain_set);
+ dummy_object->set("terrain", -1);
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) {
+ dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit));
+ }
+ }
+ }
+ terrain_set_property_editor->update_property();
+ _update_terrain_selector();
+ picker_button->set_pressed(false);
+ }
+ } else {
+ int terrain_set = int(dummy_object->get("terrain_set"));
+ int terrain = int(dummy_object->get("terrain"));
+
+ Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position());
+ Vector2i coords = Vector2i(tile.x, tile.y);
+ int alternative_tile = tile.z;
+
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile));
+
+ if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) {
+ drag_type = DRAG_TYPE_PAINT_TERRAIN_SET;
+ drag_modified.clear();
+ drag_painted_value = int(dummy_object->get("terrain_set"));
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = alternative_tile;
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+ tile_data->set_terrain_set(drag_painted_value);
+ }
+ drag_last_pos = mb->get_position();
+ } else if (tile_data && tile_data->get_terrain_set() == terrain_set) {
+ // Paint terrain bits.
+ drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS;
+ drag_modified.clear();
+ Dictionary painted_dict;
+ painted_dict["terrain_set"] = terrain_set;
+ painted_dict["terrain"] = terrain;
+ drag_painted_value = painted_dict;
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ TileMapCell cell;
+ cell.source_id = 0;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = alternative_tile;
+
+ // Save the old terrain_set and terrains bits.
+ Dictionary dict;
+ dict["terrain_set"] = tile_data->get_terrain_set();
+ Array array;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1);
+ }
+ dict["terrain_peering_bits"] = array;
+ drag_modified[cell] = dict;
+
+ // Set the terrain bit.
+ Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile);
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit);
+ if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) {
+ tile_data->set_peering_bit_terrain(bit, terrain);
+ }
+ }
+ }
+ }
+ drag_last_pos = mb->get_position();
+ }
+ }
+ } else {
+ if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) {
+ undo_redo->create_action(TTR("Painting Tiles Property"));
+ for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) {
+ Dictionary dict = E->get();
+ Vector2i coords = E->key().get_atlas_coords();
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]);
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value);
+ Array array = dict["terrain_peering_bits"];
+ for (int i = 0; i < array.size(); i++) {
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]);
+ }
+ }
+ undo_redo->commit_action(false);
+ drag_type = DRAG_TYPE_NONE;
+ } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) {
+ Dictionary painted = Dictionary(drag_painted_value);
+ int terrain_set = int(painted["terrain_set"]);
+ int terrain = int(painted["terrain"]);
+ undo_redo->create_action(TTR("Painting Terrain"));
+ for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) {
+ Dictionary dict = E->get();
+ Vector2i coords = E->key().get_atlas_coords();
+ Array array = dict["terrain_peering_bits"];
+ for (int i = 0; i < array.size(); i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) {
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain);
+ }
+ if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) {
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]);
+ }
+ }
+ }
+ undo_redo->commit_action(false);
+ drag_type = DRAG_TYPE_NONE;
+ }
+ }
+ }
+ }
+}
+
+void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ ERR_FAIL_COND(!tile_data);
+
+ tile_set->draw_terrains(p_canvas_item, p_transform, tile_data);
+}
+
+void TileDataTerrainsEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
+ break;
+ default:
+ break;
+ }
+}
+
+TileDataTerrainsEditor::TileDataTerrainsEditor() {
+ label = memnew(Label);
+ label->set_text("Painting:");
+ add_child(label);
+
+ // Toolbar
+ toolbar->add_child(memnew(VSeparator));
+
+ picker_button = memnew(Button);
+ picker_button->set_flat(true);
+ picker_button->set_toggle_mode(true);
+ picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P));
+ toolbar->add_child(picker_button);
+
+ // Setup
+ dummy_object->add_dummy_property("terrain_set");
+ dummy_object->set("terrain_set", -1);
+ dummy_object->add_dummy_property("terrain");
+ dummy_object->set("terrain", -1);
+
+ // Get the default value for the type.
+ terrain_set_property_editor = memnew(EditorPropertyEnum);
+ terrain_set_property_editor->set_object_and_property(dummy_object, "terrain_set");
+ terrain_set_property_editor->set_label("Terrain Set");
+ terrain_set_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1));
+ add_child(terrain_set_property_editor);
+
+ terrain_property_editor = memnew(EditorPropertyEnum);
+ terrain_property_editor->set_object_and_property(dummy_object, "terrain");
+ terrain_property_editor->set_label("Terrain");
+ terrain_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1));
+ add_child(terrain_property_editor);
+}
+
+TileDataTerrainsEditor::~TileDataTerrainsEditor() {
+ toolbar->queue_delete();
+ memdelete(dummy_object);
+}
+
+Variant TileDataNavigationEditor::_get_painted_value() {
+ Ref<NavigationPolygon> navigation_polygon;
+ navigation_polygon.instantiate();
+
+ for (int i = 0; i < polygon_editor->get_polygon_count(); i++) {
+ Vector<Vector2> polygon = polygon_editor->get_polygon(i);
+ navigation_polygon->add_outline(polygon);
+ }
+
+ navigation_polygon->make_polygons_from_outlines();
+ return navigation_polygon;
+}
+
+void TileDataNavigationEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+
+ Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer);
+ polygon_editor->clear_polygons();
+ if (navigation_polygon.is_valid()) {
+ for (int i = 0; i < navigation_polygon->get_outline_count(); i++) {
+ polygon_editor->add_polygon(navigation_polygon->get_outline(i));
+ }
+ }
+ polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+}
+
+void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND(!tile_data);
+ Ref<NavigationPolygon> navigation_polygon = p_value;
+ tile_data->set_navigation_polygon(navigation_layer, navigation_polygon);
+
+ polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+}
+
+Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
+ TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile));
+ ERR_FAIL_COND_V(!tile_data, Variant());
+ return tile_data->get_navigation_polygon(navigation_layer);
+}
+
+void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) {
+ for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) {
+ Vector2i coords = E->key().get_atlas_coords();
+ undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), E->get());
+ undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), p_new_value);
+ }
+}
+
+void TileDataNavigationEditor::_tile_set_changed() {
+ polygon_editor->set_tile_set(tile_set);
+}
+
+void TileDataNavigationEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ polygon_editor->set_polygons_color(get_tree()->get_debug_navigation_color());
+ break;
+ default:
+ break;
+ }
+}
+
+TileDataNavigationEditor::TileDataNavigationEditor() {
+ polygon_editor = memnew(GenericTilePolygonEditor);
+ polygon_editor->set_multiple_polygon_mode(true);
+ add_child(polygon_editor);
+}
+
+void TileDataNavigationEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) {
+ TileData *tile_data = _get_tile_data(p_cell);
+ ERR_FAIL_COND(!tile_data);
+
+ // Draw all shapes.
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
+
+ Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer);
+ if (navigation_polygon.is_valid()) {
+ Vector<Vector2> verts = navigation_polygon->get_vertices();
+ if (verts.size() < 3) {
+ return;
+ }
+
+ Color color = p_canvas_item->get_tree()->get_debug_navigation_color();
+ 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);
+ selection_color.a = 0.7;
+ color = selection_color;
+ }
+
+ RandomPCG rand;
+ for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) {
+ // An array of vertices for this polygon.
+ Vector<int> polygon = navigation_polygon->get_polygon(i);
+ Vector<Vector2> vertices;
+ vertices.resize(polygon.size());
+ for (int j = 0; j < polygon.size(); j++) {
+ ERR_FAIL_INDEX(polygon[j], verts.size());
+ vertices.write[j] = verts[polygon[j]];
+ }
+
+ // Generate the polygon color, slightly randomly modified from the settings one.
+ Color random_variation_color;
+ random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1);
+ random_variation_color.a = color.a;
+ Vector<Color> colors;
+ colors.push_back(random_variation_color);
+
+ RenderingServer::get_singleton()->canvas_item_add_polygon(p_canvas_item->get_canvas_item(), vertices, colors);
+ }
+ }
+
+ RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
+}
diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h
new file mode 100644
index 0000000000..781f26cc02
--- /dev/null
+++ b/editor/plugins/tiles/tile_data_editors.h
@@ -0,0 +1,408 @@
+/*************************************************************************/
+/* tile_data_editors.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 TILE_DATA_EDITORS_H
+#define TILE_DATA_EDITORS_H
+
+#include "tile_atlas_view.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_properties.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/control.h"
+#include "scene/gui/label.h"
+#include "scene/resources/tile_set.h"
+
+class TileDataEditor : public VBoxContainer {
+ GDCLASS(TileDataEditor, VBoxContainer);
+
+private:
+ void _call_tile_set_changed();
+
+protected:
+ Ref<TileSet> tile_set;
+ TileData *_get_tile_data(TileMapCell p_cell);
+ virtual void _tile_set_changed(){};
+
+ static void _bind_methods();
+
+public:
+ void set_tile_set(Ref<TileSet> p_tile_set);
+
+ // Input to handle painting.
+ virtual Control *get_toolbar() { return nullptr; };
+ virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){};
+ virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){};
+ virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event){};
+ virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event){};
+
+ // Used to draw the tile data property value over a tile.
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false){};
+};
+
+class DummyObject : public Object {
+ GDCLASS(DummyObject, Object)
+private:
+ Map<String, Variant> properties;
+
+protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+
+public:
+ bool has_dummy_property(StringName p_name);
+ void add_dummy_property(StringName p_name);
+ void remove_dummy_property(StringName p_name);
+ void clear_dummy_properties();
+};
+
+class GenericTilePolygonEditor : public VBoxContainer {
+ GDCLASS(GenericTilePolygonEditor, VBoxContainer);
+
+private:
+ Ref<TileSet> tile_set;
+ LocalVector<Vector<Point2>> polygons;
+ bool multiple_polygon_mode = false;
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ // UI
+ int hovered_polygon_index = -1;
+ int hovered_point_index = -1;
+ int hovered_segment_index = -1;
+ Vector2 hovered_segment_point;
+
+ enum DragType {
+ DRAG_TYPE_NONE,
+ DRAG_TYPE_DRAG_POINT,
+ DRAG_TYPE_CREATE_POINT,
+ DRAG_TYPE_PAN,
+ };
+ DragType drag_type;
+ int drag_polygon_index;
+ int drag_point_index;
+ Vector2 drag_last_pos;
+ PackedVector2Array drag_old_polygon;
+
+ HBoxContainer *toolbar;
+ Ref<ButtonGroup> tools_button_group;
+ Button *button_create;
+ Button *button_edit;
+ Button *button_delete;
+ Button *button_pixel_snap;
+ MenuButton *button_advanced_menu;
+
+ Vector<Point2> in_creation_polygon;
+
+ Panel *panel;
+ Control *base_control;
+ EditorZoomWidget *editor_zoom_widget;
+ Button *button_center_view;
+ Vector2 panning;
+
+ Ref<Texture2D> background_texture;
+ Rect2 background_region;
+ Vector2 background_offset;
+ bool background_h_flip;
+ bool background_v_flip;
+ bool background_transpose;
+ Color background_modulate;
+
+ Color polygon_color = Color(1.0, 0.0, 0.0);
+
+ enum AdvancedMenuOption {
+ RESET_TO_DEFAULT_TILE,
+ CLEAR_TILE,
+ };
+
+ void _base_control_draw();
+ void _zoom_changed();
+ void _advanced_menu_item_pressed(int p_item_pressed);
+ void _center_view();
+ void _base_control_gui_input(Ref<InputEvent> p_event);
+
+ void _snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist);
+ void _snap_to_half_pixel(Point2 &r_point);
+ void _grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index);
+ void _grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point);
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_tile_set(Ref<TileSet> p_tile_set);
+ void set_background(Ref<Texture2D> p_texture, Rect2 p_region = Rect2(), Vector2 p_offset = Vector2(), bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, Color p_modulate = Color(1.0, 1.0, 1.0, 0.0));
+
+ int get_polygon_count();
+ int add_polygon(Vector<Point2> p_polygon, int p_index = -1);
+ void remove_polygon(int p_index);
+ void clear_polygons();
+ void set_polygon(int p_polygon_index, Vector<Point2> p_polygon);
+ Vector<Point2> get_polygon(int p_polygon_index);
+
+ void set_polygons_color(Color p_color);
+ void set_multiple_polygon_mode(bool p_multiple_polygon_mode);
+
+ GenericTilePolygonEditor();
+};
+
+class TileDataDefaultEditor : public TileDataEditor {
+ GDCLASS(TileDataDefaultEditor, TileDataEditor);
+
+private:
+ // Toolbar
+ HBoxContainer *toolbar = memnew(HBoxContainer);
+ Button *picker_button;
+
+ // UI
+ Ref<Texture2D> tile_bool_checked;
+ Ref<Texture2D> tile_bool_unchecked;
+ Label *label;
+
+ EditorProperty *property_editor = nullptr;
+
+ // Painting state.
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_PAINT,
+ DRAG_TYPE_PAINT_RECT,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2 drag_start_pos;
+ Vector2 drag_last_pos;
+ Map<TileMapCell, Variant> drag_modified;
+ Variant drag_painted_value;
+
+ void _property_value_changed(StringName p_property, Variant p_value, StringName p_field);
+
+protected:
+ DummyObject *dummy_object = memnew(DummyObject);
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ StringName type;
+ String property;
+ void _notification(int p_what);
+
+ virtual Variant _get_painted_value();
+ virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile);
+ virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value);
+ virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile);
+ virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value);
+
+public:
+ virtual Control *get_toolbar() override { return toolbar; };
+ virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
+ virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
+ virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
+ virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+
+ void setup_property_editor(Variant::Type p_type, String p_property, String p_label = "", Variant p_default_value = Variant());
+
+ TileDataDefaultEditor();
+ ~TileDataDefaultEditor();
+};
+
+class TileDataTextureOffsetEditor : public TileDataDefaultEditor {
+ GDCLASS(TileDataTextureOffsetEditor, TileDataDefaultEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+};
+
+class TileDataPositionEditor : public TileDataDefaultEditor {
+ GDCLASS(TileDataPositionEditor, TileDataDefaultEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+};
+
+class TileDataYSortEditor : public TileDataDefaultEditor {
+ GDCLASS(TileDataYSortEditor, TileDataDefaultEditor);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+};
+
+class TileDataOcclusionShapeEditor : public TileDataDefaultEditor {
+ GDCLASS(TileDataOcclusionShapeEditor, TileDataDefaultEditor);
+
+private:
+ int occlusion_layer = -1;
+
+ // UI
+ GenericTilePolygonEditor *polygon_editor;
+
+ void _polygon_changed(PackedVector2Array p_polygon);
+
+ virtual Variant _get_painted_value() override;
+ virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
+ virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override;
+ virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
+ virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override;
+
+protected:
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ virtual void _tile_set_changed() override;
+
+ void _notification(int p_what);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+
+ void set_occlusion_layer(int p_occlusion_layer) { occlusion_layer = p_occlusion_layer; }
+
+ TileDataOcclusionShapeEditor();
+};
+
+class TileDataCollisionEditor : public TileDataDefaultEditor {
+ GDCLASS(TileDataCollisionEditor, TileDataDefaultEditor);
+
+ int physics_layer = -1;
+
+ // UI
+ GenericTilePolygonEditor *polygon_editor;
+ DummyObject *dummy_object = memnew(DummyObject);
+ Map<StringName, EditorProperty *> property_editors;
+
+ void _property_value_changed(StringName p_property, Variant p_value, StringName p_field);
+ void _polygons_changed();
+
+ virtual Variant _get_painted_value() override;
+ virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
+ virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override;
+ virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
+ virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override;
+
+protected:
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ virtual void _tile_set_changed() override;
+
+ void _notification(int p_what);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+
+ void set_physics_layer(int p_physics_layer) { physics_layer = p_physics_layer; }
+
+ TileDataCollisionEditor();
+ ~TileDataCollisionEditor();
+};
+
+class TileDataTerrainsEditor : public TileDataEditor {
+ GDCLASS(TileDataTerrainsEditor, TileDataEditor);
+
+private:
+ // Toolbar
+ HBoxContainer *toolbar = memnew(HBoxContainer);
+ Button *picker_button;
+
+ // Painting state.
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_PAINT_TERRAIN_SET,
+ DRAG_TYPE_PAINT_TERRAIN_SET_RECT,
+ DRAG_TYPE_PAINT_TERRAIN_BITS,
+ DRAG_TYPE_PAINT_TERRAIN_BITS_RECT,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2 drag_start_pos;
+ Vector2 drag_last_pos;
+ Map<TileMapCell, Variant> drag_modified;
+ Variant drag_painted_value;
+
+ // UI
+ Label *label;
+ DummyObject *dummy_object = memnew(DummyObject);
+ EditorPropertyEnum *terrain_set_property_editor = nullptr;
+ EditorPropertyEnum *terrain_property_editor = nullptr;
+
+ void _property_value_changed(StringName p_property, Variant p_value, StringName p_field);
+
+ void _update_terrain_selector();
+
+protected:
+ virtual void _tile_set_changed() override;
+
+ void _notification(int p_what);
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+public:
+ virtual Control *get_toolbar() override { return toolbar; };
+ virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
+ virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override;
+ virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
+ virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override;
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+
+ TileDataTerrainsEditor();
+ ~TileDataTerrainsEditor();
+};
+
+class TileDataNavigationEditor : public TileDataDefaultEditor {
+ GDCLASS(TileDataNavigationEditor, TileDataDefaultEditor);
+
+private:
+ int navigation_layer = -1;
+ PackedVector2Array navigation_polygon;
+
+ // UI
+ GenericTilePolygonEditor *polygon_editor;
+
+ void _polygon_changed(PackedVector2Array p_polygon);
+
+ virtual Variant _get_painted_value() override;
+ virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
+ virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override;
+ virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override;
+ virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override;
+
+protected:
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ virtual void _tile_set_changed() override;
+
+ void _notification(int p_what);
+
+public:
+ virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override;
+
+ void set_navigation_layer(int p_navigation_layer) { navigation_layer = p_navigation_layer; }
+
+ TileDataNavigationEditor();
+};
+
+#endif // TILE_DATA_EDITORS_H
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
new file mode 100644
index 0000000000..86bd115ac2
--- /dev/null
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -0,0 +1,3525 @@
+/*************************************************************************/
+/* tile_map_editor.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 "tile_map_editor.h"
+
+#include "tiles_editor_plugin.h"
+
+#include "editor/editor_resource_preview.h"
+#include "editor/editor_scale.h"
+#include "editor/plugins/canvas_item_editor_plugin.h"
+
+#include "scene/gui/center_container.h"
+#include "scene/gui/split_container.h"
+
+#include "core/input/input.h"
+#include "core/math/geometry_2d.h"
+#include "core/os/keyboard.h"
+
+void TileMapEditorTilesPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ select_tool_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons"));
+ paint_tool_button->set_icon(get_theme_icon("Edit", "EditorIcons"));
+ line_tool_button->set_icon(get_theme_icon("CurveLinear", "EditorIcons"));
+ rect_tool_button->set_icon(get_theme_icon("Rectangle", "EditorIcons"));
+ bucket_tool_button->set_icon(get_theme_icon("Bucket", "EditorIcons"));
+
+ picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
+ erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons"));
+
+ toggle_grid_button->set_icon(get_theme_icon("Grid", "EditorIcons"));
+
+ missing_atlas_texture_icon = get_theme_icon("TileSet", "EditorIcons");
+
+ toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"));
+ break;
+ case NOTIFICATION_VISIBILITY_CHANGED:
+ _stop_dragging();
+ break;
+ case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED:
+ toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"));
+ break;
+ }
+}
+
+void TileMapEditorTilesPlugin::tile_set_changed() {
+ _update_fix_selected_and_hovered();
+ _update_tile_set_sources_list();
+ _update_bottom_panel();
+}
+
+void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) {
+ scatter_spinbox->set_editable(p_pressed);
+}
+
+void TileMapEditorTilesPlugin::_on_scattering_spinbox_changed(double p_value) {
+ scattering = p_value;
+}
+
+void TileMapEditorTilesPlugin::_on_grid_toggled(bool p_pressed) {
+ EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed);
+}
+
+void TileMapEditorTilesPlugin::_update_toolbar() {
+ // Stop draggig if needed.
+ _stop_dragging();
+
+ // Hide all settings.
+ for (int i = 0; i < tools_settings->get_child_count(); i++) {
+ Object::cast_to<CanvasItem>(tools_settings->get_child(i))->hide();
+ }
+
+ // Show only the correct settings.
+ if (tool_buttons_group->get_pressed_button() == select_tool_button) {
+ } else if (tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ } else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ tools_settings_vsep_2->show();
+ bucket_continuous_checkbox->show();
+ random_tile_checkbox->show();
+ scatter_label->show();
+ scatter_spinbox->show();
+ }
+}
+
+Control *TileMapEditorTilesPlugin::get_toolbar() const {
+ return toolbar;
+}
+
+void TileMapEditorTilesPlugin::_update_tile_set_sources_list() {
+ // Update the sources.
+ int old_current = sources_list->get_current();
+ sources_list->clear();
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ for (int i = 0; i < tile_set->get_source_count(); i++) {
+ int source_id = tile_set->get_source_id(i);
+
+ TileSetSource *source = *tile_set->get_source(source_id);
+
+ Ref<Texture2D> texture;
+ String item_text;
+
+ // Atlas source.
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ texture = atlas_source->get_texture();
+ if (texture.is_valid()) {
+ item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id);
+ } else {
+ item_text = vformat("No Texture Atlas Source (id:%d)", source_id);
+ }
+ }
+
+ // Scene collection source.
+ TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+ if (scene_collection_source) {
+ texture = get_theme_icon("PackedScene", "EditorIcons");
+ item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id);
+ }
+
+ // Use default if not valid.
+ if (item_text.is_empty()) {
+ item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id);
+ }
+ if (!texture.is_valid()) {
+ texture = missing_atlas_texture_icon;
+ }
+
+ sources_list->add_item(item_text, texture);
+ sources_list->set_item_metadata(i, source_id);
+ }
+
+ if (sources_list->get_item_count() > 0) {
+ if (old_current > 0) {
+ // Keep the current selected item if needed.
+ sources_list->set_current(CLAMP(old_current, 0, sources_list->get_item_count() - 1));
+ } else {
+ sources_list->set_current(0);
+ }
+ sources_list->emit_signal("item_selected", sources_list->get_current());
+ }
+
+ // Synchronize
+ TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current());
+}
+
+void TileMapEditorTilesPlugin::_update_bottom_panel() {
+ // Update the atlas display.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int source_index = sources_list->get_current();
+ if (source_index >= 0 && source_index < sources_list->get_item_count()) {
+ atlas_sources_split_container->show();
+ missing_source_label->hide();
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ TileSetSource *source = *tile_set->get_source(source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+
+ if (atlas_source) {
+ tile_atlas_view->show();
+ scene_tiles_list->hide();
+ invalid_source_label->hide();
+ _update_atlas_view();
+ } else if (scenes_collection_source) {
+ tile_atlas_view->hide();
+ scene_tiles_list->show();
+ invalid_source_label->hide();
+ _update_scenes_collection_view();
+ } else {
+ tile_atlas_view->hide();
+ scene_tiles_list->hide();
+ invalid_source_label->show();
+ }
+ } else {
+ atlas_sources_split_container->hide();
+ missing_source_label->show();
+
+ tile_atlas_view->hide();
+ scene_tiles_list->hide();
+ invalid_source_label->hide();
+ }
+}
+
+void TileMapEditorTilesPlugin::_update_atlas_view() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(sources_list->get_current());
+ TileSetSource *source = *tile_set->get_source(source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ ERR_FAIL_COND(!atlas_source);
+
+ tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id);
+ TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view);
+ tile_atlas_control->update();
+}
+
+void TileMapEditorTilesPlugin::_update_scenes_collection_view() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(sources_list->get_current());
+ TileSetSource *source = *tile_set->get_source(source_id);
+ TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+ ERR_FAIL_COND(!scenes_collection_source);
+
+ // Clear the list.
+ scene_tiles_list->clear();
+
+ // Rebuild the list.
+ for (int i = 0; i < scenes_collection_source->get_scene_tiles_count(); i++) {
+ int scene_id = scenes_collection_source->get_scene_tile_id(i);
+
+ Ref<PackedScene> scene = scenes_collection_source->get_scene_tile_scene(scene_id);
+
+ int item_index = 0;
+ if (scene.is_valid()) {
+ item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id));
+ Variant udata = i;
+ EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata);
+ } else {
+ item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons"));
+ }
+ scene_tiles_list->set_item_metadata(item_index, scene_id);
+
+ // Check if in selection.
+ if (tile_set_selection.has(TileMapCell(source_id, Vector2i(), scene_id))) {
+ scene_tiles_list->select(item_index, false);
+ }
+ }
+
+ // Icon size update.
+ int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE;
+ scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size));
+}
+
+void TileMapEditorTilesPlugin::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) {
+ int index = p_ud;
+
+ if (index >= 0 && index < scene_tiles_list->get_item_count()) {
+ scene_tiles_list->set_item_icon(index, p_preview);
+ }
+}
+
+void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_selected) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ // Add or remove the Tile form the selection.
+ int scene_id = scene_tiles_list->get_item_metadata(p_index);
+ int source_id = sources_list->get_item_metadata(sources_list->get_current());
+ TileSetSource *source = *tile_set->get_source(source_id);
+ TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+ ERR_FAIL_COND(!scenes_collection_source);
+
+ TileMapCell selected = TileMapCell(source_id, Vector2i(), scene_id);
+
+ // Clear the selection if shift is not pressed.
+ if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
+ tile_set_selection.clear();
+ }
+
+ if (p_selected) {
+ tile_set_selection.insert(selected);
+ } else {
+ if (tile_set_selection.has(selected)) {
+ tile_set_selection.erase(selected);
+ }
+ }
+
+ _update_selection_pattern_from_tileset_selection();
+}
+
+void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() {
+ scene_tiles_list->deselect_all();
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ _update_selection_pattern_from_tileset_selection();
+}
+
+bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
+ if (!is_visible_in_tree()) {
+ // If the bottom editor is not visible, we ignore inputs.
+ return false;
+ }
+
+ if (CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
+ return false;
+ }
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return false;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return false;
+ }
+
+ // Shortcuts
+ if (ED_IS_SHORTCUT("tiles_editor/cut", p_event) || ED_IS_SHORTCUT("tiles_editor/copy", p_event)) {
+ // Fill in the clipboard.
+ if (!tile_map_selection.is_empty()) {
+ memdelete(tile_map_clipboard);
+ TypedArray<Vector2i> coords_array;
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ coords_array.push_back(E->get());
+ }
+ tile_map_clipboard = tile_map->get_pattern(coords_array);
+ }
+
+ if (ED_IS_SHORTCUT("tiles_editor/cut", p_event)) {
+ // Delete selected tiles.
+ if (!tile_map_selection.is_empty()) {
+ undo_redo->create_action(TTR("Delete tiles"));
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get()));
+ }
+ undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ tile_map_selection.clear();
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action();
+ }
+ }
+
+ return true;
+ }
+ if (ED_IS_SHORTCUT("tiles_editor/paste", p_event)) {
+ if (drag_type == DRAG_TYPE_NONE) {
+ drag_type = DRAG_TYPE_CLIPBOARD_PASTE;
+ }
+ CanvasItemEditor::get_singleton()->update_viewport();
+ return true;
+ }
+ if (ED_IS_SHORTCUT("tiles_editor/cancel", p_event)) {
+ if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) {
+ drag_type = DRAG_TYPE_NONE;
+ CanvasItemEditor::get_singleton()->update_viewport();
+ return true;
+ }
+ }
+ if (ED_IS_SHORTCUT("tiles_editor/delete", p_event)) {
+ // Delete selected tiles.
+ if (!tile_map_selection.is_empty()) {
+ undo_redo->create_action(TTR("Delete tiles"));
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get()));
+ }
+ undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ tile_map_selection.clear();
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action();
+ }
+ return true;
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ has_mouse = true;
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mm->get_position());
+
+ switch (drag_type) {
+ case DRAG_TYPE_PAINT: {
+ Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos);
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)));
+ }
+ tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ } break;
+ case DRAG_TYPE_BUCKET: {
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos));
+ for (int i = 0; i < line.size(); i++) {
+ if (!drag_modified.has(line[i])) {
+ Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed());
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)));
+ }
+ tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ drag_last_mouse_pos = mpos;
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ has_mouse = true;
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mb->get_position());
+
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Pressed
+ if (tool_buttons_group->get_pressed_button() == select_tool_button) {
+ drag_start_mouse_pos = mpos;
+ if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) {
+ // Move the selection
+ drag_type = DRAG_TYPE_MOVE;
+ drag_modified.clear();
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ Vector2i coords = E->get();
+ drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)));
+ tile_map->set_cell(coords, -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ }
+ } else {
+ // Select tiles
+ drag_type = DRAG_TYPE_SELECT;
+ }
+ } else {
+ // Check if we are picking a tile.
+ if (picker_button->is_pressed()) {
+ drag_type = DRAG_TYPE_PICK;
+ drag_start_mouse_pos = mpos;
+ } else {
+ // Paint otherwise.
+ if (tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ drag_type = DRAG_TYPE_PAINT;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos);
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)));
+ }
+ tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ } else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
+ drag_type = DRAG_TYPE_LINE;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) {
+ drag_type = DRAG_TYPE_RECT;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) {
+ drag_type = DRAG_TYPE_BUCKET;
+ drag_start_mouse_pos = mpos;
+ drag_modified.clear();
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos));
+ for (int i = 0; i < line.size(); i++) {
+ if (!drag_modified.has(line[i])) {
+ Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed());
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ Vector2i coords = E->key();
+ if (!drag_modified.has(coords)) {
+ drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)));
+ }
+ tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ // Released
+ _stop_dragging();
+ }
+
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+ drag_last_mouse_pos = mpos;
+ }
+
+ return false;
+}
+
+void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ if (!tile_map->is_visible_in_tree()) {
+ return;
+ }
+
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2i tile_shape_size = tile_set->get_tile_size();
+
+ // Draw the selection.
+ if (is_visible_in_tree() && tool_buttons_group->get_pressed_button() == select_tool_button) {
+ // In select mode, we only draw the current selection if we are modifying it (pressing control or shift).
+ if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) {
+ // Do nothing
+ } else {
+ 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);
+ tile_map->draw_cells_outline(p_overlay, tile_map_selection, selection_color, xform);
+ }
+ }
+
+ // Handle the preview of the tiles to be placed.
+ if (is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered.
+ Map<Vector2i, TileMapCell> preview;
+ Rect2i drawn_grid_rect;
+
+ if (drag_type == DRAG_TYPE_PICK) {
+ // Draw the area being picvked.
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ rect.size += Vector2i(1, 1);
+ for (int x = rect.position.x; x < rect.get_end().x; x++) {
+ for (int y = rect.position.y; y < rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ 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);
+ }
+ }
+ }
+ } else if (drag_type == DRAG_TYPE_SELECT) {
+ // Draw the area being selected.
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ rect.size += Vector2i(1, 1);
+ Set<Vector2i> to_draw;
+ for (int x = rect.position.x; x < rect.get_end().x; x++) {
+ for (int y = rect.position.y; y < rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ to_draw.insert(coords);
+ }
+ }
+ }
+ tile_map->draw_cells_outline(p_overlay, to_draw, Color(1.0, 1.0, 1.0), xform);
+ } else if (drag_type == DRAG_TYPE_MOVE) {
+ // Preview when moving.
+ Vector2i top_left;
+ if (!tile_map_selection.is_empty()) {
+ top_left = tile_map_selection.front()->get();
+ }
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ top_left = top_left.min(E->get());
+ }
+ Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
+ offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
+
+ TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern);
+ preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
+ }
+ } else if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) {
+ // Preview when pasting.
+ Vector2 mouse_offset = (Vector2(tile_map_clipboard->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ TypedArray<Vector2i> clipboard_used_cells = tile_map_clipboard->get_used_cells();
+ for (int i = 0; i < clipboard_used_cells.size(); i++) {
+ Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard);
+ preview[coords] = TileMapCell(tile_map_clipboard->get_cell_source_id(clipboard_used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(clipboard_used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(clipboard_used_cells[i]));
+ }
+ } else if (!picker_button->is_pressed()) {
+ bool expand_grid = false;
+ if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) {
+ // Preview for a single pattern.
+ preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos);
+ expand_grid = true;
+ } else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
+ if (drag_type == DRAG_TYPE_NONE) {
+ // Preview for a single pattern.
+ preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos);
+ expand_grid = true;
+ } else if (drag_type == DRAG_TYPE_LINE) {
+ // Preview for a line pattern.
+ preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos);
+ expand_grid = true;
+ }
+ } else if (tool_buttons_group->get_pressed_button() == rect_tool_button && drag_type == DRAG_TYPE_RECT) {
+ // Preview for a line pattern.
+ preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos));
+ expand_grid = true;
+ } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) {
+ // Preview for a line pattern.
+ preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed());
+ }
+
+ // Expand the grid if needed
+ if (expand_grid && !preview.is_empty()) {
+ drawn_grid_rect = Rect2i(preview.front()->key(), Vector2i(1, 1));
+ for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) {
+ drawn_grid_rect.expand_to(E->key());
+ }
+ }
+ }
+
+ if (!preview.is_empty()) {
+ const int fading = 5;
+
+ // Draw the lines of the grid behind the preview.
+ bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid");
+ if (display_grid) {
+ Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color");
+ if (drawn_grid_rect.size.x > 0 && drawn_grid_rect.size.y > 0) {
+ drawn_grid_rect = drawn_grid_rect.grow(fading);
+ for (int x = drawn_grid_rect.position.x; x < (drawn_grid_rect.position.x + drawn_grid_rect.size.x); x++) {
+ for (int y = drawn_grid_rect.position.y; y < (drawn_grid_rect.position.y + drawn_grid_rect.size.y); y++) {
+ Vector2i pos_in_rect = Vector2i(x, y) - drawn_grid_rect.position;
+
+ // Fade out the border of the grid.
+ float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f);
+ float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f);
+ float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f);
+ 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));
+ Color color = grid_color;
+ color.a = color.a * opacity;
+ tile_set->draw_tile_shape(p_overlay, cell_region, color, false);
+ }
+ }
+ }
+ }
+
+ // 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));
+ 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);
+ } else {
+ if (tile_set->has_source(E->get().source_id)) {
+ TileSetSource *source = *tile_set->get_source(E->get().source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ // Get tile data.
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+
+ // Compute the offset
+ Rect2i source_rect = atlas_source->get_tile_texture_region(E->get().get_atlas_coords());
+ Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E->get().get_atlas_coords(), E->get().alternative_tile);
+
+ // Compute the destination rectangle in the CanvasItem.
+ Rect2 dest_rect;
+ dest_rect.size = source_rect.size;
+
+ bool transpose = tile_data->get_transpose();
+ if (transpose) {
+ dest_rect.position = (tile_map->map_to_world(E->key()) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset);
+ } else {
+ dest_rect.position = (tile_map->map_to_world(E->key()) - dest_rect.size / 2 - tile_offset);
+ }
+
+ dest_rect = xform.xform(dest_rect);
+
+ if (tile_data->get_flip_h()) {
+ dest_rect.size.x = -dest_rect.size.x;
+ }
+
+ if (tile_data->get_flip_v()) {
+ dest_rect.size.y = -dest_rect.size.y;
+ }
+
+ // Get the tile modulation.
+ Color modulate = tile_data->get_modulate();
+ Color self_modulate = tile_map->get_self_modulate();
+ modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g, modulate.b * self_modulate.b, modulate.a * self_modulate.a);
+
+ // 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);
+ }
+ } else {
+ tile_set->draw_tile_shape(p_overlay, cell_region, Color(0.0, 0.0, 0.0, 0.5), true);
+ }
+ }
+ }
+ }
+ }
+}
+
+void TileMapEditorTilesPlugin::_mouse_exited_viewport() {
+ has_mouse = false;
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_pattern) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return TileMapCell();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return TileMapCell();
+ }
+
+ TypedArray<Vector2i> used_cells = p_pattern->get_used_cells();
+ double sum = 0.0;
+ for (int i = 0; i < used_cells.size(); i++) {
+ int source_id = p_pattern->get_cell_source_id(used_cells[i]);
+ Vector2i atlas_coords = p_pattern->get_cell_atlas_coords(used_cells[i]);
+ int alternative_tile = p_pattern->get_cell_alternative_tile(used_cells[i]);
+
+ TileSetSource *source = *tile_set->get_source(source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile));
+ ERR_FAIL_COND_V(!tile_data, TileMapCell());
+ sum += tile_data->get_probability();
+ } else {
+ sum += 1.0;
+ }
+ }
+
+ double empty_probability = sum * scattering;
+ double current = 0.0;
+ double rand = Math::random(0.0, sum + empty_probability);
+ for (int i = 0; i < used_cells.size(); i++) {
+ int source_id = p_pattern->get_cell_source_id(used_cells[i]);
+ Vector2i atlas_coords = p_pattern->get_cell_atlas_coords(used_cells[i]);
+ int alternative_tile = p_pattern->get_cell_alternative_tile(used_cells[i]);
+
+ TileSetSource *source = *tile_set->get_source(source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ current += Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile))->get_probability();
+ } else {
+ current += 1.0;
+ }
+
+ if (current >= rand) {
+ return TileMapCell(source_id, atlas_coords, alternative_tile);
+ }
+ }
+ return TileMapCell();
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ // Get or create the pattern.
+ TileMapPattern erase_pattern;
+ erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
+
+ Map<Vector2i, TileMapCell> output;
+ if (!pattern->is_empty()) {
+ // Paint the tiles on the tile map.
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(p_from_mouse_pos), tile_map->world_to_map(p_to_mouse_pos));
+ for (int i = 0; i < line.size(); i++) {
+ output.insert(line[i], _pick_random_tile(pattern));
+ }
+ } else {
+ // Paint the pattern.
+ // If we paint several tiles, we virtually move the mouse as if it was in the center of the "brush"
+ Vector2 mouse_offset = (Vector2(pattern->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ Vector2i last_hovered_cell = tile_map->world_to_map(p_from_mouse_pos - mouse_offset);
+ Vector2i new_hovered_cell = tile_map->world_to_map(p_to_mouse_pos - mouse_offset);
+ Vector2i drag_start_cell = tile_map->world_to_map(p_start_drag_mouse_pos - mouse_offset);
+
+ TypedArray<Vector2i> used_cells = pattern->get_used_cells();
+ Vector2i offset = Vector2i(Math::posmod(drag_start_cell.x, pattern->get_size().x), Math::posmod(drag_start_cell.y, pattern->get_size().y)); // Note: no posmodv for Vector2i for now. Meh.s
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, (last_hovered_cell - offset) / pattern->get_size(), (new_hovered_cell - offset) / pattern->get_size());
+ for (int i = 0; i < line.size(); i++) {
+ Vector2i top_left = line[i] * pattern->get_size() + offset;
+ for (int j = 0; j < used_cells.size(); j++) {
+ Vector2i coords = tile_map->map_pattern(top_left, used_cells[j], pattern);
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(used_cells[j]), pattern->get_cell_atlas_coords(used_cells[j]), pattern->get_cell_alternative_tile(used_cells[j])));
+ }
+ }
+ }
+ }
+ return output;
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ // Create the rect to draw.
+ Rect2i rect = Rect2i(p_start_cell, p_end_cell - p_start_cell).abs();
+ rect.size += Vector2i(1, 1);
+
+ // Get or create the pattern.
+ TileMapPattern erase_pattern;
+ erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
+
+ // Compute the offset to align things to the bottom or right.
+ bool aligned_right = p_end_cell.x < p_start_cell.x;
+ bool valigned_bottom = p_end_cell.y < p_start_cell.y;
+ Vector2i offset = Vector2i(aligned_right ? -(pattern->get_size().x - (rect.get_size().x % pattern->get_size().x)) : 0, valigned_bottom ? -(pattern->get_size().y - (rect.get_size().y % pattern->get_size().y)) : 0);
+
+ Map<Vector2i, TileMapCell> output;
+ if (!pattern->is_empty()) {
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ for (int x = 0; x < rect.size.x; x++) {
+ for (int y = 0; y < rect.size.y; y++) {
+ Vector2i coords = rect.position + Vector2i(x, y);
+ output.insert(coords, _pick_random_tile(pattern));
+ }
+ }
+ } else {
+ // Paint the pattern.
+ TypedArray<Vector2i> used_cells = pattern->get_used_cells();
+ for (int x = 0; x <= rect.size.x / pattern->get_size().x; x++) {
+ for (int y = 0; y <= rect.size.y / pattern->get_size().y; y++) {
+ Vector2i pattern_coords = rect.position + Vector2i(x, y) * pattern->get_size() + offset;
+ for (int j = 0; j < used_cells.size(); j++) {
+ Vector2i coords = pattern_coords + used_cells[j];
+ if (rect.has_point(coords)) {
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(used_cells[j]), pattern->get_cell_atlas_coords(used_cells[j]), pattern->get_cell_alternative_tile(used_cells[j])));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return output;
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ // Get or create the pattern.
+ TileMapPattern erase_pattern;
+ erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
+
+ Map<Vector2i, TileMapCell> output;
+ if (!pattern->is_empty()) {
+ TileMapCell source = tile_map->get_cell(p_coords);
+
+ // If we are filling empty tiles, compute the tilemap boundaries.
+ Rect2i boundaries;
+ if (source.source_id == -1) {
+ boundaries = tile_map->get_used_rect();
+ }
+
+ if (p_contiguous) {
+ // Replace continuous tiles like the source.
+ Set<Vector2i> already_checked;
+ List<Vector2i> to_check;
+ to_check.push_back(p_coords);
+ while (!to_check.is_empty()) {
+ Vector2i coords = to_check.back()->get();
+ to_check.pop_back();
+ if (!already_checked.has(coords)) {
+ if (source.source_id == tile_map->get_cell_source_id(coords) &&
+ source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) &&
+ source.alternative_tile == tile_map->get_cell_alternative_tile(coords) &&
+ (source.source_id != -1 || boundaries.has_point(coords))) {
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ output.insert(coords, _pick_random_tile(pattern));
+ } else {
+ // Paint the pattern.
+ Vector2i pattern_coords = (coords - p_coords) % pattern->get_size(); // Note: it would be good to have posmodv for Vector2i.
+ pattern_coords.x = pattern_coords.x < 0 ? pattern_coords.x + pattern->get_size().x : pattern_coords.x;
+ pattern_coords.y = pattern_coords.y < 0 ? pattern_coords.y + pattern->get_size().y : pattern_coords.y;
+ if (pattern->has_cell(pattern_coords)) {
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(pattern_coords), pattern->get_cell_atlas_coords(pattern_coords), pattern->get_cell_alternative_tile(pattern_coords)));
+ } else {
+ output.insert(coords, TileMapCell());
+ }
+ }
+
+ // Get surrounding tiles (handles different tile shapes).
+ TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords);
+ for (int i = 0; i < around.size(); i++) {
+ to_check.push_back(around[i]);
+ }
+ }
+ already_checked.insert(coords);
+ }
+ }
+ } else {
+ // Replace all tiles like the source.
+ TypedArray<Vector2i> to_check;
+ if (source.source_id == -1) {
+ Rect2i rect = tile_map->get_used_rect();
+ if (rect.size.x <= 0 || rect.size.y <= 0) {
+ rect = Rect2i(p_coords, Vector2i(1, 1));
+ }
+ for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) {
+ for (int y = boundaries.position.y; y < boundaries.get_end().y; y++) {
+ to_check.append(Vector2i(x, y));
+ }
+ }
+ } else {
+ to_check = tile_map->get_used_cells();
+ }
+ for (int i = 0; i < to_check.size(); i++) {
+ Vector2i coords = to_check[i];
+ if (source.source_id == tile_map->get_cell_source_id(coords) &&
+ source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) &&
+ source.alternative_tile == tile_map->get_cell_alternative_tile(coords) &&
+ (source.source_id != -1 || boundaries.has_point(coords))) {
+ if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
+ // Paint a random tile.
+ output.insert(coords, _pick_random_tile(pattern));
+ } else {
+ // Paint the pattern.
+ Vector2i pattern_coords = (coords - p_coords) % pattern->get_size(); // Note: it would be good to have posmodv for Vector2i.
+ pattern_coords.x = pattern_coords.x < 0 ? pattern_coords.x + pattern->get_size().x : pattern_coords.x;
+ pattern_coords.y = pattern_coords.y < 0 ? pattern_coords.y + pattern->get_size().y : pattern_coords.y;
+ if (pattern->has_cell(pattern_coords)) {
+ output.insert(coords, TileMapCell(pattern->get_cell_source_id(pattern_coords), pattern->get_cell_atlas_coords(pattern_coords), pattern->get_cell_alternative_tile(pattern_coords)));
+ } else {
+ output.insert(coords, TileMapCell());
+ }
+ }
+ }
+ }
+ }
+ }
+ return output;
+}
+
+void TileMapEditorTilesPlugin::_stop_dragging() {
+ if (drag_type == DRAG_TYPE_NONE) {
+ return;
+ }
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position());
+
+ switch (drag_type) {
+ case DRAG_TYPE_SELECT: {
+ undo_redo->create_action(TTR("Change selection"));
+ undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+
+ if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT) && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
+ tile_map_selection.clear();
+ }
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ for (int x = rect.position.x; x <= rect.get_end().x; x++) {
+ for (int y = rect.position.y; y <= rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
+ if (tile_map_selection.has(coords)) {
+ tile_map_selection.erase(coords);
+ }
+ } else {
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ tile_map_selection.insert(coords);
+ }
+ }
+ }
+ }
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action(false);
+
+ _update_selection_pattern_from_tilemap_selection();
+ _update_tileset_selection_from_selection_pattern();
+ } break;
+ case DRAG_TYPE_MOVE: {
+ Vector2i top_left;
+ if (!tile_map_selection.is_empty()) {
+ top_left = tile_map_selection.front()->get();
+ }
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ top_left = top_left.min(E->get());
+ }
+
+ Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left);
+ offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset);
+
+ TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells();
+
+ Vector2i coords;
+ Map<Vector2i, TileMapCell> cells_undo;
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords));
+ }
+
+ Map<Vector2i, TileMapCell> cells_do;
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_do[coords] = TileMapCell();
+ }
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i]));
+ }
+ undo_redo->create_action(TTR("Move tiles"));
+ // Move the tiles.
+ for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) {
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+
+ // Update the selection.
+ undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ tile_map_selection.clear();
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
+ tile_map_selection.insert(coords);
+ }
+ undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection());
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_PICK: {
+ Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs();
+ rect.size += Vector2i(1, 1);
+ memdelete(selection_pattern);
+ TypedArray<Vector2i> coords_array;
+ for (int x = rect.position.x; x < rect.get_end().x; x++) {
+ for (int y = rect.position.y; y < rect.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_map->get_cell_source_id(coords) != -1) {
+ coords_array.push_back(coords);
+ }
+ }
+ }
+ selection_pattern = tile_map->get_pattern(coords_array);
+ if (!selection_pattern->is_empty()) {
+ _update_tileset_selection_from_selection_pattern();
+ } else {
+ _update_selection_pattern_from_tileset_selection();
+ }
+ picker_button->set_pressed(false);
+ } break;
+ case DRAG_TYPE_PAINT: {
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ undo_redo->commit_action(false);
+ } break;
+ case DRAG_TYPE_LINE: {
+ Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos);
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_RECT: {
+ Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos));
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_BUCKET: {
+ undo_redo->create_action(TTR("Paint tiles"));
+ for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
+ if (!erase_button->is_pressed() && E->get().source_id == -1) {
+ continue;
+ }
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ undo_redo->commit_action(false);
+ } break;
+ case DRAG_TYPE_CLIPBOARD_PASTE: {
+ Vector2 mouse_offset = (Vector2(tile_map_clipboard->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ undo_redo->create_action(TTR("Paste tiles"));
+ TypedArray<Vector2i> used_cells = tile_map_clipboard->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(mpos - mouse_offset), used_cells[i], tile_map_clipboard);
+ undo_redo->add_do_method(tile_map, "set_cell", coords, tile_map_clipboard->get_cell_source_id(used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(used_cells[i]));
+ undo_redo->add_undo_method(tile_map, "set_cell", coords, tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords));
+ }
+ undo_redo->commit_action();
+ } break;
+ default:
+ break;
+ }
+ drag_type = DRAG_TYPE_NONE;
+}
+
+void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ hovered_tile.source_id = -1;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ hovered_tile.source_id = -1;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ return;
+ }
+
+ int source_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ hovered_tile.source_id = -1;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+
+ // Clear hovered if needed.
+ if (source_id != hovered_tile.source_id ||
+ !tile_set->has_source(hovered_tile.source_id) ||
+ !tile_set->get_source(hovered_tile.source_id)->has_tile(hovered_tile.get_atlas_coords()) ||
+ !tile_set->get_source(hovered_tile.source_id)->has_alternative_tile(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)) {
+ hovered_tile.source_id = -1;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ }
+
+ // Selection if needed.
+ for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
+ const TileMapCell *selected = &(E->get());
+ if (!tile_set->has_source(selected->source_id) ||
+ !tile_set->get_source(selected->source_id)->has_tile(selected->get_atlas_coords()) ||
+ !tile_set->get_source(selected->source_id)->has_alternative_tile(selected->get_atlas_coords(), selected->alternative_tile)) {
+ tile_set_selection.erase(E);
+ }
+ }
+
+ if (!tile_map_selection.is_empty()) {
+ _update_selection_pattern_from_tilemap_selection();
+ } else {
+ _update_selection_pattern_from_tileset_selection();
+ }
+}
+
+void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ memdelete(selection_pattern);
+
+ TypedArray<Vector2i> coords_array;
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ coords_array.push_back(E->get());
+ }
+ selection_pattern = tile_map->get_pattern(coords_array);
+}
+
+void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ // Clear the tilemap selection.
+ tile_map_selection.clear();
+
+ // Clear the selected pattern.
+ selection_pattern->clear();
+
+ // Group per source.
+ Map<int, List<const TileMapCell *>> per_source;
+ for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
+ per_source[E->get().source_id].push_back(&(E->get()));
+ }
+
+ int vertical_offset = 0;
+ for (Map<int, List<const TileMapCell *>>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) {
+ // Per source.
+ List<const TileMapCell *> unorganized;
+ Rect2i encompassing_rect_coords;
+ Map<Vector2i, const TileMapCell *> organized_pattern;
+
+ TileSetSource *source = *tile_set->get_source(E_source->key());
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ // Organize using coordinates.
+ for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) {
+ const TileMapCell *current = E_cell->get();
+ if (current->alternative_tile == 0) {
+ organized_pattern[current->get_atlas_coords()] = current;
+ } else {
+ unorganized.push_back(current);
+ }
+ }
+
+ // Compute the encompassing rect for the organized pattern.
+ Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front();
+ if (E_cell) {
+ encompassing_rect_coords = Rect2i(E_cell->key(), Vector2i(1, 1));
+ for (; E_cell; E_cell = E_cell->next()) {
+ encompassing_rect_coords.expand_to(E_cell->key() + Vector2i(1, 1));
+ encompassing_rect_coords.expand_to(E_cell->key());
+ }
+ }
+ } else {
+ // Add everything unorganized.
+ for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) {
+ unorganized.push_back(E_cell->get());
+ }
+ }
+
+ // Now add everything to the output pattern.
+ for (Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); E_cell; E_cell = E_cell->next()) {
+ selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile);
+ }
+ Vector2i organized_size = selection_pattern->get_size();
+ int unorganized_index = 0;
+ for (List<const TileMapCell *>::Element *E_cell = unorganized.front(); E_cell; E_cell = E_cell->next()) {
+ selection_pattern->set_cell(Vector2(organized_size.x + unorganized_index, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile);
+ unorganized_index++;
+ }
+ vertical_offset += MAX(organized_size.y, 1);
+ }
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern() {
+ tile_set_selection.clear();
+ TypedArray<Vector2i> used_cells = selection_pattern->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = used_cells[i];
+ if (selection_pattern->get_cell_source_id(coords) != -1) {
+ tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords)));
+ }
+ }
+ _update_bottom_panel();
+}
+
+void TileMapEditorTilesPlugin::_tile_atlas_control_draw() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int source_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // Draw the selection.
+ 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);
+ for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
+ if (E->get().source_id == source_id && E->get().alternative_tile == 0) {
+ tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords()), selection_color, false);
+ }
+ }
+
+ // Draw the hovered tile.
+ if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) {
+ tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords()), Color(1.0, 1.0, 1.0), false);
+ }
+
+ // Draw the selection rect.
+ if (tile_set_dragging_selection) {
+ Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos);
+ Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+
+ Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs();
+ region.size += Vector2i(1, 1);
+
+ Set<Vector2i> to_draw;
+ for (int x = region.position.x; x < region.get_end().x; x++) {
+ for (int y = region.position.y; y < region.get_end().y; y++) {
+ Vector2i tile = atlas->get_tile_at_coords(Vector2i(x, y));
+ if (tile != TileSetSource::INVALID_ATLAS_COORDS) {
+ to_draw.insert(tile);
+ }
+ }
+ }
+ Color selection_rect_color = selection_color.lightened(0.2);
+ for (Set<Vector2i>::Element *E = to_draw.front(); E; E = E->next()) {
+ tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get()), selection_rect_color, false);
+ }
+ }
+}
+
+void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() {
+ hovered_tile.source_id = -1;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ tile_set_dragging_selection = false;
+ tile_atlas_control->update();
+}
+
+void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int source_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // Update the hovered tile
+ hovered_tile.source_id = source_id;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ coords = atlas->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ hovered_tile.set_atlas_coords(coords);
+ hovered_tile.alternative_tile = 0;
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ tile_atlas_control->update();
+ alternative_tiles_control->update();
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) { // Pressed
+ tile_set_dragging_selection = true;
+ tile_set_drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position();
+ if (!mb->is_shift_pressed()) {
+ tile_set_selection.clear();
+ }
+
+ if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) {
+ if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0))) {
+ tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0));
+ } else {
+ tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0));
+ }
+ }
+ _update_selection_pattern_from_tileset_selection();
+ } else { // Released
+ if (tile_set_dragging_selection) {
+ if (!mb->is_shift_pressed()) {
+ tile_set_selection.clear();
+ }
+ // Compute the covered area.
+ Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos);
+ Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ if (start_tile != TileSetSource::INVALID_ATLAS_COORDS && end_tile != TileSetSource::INVALID_ATLAS_COORDS) {
+ Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs();
+ region.size += Vector2i(1, 1);
+
+ // To update the selection, we copy the selected/not selected status of the tiles we drag from.
+ Vector2i start_coords = atlas->get_tile_at_coords(start_tile);
+ if (mb->is_shift_pressed() && start_coords != TileSetSource::INVALID_ATLAS_COORDS && !tile_set_selection.has(TileMapCell(source_id, start_coords, 0))) {
+ // Remove from the selection.
+ for (int x = region.position.x; x < region.get_end().x; x++) {
+ for (int y = region.position.y; y < region.get_end().y; y++) {
+ Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y));
+ if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_selection.has(TileMapCell(source_id, tile_coords, 0))) {
+ tile_set_selection.erase(TileMapCell(source_id, tile_coords, 0));
+ }
+ }
+ }
+ } else {
+ // Insert in the selection.
+ for (int x = region.position.x; x < region.get_end().x; x++) {
+ for (int y = region.position.y; y < region.get_end().y; y++) {
+ Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y));
+ if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ tile_set_selection.insert(TileMapCell(source_id, tile_coords, 0));
+ }
+ }
+ }
+ }
+ }
+ _update_selection_pattern_from_tileset_selection();
+ }
+ tile_set_dragging_selection = false;
+ }
+ tile_atlas_control->update();
+ }
+}
+
+void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int source_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // Draw the selection.
+ for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) {
+ if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && E->get().alternative_tile > 0) {
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().get_atlas_coords(), E->get().alternative_tile);
+ if (rect != Rect2i()) {
+ alternative_tiles_control->draw_rect(rect, Color(0.2, 0.2, 1.0), false);
+ }
+ }
+ }
+
+ // Draw hovered tile.
+ if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) {
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile);
+ if (rect != Rect2i()) {
+ alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false);
+ }
+ }
+}
+
+void TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited() {
+ hovered_tile.source_id = -1;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ tile_set_dragging_selection = false;
+ alternative_tiles_control->update();
+}
+
+void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ int source_index = sources_list->get_current();
+ if (source_index < 0 || source_index >= sources_list->get_item_count()) {
+ return;
+ }
+
+ int source_id = sources_list->get_item_metadata(source_index);
+ if (!tile_set->has_source(source_id)) {
+ return;
+ }
+
+ TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ if (!atlas) {
+ return;
+ }
+
+ // Update the hovered tile
+ hovered_tile.source_id = source_id;
+ hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ Vector3i alternative_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position());
+ Vector2i coords = Vector2i(alternative_coords.x, alternative_coords.y);
+ int alternative = alternative_coords.z;
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetSource::INVALID_TILE_ALTERNATIVE) {
+ hovered_tile.set_atlas_coords(coords);
+ hovered_tile.alternative_tile = alternative;
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ tile_atlas_control->update();
+ alternative_tiles_control->update();
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) { // Pressed
+ // Left click pressed.
+ if (!mb->is_shift_pressed()) {
+ tile_set_selection.clear();
+ }
+
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) {
+ if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile))) {
+ tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile));
+ } else {
+ tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile));
+ }
+ }
+ _update_selection_pattern_from_tileset_selection();
+ }
+ tile_atlas_control->update();
+ alternative_tiles_control->update();
+ }
+}
+
+void TileMapEditorTilesPlugin::_set_tile_map_selection(const TypedArray<Vector2i> &p_selection) {
+ tile_map_selection.clear();
+ for (int i = 0; i < p_selection.size(); i++) {
+ tile_map_selection.insert(p_selection[i]);
+ }
+ _update_selection_pattern_from_tilemap_selection();
+ _update_tileset_selection_from_selection_pattern();
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const {
+ TypedArray<Vector2i> output;
+ for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
+ output.push_back(E->get());
+ }
+ return output;
+}
+
+void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id) {
+ tile_map_id = p_tile_map_id;
+
+ // Clear the selection.
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+}
+
+void TileMapEditorTilesPlugin::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileMapEditorTilesPlugin::_scene_thumbnail_done);
+ ClassDB::bind_method(D_METHOD("_set_tile_map_selection", "selection"), &TileMapEditorTilesPlugin::_set_tile_map_selection);
+ ClassDB::bind_method(D_METHOD("_get_tile_map_selection"), &TileMapEditorTilesPlugin::_get_tile_map_selection);
+}
+
+TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
+ CanvasItemEditor::get_singleton()->get_viewport_control()->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_mouse_exited_viewport));
+
+ // --- Shortcuts ---
+ ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KEY_MASK_CMD | KEY_X);
+ ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KEY_MASK_CMD | KEY_C);
+ ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KEY_MASK_CMD | KEY_V);
+ ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), KEY_ESCAPE);
+ ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE);
+
+ // --- Toolbar ---
+ toolbar = memnew(HBoxContainer);
+ toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer);
+
+ tool_buttons_group.instantiate();
+
+ select_tool_button = memnew(Button);
+ select_tool_button->set_flat(true);
+ select_tool_button->set_toggle_mode(true);
+ select_tool_button->set_button_group(tool_buttons_group);
+ select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", "Selection", KEY_S));
+ select_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(select_tool_button);
+
+ paint_tool_button = memnew(Button);
+ paint_tool_button->set_flat(true);
+ paint_tool_button->set_toggle_mode(true);
+ paint_tool_button->set_button_group(tool_buttons_group);
+ paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E));
+ paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(paint_tool_button);
+
+ line_tool_button = memnew(Button);
+ line_tool_button->set_flat(true);
+ line_tool_button->set_toggle_mode(true);
+ line_tool_button->set_button_group(tool_buttons_group);
+ line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", KEY_L));
+ line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(line_tool_button);
+
+ rect_tool_button = memnew(Button);
+ rect_tool_button->set_flat(true);
+ rect_tool_button->set_toggle_mode(true);
+ rect_tool_button->set_button_group(tool_buttons_group);
+ rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", KEY_R));
+ rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(rect_tool_button);
+
+ bucket_tool_button = memnew(Button);
+ bucket_tool_button->set_flat(true);
+ bucket_tool_button->set_toggle_mode(true);
+ bucket_tool_button->set_button_group(tool_buttons_group);
+ bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", KEY_B));
+ bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(bucket_tool_button);
+ toolbar->add_child(tilemap_tiles_tools_buttons);
+
+ // -- TileMap tool settings --
+ tools_settings = memnew(HBoxContainer);
+ toolbar->add_child(tools_settings);
+
+ tools_settings_vsep = memnew(VSeparator);
+ tools_settings->add_child(tools_settings_vsep);
+
+ // Picker
+ picker_button = memnew(Button);
+ picker_button->set_flat(true);
+ picker_button->set_toggle_mode(true);
+ picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P));
+ picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(picker_button);
+
+ // Erase button.
+ erase_button = memnew(Button);
+ erase_button->set_flat(true);
+ erase_button->set_toggle_mode(true);
+ erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E));
+ erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(erase_button);
+
+ // Separator 2.
+ tools_settings_vsep_2 = memnew(VSeparator);
+ tools_settings->add_child(tools_settings_vsep_2);
+
+ // Continuous checkbox.
+ bucket_continuous_checkbox = memnew(CheckBox);
+ bucket_continuous_checkbox->set_flat(true);
+ bucket_continuous_checkbox->set_text(TTR("Contiguous"));
+ tools_settings->add_child(bucket_continuous_checkbox);
+
+ // Random tile checkbox.
+ random_tile_checkbox = memnew(CheckBox);
+ random_tile_checkbox->set_flat(true);
+ random_tile_checkbox->set_text(TTR("Place Random Tile"));
+ random_tile_checkbox->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled));
+ tools_settings->add_child(random_tile_checkbox);
+
+ // Random tile scattering.
+ scatter_label = memnew(Label);
+ scatter_label->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile."));
+ scatter_label->set_text(TTR("Scattering:"));
+ tools_settings->add_child(scatter_label);
+
+ scatter_spinbox = memnew(SpinBox);
+ scatter_spinbox->set_min(0.0);
+ scatter_spinbox->set_max(1000);
+ scatter_spinbox->set_step(0.001);
+ scatter_spinbox->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile."));
+ scatter_spinbox->get_line_edit()->add_theme_constant_override("minimum_character_width", 4);
+ scatter_spinbox->connect("value_changed", callable_mp(this, &TileMapEditorTilesPlugin::_on_scattering_spinbox_changed));
+ tools_settings->add_child(scatter_spinbox);
+
+ _on_random_tile_checkbox_toggled(false);
+
+ // Wide empty separation control.
+ Control *h_empty_space = memnew(Control);
+ h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL);
+ toolbar->add_child(h_empty_space);
+
+ // Grid toggle.
+ toggle_grid_button = memnew(Button);
+ toggle_grid_button->set_flat(true);
+ toggle_grid_button->set_toggle_mode(true);
+ toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_grid_toggled));
+ toolbar->add_child(toggle_grid_button);
+
+ // Default tool.
+ paint_tool_button->set_pressed(true);
+ _update_toolbar();
+
+ // --- Bottom panel ---
+ set_name("Tiles");
+
+ missing_source_label = memnew(Label);
+ missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Edit the TileSet resource to add one."));
+ missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ missing_source_label->set_align(Label::ALIGN_CENTER);
+ missing_source_label->set_valign(Label::VALIGN_CENTER);
+ missing_source_label->hide();
+ add_child(missing_source_label);
+
+ atlas_sources_split_container = memnew(HSplitContainer);
+ atlas_sources_split_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ atlas_sources_split_container->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(atlas_sources_split_container);
+
+ sources_list = memnew(ItemList);
+ sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE);
+ sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ sources_list->set_stretch_ratio(0.25);
+ sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
+ sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1));
+ sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1));
+ sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current));
+ sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list));
+ atlas_sources_split_container->add_child(sources_list);
+
+ // Tile atlas source.
+ tile_atlas_view = memnew(TileAtlasView);
+ tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view->set_texture_grid_visible(false);
+ tile_atlas_view->set_tile_shape_grid_visible(false);
+ tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform));
+ atlas_sources_split_container->add_child(tile_atlas_view);
+
+ tile_atlas_control = memnew(Control);
+ tile_atlas_control->connect("draw", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_draw));
+ tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited));
+ tile_atlas_control->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_gui_input));
+ tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control);
+
+ alternative_tiles_control = memnew(Control);
+ alternative_tiles_control->connect("draw", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_draw));
+ alternative_tiles_control->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited));
+ alternative_tiles_control->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input));
+ tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control);
+
+ // Scenes collection source.
+ scene_tiles_list = memnew(ItemList);
+ scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
+ scene_tiles_list->set_drag_forwarding(this);
+ scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI);
+ scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected));
+ scene_tiles_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_nothing_selected));
+ scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ atlas_sources_split_container->add_child(scene_tiles_list);
+
+ // Invalid source label.
+ invalid_source_label = memnew(Label);
+ invalid_source_label->set_text(TTR("Invalid source selected."));
+ invalid_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ invalid_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ invalid_source_label->set_align(Label::ALIGN_CENTER);
+ invalid_source_label->set_valign(Label::VALIGN_CENTER);
+ invalid_source_label->hide();
+ atlas_sources_split_container->add_child(invalid_source_label);
+
+ _update_bottom_panel();
+}
+
+TileMapEditorTilesPlugin::~TileMapEditorTilesPlugin() {
+ memdelete(selection_pattern);
+ memdelete(tile_map_clipboard);
+}
+
+void TileMapEditorTerrainsPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ paint_tool_button->set_icon(get_theme_icon("Edit", "EditorIcons"));
+ picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons"));
+ erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons"));
+ break;
+ }
+}
+
+void TileMapEditorTerrainsPlugin::tile_set_changed() {
+ _update_terrains_cache();
+ _update_terrains_tree();
+ _update_tiles_list();
+}
+
+void TileMapEditorTerrainsPlugin::_update_toolbar() {
+ // Hide all settings.
+ for (int i = 0; i < tools_settings->get_child_count(); i++) {
+ Object::cast_to<CanvasItem>(tools_settings->get_child(i))->hide();
+ }
+
+ // Show only the correct settings.
+ if (tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ tools_settings_vsep->show();
+ picker_button->show();
+ erase_button->show();
+ }
+}
+
+Control *TileMapEditorTerrainsPlugin::get_toolbar() const {
+ return toolbar;
+}
+
+Map<Vector2i, TileSet::CellNeighbor> TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const {
+ Map<Vector2i, TileSet::CellNeighbor> output;
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), output);
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ // Half offset shapes.
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ } else {
+ switch (bit) {
+ case 0:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 1:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE;
+ break;
+ case 2:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER;
+ break;
+ case 3:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE;
+ break;
+ case 4:
+ output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE;
+ output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE;
+ break;
+ default:
+ ERR_FAIL_V(output);
+ }
+ }
+ }
+ return output;
+}
+
+TileMapEditorTerrainsPlugin::Constraint::Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) {
+ // The way we build the constraint make it easy to detect conflicting constraints.
+ tile_map = p_tile_map;
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE || shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ // Half-offset shapes
+ TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis();
+ if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_SIDE:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_SIDE:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_CORNER:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ } else {
+ switch (p_bit) {
+ case TileSet::CELL_NEIGHBOR_RIGHT_CORNER:
+ bit = 0;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
+ bit = 1;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE:
+ bit = 3;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
+ bit = 4;
+ base_cell_coords = p_position;
+ break;
+ case TileSet::CELL_NEIGHBOR_LEFT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE:
+ bit = 1;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER:
+ bit = 0;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_SIDE:
+ bit = 3;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER:
+ bit = 2;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ break;
+ case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE:
+ bit = 4;
+ base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ break;
+ default:
+ ERR_FAIL();
+ break;
+ }
+ }
+ }
+ terrain = p_terrain;
+}
+
+Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<Constraint> p_constraints) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Set<TerrainsTilePattern>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Set<TerrainsTilePattern>();
+ }
+
+ // Returns all tiles compatible with the given constraints.
+ Set<TerrainsTilePattern> compatible_terrain_tile_patterns;
+ for (Map<TerrainsTilePattern, Set<TileMapCell>>::Element *E = per_terrain_terrains_tile_patterns_tiles[p_terrain_set].front(); E; E = E->next()) {
+ int valid = true;
+ int in_pattern_count = 0;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ // Check if the bit is compatible with the constraints.
+ Constraint terrain_bit_constraint = Constraint(tile_map, p_position, bit, E->key()[in_pattern_count]);
+
+ Set<Constraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint);
+ if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) {
+ valid = false;
+ break;
+ }
+ in_pattern_count++;
+ }
+ }
+
+ if (valid) {
+ compatible_terrain_tile_patterns.insert(E->key());
+ }
+ }
+
+ return compatible_terrain_tile_patterns;
+}
+
+Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Set<Constraint>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Set<Constraint>();
+ }
+
+ ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<Constraint>());
+
+ // Build a set of dummy constraints get the constrained points.
+ Set<Constraint> dummy_constraints;
+ for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides.
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) {
+ dummy_constraints.insert(Constraint(tile_map, E->get(), bit, -1));
+ }
+ }
+ }
+
+ // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it.
+ Set<Constraint> constraints;
+ for (Set<Constraint>::Element *E = dummy_constraints.front(); E; E = E->next()) {
+ Constraint c = E->get();
+
+ Map<int, int> terrain_count;
+
+ // Count the number of occurrences per terrain.
+ Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits();
+ for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_overlapping = overlapping_terrain_bits.front(); E_overlapping; E_overlapping = E_overlapping->next()) {
+ if (!p_to_replace.has(E_overlapping->key())) {
+ TileMapCell neighbor_cell = tile_map->get_cell(E_overlapping->key());
+ TileData *neighbor_tile_data = nullptr;
+ if (terrain_tiles.has(neighbor_cell) && terrain_tiles[neighbor_cell]->get_terrain_set() == p_terrain_set) {
+ neighbor_tile_data = terrain_tiles[neighbor_cell];
+ }
+
+ int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping->get())) : -1;
+ if (terrain_count.has(terrain)) {
+ terrain_count[terrain] = 0;
+ }
+ terrain_count[terrain] += 1;
+ }
+ }
+
+ // Get the terrain with the max number of occurrences.
+ int max = 0;
+ int max_terrain = -1;
+ for (Map<int, int>::Element *E_terrain_count = terrain_count.front(); E_terrain_count; E_terrain_count = E_terrain_count->next()) {
+ if (E_terrain_count->get() > max) {
+ max = E_terrain_count->get();
+ max_terrain = E_terrain_count->key();
+ }
+ }
+
+ // Set the adequate terrain.
+ if (max > 0) {
+ c.set_terrain(max_terrain);
+ constraints.insert(c);
+ }
+ }
+
+ return constraints;
+}
+
+Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Set<TileMapEditorTerrainsPlugin::Constraint>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Set<TileMapEditorTerrainsPlugin::Constraint>();
+ }
+
+ // Compute the constraints needed from the surrounding tiles.
+ Set<TileMapEditorTerrainsPlugin::Constraint> output;
+ int in_pattern_count = 0;
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) {
+ Constraint c = Constraint(tile_map, p_position, side, p_terrains_tile_pattern[in_pattern_count]);
+ output.insert(c);
+ in_pattern_count++;
+ }
+ }
+
+ return output;
+}
+
+Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TerrainsTilePattern>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern>();
+ }
+
+ // Copy the constraints set.
+ Set<TileMapEditorTerrainsPlugin::Constraint> constraints = p_constraints;
+
+ // Compute all acceptable tiles for each cell.
+ Map<Vector2i, Set<TerrainsTilePattern>> per_cell_acceptable_tiles;
+ for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) {
+ per_cell_acceptable_tiles[E->get()] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, E->get(), constraints);
+ }
+
+ // Output map.
+ Map<Vector2i, TerrainsTilePattern> output;
+
+ // Add all positions to a set.
+ Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace);
+ while (!to_replace.is_empty()) {
+ // Compute the minimum number of tile possibilities for each cell.
+ int min_nb_possibilities = 100000000;
+ for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) {
+ min_nb_possibilities = MIN(min_nb_possibilities, E->get().size());
+ }
+
+ // Get the set of possible cells to fill.
+ LocalVector<Vector2i> to_choose_from;
+ for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) {
+ if (E->get().size() == min_nb_possibilities) {
+ to_choose_from.push_back(E->key());
+ }
+ }
+
+ // Randomly pick a tile out of the most constrained.
+ Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)];
+
+ // Randomly select a tile out of them the put it in the grid.
+ Set<TerrainsTilePattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace];
+ if (valid_tiles.is_empty()) {
+ // No possibilities :/
+ break;
+ }
+ int random_terrain_tile_pattern_index = Math::random(0, valid_tiles.size() - 1);
+ Set<TerrainsTilePattern>::Element *E = valid_tiles.front();
+ for (int i = 0; i < random_terrain_tile_pattern_index; i++) {
+ E = E->next();
+ }
+ TerrainsTilePattern selected_terrain_tile_pattern = E->get();
+
+ // Set the selected cell into the output.
+ output[selected_cell_to_replace] = selected_terrain_tile_pattern;
+ to_replace.erase(selected_cell_to_replace);
+ per_cell_acceptable_tiles.erase(selected_cell_to_replace);
+
+ // Add the new constraints from the added tiles.
+ Set<TileMapEditorTerrainsPlugin::Constraint> new_constraints = _get_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern);
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) {
+ constraints.insert(E_constraint->get());
+ }
+
+ // Compute valid tiles again for neighbors.
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_map->is_existing_neighbor(side)) {
+ Vector2i neighbor = tile_map->get_neighbor_cell(selected_cell_to_replace, side);
+ if (to_replace.has(neighbor)) {
+ per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, neighbor, constraints);
+ }
+ }
+ }
+ }
+ return output;
+}
+
+TileMapCell TileMapEditorTerrainsPlugin::_get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return TileMapCell();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return TileMapCell();
+ }
+
+ // Count the sum of probabilities.
+ double sum = 0.0;
+ Set<TileMapCell> set = per_terrain_terrains_tile_patterns_tiles[p_terrain_set][p_terrain_tile_pattern];
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = tile_set->get_source(E->get().source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ sum += tile_data->get_probability();
+ } else {
+ sum += 1.0;
+ }
+ } else {
+ sum += 1.0;
+ }
+ }
+
+ // Generate a random number.
+ double count = 0.0;
+ double picked = Math::random(0.0, sum);
+
+ // Pick the tile.
+ for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) {
+ if (E->get().source_id >= 0) {
+ Ref<TileSetSource> source = tile_set->get_source(E->get().source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile));
+ count += tile_data->get_probability();
+ } else {
+ count += 1.0;
+ }
+ } else {
+ count += 1.0;
+ }
+
+ if (count >= picked) {
+ return E->get();
+ }
+ }
+
+ ERR_FAIL_V(TileMapCell());
+}
+
+Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return Map<Vector2i, TileMapCell>();
+ }
+
+ Map<Vector2i, TileMapCell> output;
+
+ // Add the constraints from the added tiles.
+ Set<TileMapEditorTerrainsPlugin::Constraint> added_tiles_constraints_set;
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ Vector2i coords = E_to_paint->key();
+ TerrainsTilePattern terrains_tile_pattern = E_to_paint->get();
+
+ Set<TileMapEditorTerrainsPlugin::Constraint> cell_constraints = _get_constraints_from_added_tile(coords, p_terrain_set, terrains_tile_pattern);
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = cell_constraints.front(); E; E = E->next()) {
+ added_tiles_constraints_set.insert(E->get());
+ }
+ }
+
+ // Build the list of potential tiles to replace.
+ Set<Vector2i> potential_to_replace;
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ Vector2i coords = E_to_paint->key();
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (tile_map->is_existing_neighbor(TileSet::CellNeighbor(i))) {
+ Vector2i neighbor = tile_map->get_neighbor_cell(coords, TileSet::CellNeighbor(i));
+ if (!p_to_paint.has(neighbor)) {
+ potential_to_replace.insert(neighbor);
+ }
+ }
+ }
+ }
+
+ // Set of tiles to replace
+ Set<Vector2i> to_replace;
+
+ // Add the central tiles to the one to replace. TODO: maybe change that.
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ to_replace.insert(E_to_paint->key());
+ }
+
+ // Add the constraints from the surroundings of the modified areas.
+ Set<TileMapEditorTerrainsPlugin::Constraint> removed_cells_constraints_set;
+ bool to_replace_modified = true;
+ while (to_replace_modified) {
+ // Get the constraints from the removed cells.
+ removed_cells_constraints_set = _get_constraints_from_removed_cells_list(to_replace, p_terrain_set);
+
+ // Filter the sources to make sure they are in the potential_to_replace.
+ Map<Constraint, Set<Vector2i>> source_tiles_of_constraint;
+ for (Set<Constraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) {
+ Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits();
+ for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_source_tile_of_constraint = sources_of_constraint.front(); E_source_tile_of_constraint; E_source_tile_of_constraint = E_source_tile_of_constraint->next()) {
+ if (potential_to_replace.has(E_source_tile_of_constraint->key())) {
+ source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint->key());
+ }
+ }
+ }
+
+ to_replace_modified = false;
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
+ Constraint c = E->get();
+ // Check if we have a conflict in constraints.
+ if (removed_cells_constraints_set.has(c) && removed_cells_constraints_set.find(c)->get().get_terrain() != c.get_terrain()) {
+ // If we do, we search for a neighbor to remove.
+ if (source_tiles_of_constraint.has(c) && !source_tiles_of_constraint[c].is_empty()) {
+ // Remove it.
+ Vector2i to_add_to_remove = source_tiles_of_constraint[c].front()->get();
+ potential_to_replace.erase(to_add_to_remove);
+ to_replace.insert(to_add_to_remove);
+ to_replace_modified = true;
+ for (Map<Constraint, Set<Vector2i>>::Element *E_source_tiles_of_constraint = source_tiles_of_constraint.front(); E_source_tiles_of_constraint; E_source_tiles_of_constraint = E_source_tiles_of_constraint->next()) {
+ E_source_tiles_of_constraint->get().erase(to_add_to_remove);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Combine all constraints together.
+ Set<TileMapEditorTerrainsPlugin::Constraint> constraints = removed_cells_constraints_set;
+ for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) {
+ constraints.insert(E->get());
+ }
+
+ // Run WFC to fill the holes with the constraints.
+ Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints);
+
+ // Use the WFC run for the output.
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E = wfc_output.front(); E; E = E->next()) {
+ output[E->key()] = _get_random_tile_from_pattern(p_terrain_set, E->get());
+ }
+
+ // Override the WFC results to make sure at least the painted tiles are actually painted.
+ for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) {
+ output[E_to_paint->key()] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint->get());
+ }
+
+ return output;
+}
+
+bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
+ if (!is_visible_in_tree()) {
+ // If the bottom editor is not visible, we ignore inputs.
+ return false;
+ }
+
+ if (CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) {
+ return false;
+ }
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return false;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return false;
+ }
+
+ // Get the selected terrain.
+ TerrainsTilePattern selected_terrains_tile_pattern;
+ int selected_terrain_set = -1;
+
+ TreeItem *selected_tree_item = terrains_tree->get_selected();
+ if (selected_tree_item && selected_tree_item->get_metadata(0)) {
+ Dictionary metadata_dict = selected_tree_item->get_metadata(0);
+ // Selected terrain
+ selected_terrain_set = metadata_dict["terrain_set"];
+
+ // Selected tile
+ if (erase_button->is_pressed()) {
+ selected_terrains_tile_pattern.clear();
+ for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor side = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(selected_terrain_set, side)) {
+ selected_terrains_tile_pattern.push_back(-1);
+ }
+ }
+ } else if (terrains_tile_list->is_anything_selected()) {
+ metadata_dict = terrains_tile_list->get_item_metadata(terrains_tile_list->get_selected_items()[0]);
+ selected_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mm->get_position());
+
+ switch (drag_type) {
+ case DRAG_TYPE_PAINT: {
+ if (selected_terrain_set >= 0) {
+ Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos));
+ Map<Vector2i, TerrainsTilePattern> to_draw;
+ for (int i = 0; i < line.size(); i++) {
+ to_draw[line[i]] = selected_terrains_tile_pattern;
+ }
+ Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set);
+ for (Map<Vector2i, TileMapCell>::Element *E = modified.front(); E; E = E->next()) {
+ if (!drag_modified.has(E->key())) {
+ drag_modified[E->key()] = tile_map->get_cell(E->key());
+ }
+ tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ drag_last_mouse_pos = mpos;
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(mb->get_position());
+
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Pressed
+ if (picker_button->is_pressed()) {
+ drag_type = DRAG_TYPE_PICK;
+ } else {
+ // Paint otherwise.
+ if (selected_terrain_set >= 0 && !selected_terrains_tile_pattern.is_empty() && tool_buttons_group->get_pressed_button() == paint_tool_button) {
+ drag_type = DRAG_TYPE_PAINT;
+ drag_start_mouse_pos = mpos;
+
+ drag_modified.clear();
+
+ Map<Vector2i, TerrainsTilePattern> terrains_to_draw;
+ terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern;
+
+ Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set);
+ for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
+ drag_modified[E->key()] = tile_map->get_cell(E->key());
+ tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ }
+ }
+ } else {
+ // Released
+ switch (drag_type) {
+ case DRAG_TYPE_PICK: {
+ Vector2i coords = tile_map->world_to_map(mpos);
+ TileMapCell tile = tile_map->get_cell(coords);
+
+ if (terrain_tiles.has(tile)) {
+ Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[tile]);
+
+ // Find the tree item for the right terrain set.
+ bool need_tree_item_switch = true;
+ TreeItem *tree_item = terrains_tree->get_selected();
+ if (tree_item) {
+ Dictionary metadata_dict = tree_item->get_metadata(0);
+ if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
+ int terrain_set = metadata_dict["terrain_set"];
+ int terrain_id = metadata_dict["terrain_id"];
+ if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ need_tree_item_switch = false;
+ }
+ }
+ }
+
+ if (need_tree_item_switch) {
+ for (tree_item = terrains_tree->get_root()->get_first_child(); tree_item; tree_item = tree_item->get_next_visible()) {
+ Dictionary metadata_dict = tree_item->get_metadata(0);
+ if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
+ int terrain_set = metadata_dict["terrain_set"];
+ int terrain_id = metadata_dict["terrain_id"];
+ if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ // Found
+ tree_item->select(0);
+ _update_tiles_list();
+ break;
+ }
+ }
+ }
+ }
+
+ // Find the list item for the given tile.
+ if (tree_item) {
+ for (int i = 0; i < terrains_tile_list->get_item_count(); i++) {
+ Dictionary metadata_dict = terrains_tile_list->get_item_metadata(i);
+ TerrainsTilePattern in_meta_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
+ bool equals = true;
+ for (int j = 0; j < terrains_tile_pattern.size(); j++) {
+ if (terrains_tile_pattern[j] != in_meta_terrains_tile_pattern[j]) {
+ equals = false;
+ break;
+ }
+ }
+ if (equals) {
+ terrains_tile_list->select(i);
+ break;
+ }
+ }
+ } else {
+ ERR_PRINT("Terrain tile not found.");
+ }
+ }
+ picker_button->set_pressed(false);
+ } break;
+ case DRAG_TYPE_PAINT: {
+ undo_redo->create_action(TTR("Paint terrain"));
+ for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ undo_redo->commit_action(false);
+ } break;
+ default:
+ break;
+ }
+ drag_type = DRAG_TYPE_NONE;
+ }
+
+ CanvasItemEditor::get_singleton()->update_viewport();
+
+ return true;
+ }
+ drag_last_mouse_pos = mpos;
+ }
+
+ return false;
+}
+
+TileMapEditorTerrainsPlugin::TerrainsTilePattern TileMapEditorTerrainsPlugin::_build_terrains_tile_pattern(TileData *p_tile_data) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return TerrainsTilePattern();
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return TerrainsTilePattern();
+ }
+
+ TerrainsTilePattern output;
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ if (tile_set->is_valid_peering_bit_terrain(p_tile_data->get_terrain_set(), TileSet::CellNeighbor(i))) {
+ output.push_back(p_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(i)));
+ }
+ }
+ return output;
+}
+
+void TileMapEditorTerrainsPlugin::_update_terrains_cache() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ // Compute the tile sides.
+ tile_sides.clear();
+ TileSet::TileShape shape = tile_set->get_tile_shape();
+ if (shape == TileSet::TILE_SHAPE_SQUARE) {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ } else {
+ if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ } else {
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE);
+ tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE);
+ }
+ }
+
+ // Organizes tiles into structures.
+ per_terrain_terrains_tile_patterns_tiles.resize(tile_set->get_terrain_sets_count());
+ per_terrain_terrains_tile_patterns.resize(tile_set->get_terrain_sets_count());
+ for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) {
+ per_terrain_terrains_tile_patterns_tiles[i].clear();
+ per_terrain_terrains_tile_patterns[i].resize(tile_set->get_terrains_count(i));
+ for (int j = 0; j < (int)per_terrain_terrains_tile_patterns[i].size(); j++) {
+ per_terrain_terrains_tile_patterns[i][j].clear();
+ }
+ }
+
+ for (int source_index = 0; source_index < tile_set->get_source_count(); source_index++) {
+ int source_id = tile_set->get_source_id(source_index);
+ Ref<TileSetSource> source = tile_set->get_source(source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) {
+ Vector2i tile_id = source->get_tile_id(tile_index);
+ for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) {
+ int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index);
+
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id));
+ int terrain_set = tile_data->get_terrain_set();
+ if (terrain_set >= 0) {
+ ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_tile_patterns.size());
+
+ TileMapCell cell;
+ cell.source_id = source_id;
+ cell.set_atlas_coords(tile_id);
+ cell.alternative_tile = alternative_id;
+
+ TerrainsTilePattern terrains_tile_pattern = _build_terrains_tile_pattern(tile_data);
+
+ // Terrain bits.
+ for (int i = 0; i < terrains_tile_pattern.size(); i++) {
+ int terrain = terrains_tile_pattern[i];
+ if (terrain >= 0 && terrain < (int)per_terrain_terrains_tile_patterns[terrain_set].size()) {
+ per_terrain_terrains_tile_patterns[terrain_set][terrain].insert(terrains_tile_pattern);
+ terrain_tiles[cell] = tile_data;
+ per_terrain_terrains_tile_patterns_tiles[terrain_set][terrains_tile_pattern].insert(cell);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add the empty cell in the possible patterns and cells.
+ for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) {
+ TerrainsTilePattern empty_pattern;
+ for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) {
+ if (tile_set->is_valid_peering_bit_terrain(i, TileSet::CellNeighbor(j))) {
+ empty_pattern.push_back(-1);
+ }
+ }
+
+ TileMapCell empty_cell;
+ empty_cell.source_id = -1;
+ empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS);
+ empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell);
+ }
+}
+
+void TileMapEditorTerrainsPlugin::_update_terrains_tree() {
+ terrains_tree->clear();
+ terrains_tree->create_item();
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ // Fill in the terrain list.
+ Vector<Vector<Ref<Texture2D>>> icons = tile_set->generate_terrains_icons(Size2(16, 16) * EDSCALE);
+ for (int terrain_set_index = 0; terrain_set_index < tile_set->get_terrain_sets_count(); terrain_set_index++) {
+ // Add an item for the terrain set.
+ TreeItem *terrain_set_tree_item = terrains_tree->create_item();
+ String matches;
+ if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) {
+ terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchCornersAndSides", "EditorIcons"));
+ matches = String(TTR("Matches Corners and Sides"));
+ } else if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS) {
+ terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchCorners", "EditorIcons"));
+ matches = String(TTR("Matches Corners Only"));
+ } else {
+ terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchSides", "EditorIcons"));
+ matches = String(TTR("Matches Sides Only"));
+ }
+ terrain_set_tree_item->set_text(0, vformat("Terrain Set %d (%s)", terrain_set_index, matches));
+ terrain_set_tree_item->set_selectable(0, false);
+
+ for (int terrain_index = 0; terrain_index < tile_set->get_terrains_count(terrain_set_index); terrain_index++) {
+ // Add the item to the terrain list.
+ TreeItem *terrain_tree_item = terrains_tree->create_item(terrain_set_tree_item);
+ terrain_tree_item->set_text(0, tile_set->get_terrain_name(terrain_set_index, terrain_index));
+ terrain_tree_item->set_icon_max_width(0, 32 * EDSCALE);
+ terrain_tree_item->set_icon(0, icons[terrain_set_index][terrain_index]);
+
+ Dictionary metadata_dict;
+ metadata_dict["terrain_set"] = terrain_set_index;
+ metadata_dict["terrain_id"] = terrain_index;
+ terrain_tree_item->set_metadata(0, metadata_dict);
+ }
+ }
+}
+
+void TileMapEditorTerrainsPlugin::_update_tiles_list() {
+ terrains_tile_list->clear();
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ TreeItem *selected_tree_item = terrains_tree->get_selected();
+ if (selected_tree_item && selected_tree_item->get_metadata(0)) {
+ Dictionary metadata_dict = selected_tree_item->get_metadata(0);
+ int selected_terrain_set = metadata_dict["terrain_set"];
+ int selected_terrain_id = metadata_dict["terrain_id"];
+ ERR_FAIL_INDEX(selected_terrain_set, (int)per_terrain_terrains_tile_patterns.size());
+ ERR_FAIL_INDEX(selected_terrain_id, (int)per_terrain_terrains_tile_patterns[selected_terrain_set].size());
+
+ // Sort the items in a map by the number of corresponding terrains.
+ Map<int, Set<TerrainsTilePattern>> sorted;
+ for (Set<TerrainsTilePattern>::Element *E = per_terrain_terrains_tile_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) {
+ // Count the number of matching sides/terrains.
+ int count = 0;
+
+ for (int i = 0; i < E->get().size(); i++) {
+ if (int(E->get()[i]) == selected_terrain_id) {
+ count++;
+ }
+ }
+ sorted[count].insert(E->get());
+ }
+
+ for (Map<int, Set<TerrainsTilePattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) {
+ for (Set<TerrainsTilePattern>::Element *E = E_set->get().front(); E; E = E->next()) {
+ TerrainsTilePattern terrains_tile_pattern = E->get();
+
+ // Get the icon.
+ Ref<Texture2D> icon;
+ Rect2 region;
+ bool transpose = false;
+
+ double max_probability = -1.0;
+ for (Set<TileMapCell>::Element *E_tile_map_cell = per_terrain_terrains_tile_patterns_tiles[selected_terrain_set][terrains_tile_pattern].front(); E_tile_map_cell; E_tile_map_cell = E_tile_map_cell->next()) {
+ Ref<TileSetSource> source = tile_set->get_source(E_tile_map_cell->get().source_id);
+
+ Ref<TileSetAtlasSource> atlas_source = source;
+ if (atlas_source.is_valid()) {
+ TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E_tile_map_cell->get().get_atlas_coords(), E_tile_map_cell->get().alternative_tile));
+ if (tile_data->get_probability() > max_probability) {
+ icon = atlas_source->get_texture();
+ region = atlas_source->get_tile_texture_region(E_tile_map_cell->get().get_atlas_coords());
+ if (tile_data->get_flip_h()) {
+ region.position.x += region.size.x;
+ region.size.x = -region.size.x;
+ }
+ if (tile_data->get_flip_v()) {
+ region.position.y += region.size.y;
+ region.size.y = -region.size.y;
+ }
+ transpose = tile_data->get_transpose();
+ max_probability = tile_data->get_probability();
+ }
+ }
+ }
+
+ // Create the ItemList's item.
+ int item_index = terrains_tile_list->add_item("");
+ terrains_tile_list->set_item_icon(item_index, icon);
+ terrains_tile_list->set_item_icon_region(item_index, region);
+ terrains_tile_list->set_item_icon_transposed(item_index, transpose);
+ Dictionary list_metadata_dict;
+ list_metadata_dict["terrains_tile_pattern"] = terrains_tile_pattern;
+ terrains_tile_list->set_item_metadata(item_index, list_metadata_dict);
+ }
+ }
+ if (terrains_tile_list->get_item_count() > 0) {
+ terrains_tile_list->select(0);
+ }
+ }
+}
+
+void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id) {
+ tile_map_id = p_tile_map_id;
+ _update_terrains_cache();
+ _update_terrains_tree();
+ _update_tiles_list();
+}
+
+TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() {
+ set_name("Terrains");
+
+ HSplitContainer *tilemap_tab_terrains = memnew(HSplitContainer);
+ tilemap_tab_terrains->set_h_size_flags(SIZE_EXPAND_FILL);
+ tilemap_tab_terrains->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(tilemap_tab_terrains);
+
+ terrains_tree = memnew(Tree);
+ terrains_tree->set_h_size_flags(SIZE_EXPAND_FILL);
+ terrains_tree->set_stretch_ratio(0.25);
+ terrains_tree->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
+ terrains_tree->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ terrains_tree->set_hide_root(true);
+ terrains_tree->connect("item_selected", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_tiles_list));
+ tilemap_tab_terrains->add_child(terrains_tree);
+
+ terrains_tile_list = memnew(ItemList);
+ terrains_tile_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ terrains_tile_list->set_max_columns(0);
+ terrains_tile_list->set_same_column_width(true);
+ terrains_tile_list->set_fixed_icon_size(Size2(30, 30) * EDSCALE);
+ terrains_tile_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ tilemap_tab_terrains->add_child(terrains_tile_list);
+
+ // --- Toolbar ---
+ toolbar = memnew(HBoxContainer);
+
+ HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer);
+
+ tool_buttons_group.instantiate();
+
+ paint_tool_button = memnew(Button);
+ paint_tool_button->set_flat(true);
+ paint_tool_button->set_toggle_mode(true);
+ paint_tool_button->set_button_group(tool_buttons_group);
+ paint_tool_button->set_pressed(true);
+ paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E));
+ paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar));
+ tilemap_tiles_tools_buttons->add_child(paint_tool_button);
+
+ toolbar->add_child(tilemap_tiles_tools_buttons);
+
+ // -- TileMap tool settings --
+ tools_settings = memnew(HBoxContainer);
+ toolbar->add_child(tools_settings);
+
+ tools_settings_vsep = memnew(VSeparator);
+ tools_settings->add_child(tools_settings_vsep);
+
+ // Picker
+ picker_button = memnew(Button);
+ picker_button->set_flat(true);
+ picker_button->set_toggle_mode(true);
+ picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P));
+ picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(picker_button);
+
+ // Erase button.
+ erase_button = memnew(Button);
+ erase_button->set_flat(true);
+ erase_button->set_toggle_mode(true);
+ erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E));
+ erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
+ tools_settings->add_child(erase_button);
+}
+
+TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() {
+}
+
+void TileMapEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ missing_tile_texture = get_theme_icon("StatusWarning", "EditorIcons");
+ warning_pattern_texture = get_theme_icon("WarningPattern", "EditorIcons");
+ break;
+ case NOTIFICATION_INTERNAL_PROCESS:
+ if (is_visible_in_tree() && tileset_changed_needs_update) {
+ _update_bottom_panel();
+ tile_map_editor_plugins[tabs->get_current_tab()]->tile_set_changed();
+ CanvasItemEditor::get_singleton()->update_viewport();
+ tileset_changed_needs_update = false;
+ }
+ break;
+ }
+}
+
+void TileMapEditor::_update_bottom_panel() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+
+ // Update the visibility of controls.
+ missing_tileset_label->set_visible(!tile_set.is_valid());
+ if (!tile_set.is_valid()) {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->hide();
+ }
+ } else {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
+ }
+ }
+}
+
+Vector<Vector2i> TileMapEditor::get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell) {
+ ERR_FAIL_COND_V(!p_tile_map, Vector<Vector2i>());
+
+ Ref<TileSet> tile_set = p_tile_map->get_tileset();
+ ERR_FAIL_COND_V(!tile_set.is_valid(), Vector<Vector2i>());
+
+ if (tile_set->get_tile_shape() == TileSet::TILE_SHAPE_SQUARE) {
+ return Geometry2D::bresenham_line(p_from_cell, p_to_cell);
+ } else {
+ // Adapt the bresenham line algorithm to half-offset shapes.
+ // See this blog post: http://zvold.blogspot.com/2010/01/bresenhams-line-drawing-algorithm-on_26.html
+ Vector<Point2i> points;
+
+ bool transposed = tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL;
+ p_from_cell = TileMap::transform_coords_layout(p_from_cell, tile_set->get_tile_offset_axis(), tile_set->get_tile_layout(), TileSet::TILE_LAYOUT_STACKED);
+ p_to_cell = TileMap::transform_coords_layout(p_to_cell, tile_set->get_tile_offset_axis(), tile_set->get_tile_layout(), TileSet::TILE_LAYOUT_STACKED);
+ if (transposed) {
+ SWAP(p_from_cell.x, p_from_cell.y);
+ SWAP(p_to_cell.x, p_to_cell.y);
+ }
+
+ Vector2i delta = p_to_cell - p_from_cell;
+ delta = Vector2i(2 * delta.x + ABS(p_to_cell.y % 2) - ABS(p_from_cell.y % 2), delta.y);
+ Vector2i sign = delta.sign();
+
+ Vector2i current = p_from_cell;
+ points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout()));
+
+ int err = 0;
+ if (ABS(delta.y) < ABS(delta.x)) {
+ Vector2i err_step = 3 * delta.abs();
+ while (current != p_to_cell) {
+ err += err_step.y;
+ if (err > ABS(delta.x)) {
+ if (sign.x == 0) {
+ current += Vector2(sign.y, 0);
+ } else {
+ current += Vector2(bool(current.y % 2) ^ (sign.x < 0) ? sign.x : 0, sign.y);
+ }
+ err -= err_step.x;
+ } else {
+ current += Vector2i(sign.x, 0);
+ err += err_step.y;
+ }
+ points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout()));
+ }
+ } else {
+ Vector2i err_step = delta.abs();
+ while (current != p_to_cell) {
+ err += err_step.x;
+ if (err > 0) {
+ if (sign.x == 0) {
+ current += Vector2(0, sign.y);
+ } else {
+ current += Vector2(bool(current.y % 2) ^ (sign.x < 0) ? sign.x : 0, sign.y);
+ }
+ err -= err_step.y;
+ } else {
+ if (sign.x == 0) {
+ current += Vector2(0, sign.y);
+ } else {
+ current += Vector2(bool(current.y % 2) ^ (sign.x > 0) ? -sign.x : 0, sign.y);
+ }
+ err += err_step.y;
+ }
+ points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout()));
+ }
+ }
+
+ return points;
+ }
+}
+
+void TileMapEditor::_tile_map_changed() {
+ tileset_changed_needs_update = true;
+}
+
+void TileMapEditor::_tab_changed(int p_tab_id) {
+ // Make the plugin edit the correct tilemap.
+ tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id);
+
+ // Update toolbar.
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->get_toolbar()->set_visible(i == p_tab_id);
+ }
+
+ // Update visible panel.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map || !tile_map->get_tileset().is_valid()) {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->hide();
+ }
+ } else {
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab());
+ }
+ }
+
+ // Graphical update.
+ tile_map_editor_plugins[tabs->get_current_tab()]->update();
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
+ return tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_gui_input(p_event);
+}
+
+void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (!tile_set.is_valid()) {
+ return;
+ }
+
+ if (!tile_map->is_visible_in_tree()) {
+ return;
+ }
+
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Transform2D xform_inv = xform.affine_inverse();
+ Vector2i tile_shape_size = tile_set->get_tile_size();
+
+ // Draw tiles with invalid IDs in the grid.
+ float icon_ratio = MIN(missing_tile_texture->get_size().x / tile_set->get_tile_size().x, missing_tile_texture->get_size().y / tile_set->get_tile_size().y) / 3;
+ TypedArray<Vector2i> used_cells = tile_map->get_used_cells();
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = used_cells[i];
+ int tile_source_id = tile_map->get_cell_source_id(coords);
+ if (tile_source_id >= 0) {
+ Vector2i tile_atlas_coords = tile_map->get_cell_atlas_coords(coords);
+ int tile_alternative_tile = tile_map->get_cell_alternative_tile(coords);
+
+ TileSetSource *source = nullptr;
+ if (tile_set->has_source(tile_source_id)) {
+ source = *tile_set->get_source(tile_source_id);
+ }
+
+ if (!source || !source->has_tile(tile_atlas_coords) || !source->has_alternative_tile(tile_atlas_coords, tile_alternative_tile)) {
+ // Generate a random color from the hashed values of the tiles.
+ Array to_hash;
+ to_hash.push_back(tile_source_id);
+ to_hash.push_back(tile_atlas_coords);
+ to_hash.push_back(tile_alternative_tile);
+ uint32_t hash = RandomPCG(to_hash.hash()).rand();
+
+ Color color;
+ color = color.from_hsv(
+ (float)((hash >> 24) & 0xFF) / 256.0,
+ Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
+ Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
+ 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);
+
+ // Draw the warning icon.
+ Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_ratio * missing_tile_texture->get_size() * xform.get_scale() / 2), icon_ratio * missing_tile_texture->get_size() * xform.get_scale());
+ p_overlay->draw_texture_rect(missing_tile_texture, rect);
+ }
+ }
+ }
+
+ // Fading on the border.
+ const int fading = 5;
+
+ // Determine the drawn area.
+ Size2 screen_size = p_overlay->get_size();
+ Rect2i screen_rect;
+ screen_rect.position = tile_map->world_to_map(xform_inv.xform(Vector2()));
+ screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(0, screen_size.height))));
+ screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0))));
+ screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(screen_size)));
+ screen_rect = screen_rect.grow(1);
+
+ Rect2i tilemap_used_rect = tile_map->get_used_rect();
+
+ Rect2i displayed_rect = tilemap_used_rect.intersection(screen_rect);
+ displayed_rect = displayed_rect.grow(fading);
+
+ // Reduce the drawn area to avoid crashes if needed.
+ int max_size = 100;
+ if (displayed_rect.size.x > max_size) {
+ displayed_rect = displayed_rect.grow_individual(-(displayed_rect.size.x - max_size) / 2, 0, -(displayed_rect.size.x - max_size) / 2, 0);
+ }
+ if (displayed_rect.size.y > max_size) {
+ displayed_rect = displayed_rect.grow_individual(0, -(displayed_rect.size.y - max_size) / 2, 0, -(displayed_rect.size.y - max_size) / 2);
+ }
+
+ // Draw the grid.
+ bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid");
+ if (display_grid) {
+ Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color");
+ for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) {
+ for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) {
+ Vector2i pos_in_rect = Vector2i(x, y) - displayed_rect.position;
+
+ // Fade out the border of the grid.
+ float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f);
+ float right_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.x, (float)(displayed_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f);
+ float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f);
+ 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));
+ Color color = grid_color;
+ color.a = color.a * opacity;
+ tile_set->draw_tile_shape(p_overlay, cell_region, color, false);
+ }
+ }
+ }
+
+ // Draw the IDs for debug.
+ /*Ref<Font> font = get_theme_font("font", "Label");
+ for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) {
+ for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) {
+ p_overlay->draw_string(font, xform.xform(tile_map->map_to_world(Vector2(x, y))) + Vector2i(-tile_shape_size.x / 2, 0), vformat("%s", Vector2(x, y)));
+ }
+ }*/
+
+ // Draw the plugins.
+ tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay);
+}
+
+void TileMapEditor::edit(TileMap *p_tile_map) {
+ if (p_tile_map && p_tile_map->get_instance_id() == tile_map_id) {
+ return;
+ }
+
+ // Disconnect to changes.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tile_map->disconnect("changed", callable_mp(this, &TileMapEditor::_tile_map_changed));
+ }
+
+ // Change the edited object.
+ if (p_tile_map) {
+ tile_map_id = p_tile_map->get_instance_id();
+ tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ // Connect to changes.
+ if (!tile_map->is_connected("changed", callable_mp(this, &TileMapEditor::_tile_map_changed))) {
+ tile_map->connect("changed", callable_mp(this, &TileMapEditor::_tile_map_changed));
+ }
+ } else {
+ tile_map_id = ObjectID();
+ }
+
+ // Call the plugins.
+ tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id);
+
+ _tile_map_changed();
+}
+
+TileMapEditor::TileMapEditor() {
+ set_process_internal(true);
+
+ // TileMap editor plugins
+ tile_map_editor_plugins.push_back(memnew(TileMapEditorTilesPlugin));
+ tile_map_editor_plugins.push_back(memnew(TileMapEditorTerrainsPlugin));
+
+ // Tabs.
+ tabs = memnew(Tabs);
+ tabs->set_clip_tabs(false);
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tabs->add_tab(tile_map_editor_plugins[i]->get_name());
+ }
+ tabs->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed));
+
+ // --- TileMap toolbar ---
+ tilemap_toolbar = memnew(HBoxContainer);
+ tilemap_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
+ tilemap_toolbar->add_child(tabs);
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ tile_map_editor_plugins[i]->get_toolbar()->hide();
+ tilemap_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar());
+ }
+
+ missing_tileset_label = memnew(Label);
+ missing_tileset_label->set_text(TTR("The edited TileMap node has no TileSet resource."));
+ missing_tileset_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ missing_tileset_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ missing_tileset_label->set_align(Label::ALIGN_CENTER);
+ missing_tileset_label->set_valign(Label::VALIGN_CENTER);
+ missing_tileset_label->hide();
+ add_child(missing_tileset_label);
+
+ for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
+ add_child(tile_map_editor_plugins[i]);
+ tile_map_editor_plugins[i]->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_map_editor_plugins[i]->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_map_editor_plugins[i]->set_visible(i == 0);
+ }
+
+ _tab_changed(0);
+}
+
+TileMapEditor::~TileMapEditor() {
+}
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
new file mode 100644
index 0000000000..a6f4ec3021
--- /dev/null
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -0,0 +1,343 @@
+/*************************************************************************/
+/* tile_map_editor.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 TILE_MAP_EDITOR_H
+#define TILE_MAP_EDITOR_H
+
+#include "tile_atlas_view.h"
+
+#include "core/typedefs.h"
+#include "editor/editor_node.h"
+#include "scene/2d/tile_map.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/tabs.h"
+
+class TileMapEditorPlugin : public VBoxContainer {
+public:
+ virtual Control *get_toolbar() const {
+ return memnew(Control);
+ };
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; };
+ virtual void forward_canvas_draw_over_viewport(Control *p_overlay){};
+ virtual void tile_set_changed(){};
+ virtual void edit(ObjectID p_tile_map_id){};
+};
+
+class TileMapEditorTilesPlugin : public TileMapEditorPlugin {
+ GDCLASS(TileMapEditorTilesPlugin, TileMapEditorPlugin);
+
+private:
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+ ObjectID tile_map_id;
+ virtual void edit(ObjectID p_tile_map_id) override;
+
+ ///// Toolbar /////
+ HBoxContainer *toolbar;
+
+ Ref<ButtonGroup> tool_buttons_group;
+ Button *select_tool_button;
+ Button *paint_tool_button;
+ Button *line_tool_button;
+ Button *rect_tool_button;
+ Button *bucket_tool_button;
+ Button *picker_button;
+
+ HBoxContainer *tools_settings;
+ VSeparator *tools_settings_vsep;
+ Button *erase_button;
+ CheckBox *bucket_continuous_checkbox;
+
+ VSeparator *tools_settings_vsep_2;
+ CheckBox *random_tile_checkbox;
+ float scattering = 0.0;
+ Label *scatter_label;
+ SpinBox *scatter_spinbox;
+ void _on_random_tile_checkbox_toggled(bool p_pressed);
+ void _on_scattering_spinbox_changed(double p_value);
+
+ Button *toggle_grid_button;
+ void _on_grid_toggled(bool p_pressed);
+
+ void _update_toolbar();
+
+ ///// Tilemap editing. /////
+ bool has_mouse = false;
+ void _mouse_exited_viewport();
+
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_SELECT,
+ DRAG_TYPE_MOVE,
+ DRAG_TYPE_PAINT,
+ DRAG_TYPE_LINE,
+ DRAG_TYPE_RECT,
+ DRAG_TYPE_BUCKET,
+ DRAG_TYPE_PICK,
+ DRAG_TYPE_CLIPBOARD_PASTE,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2 drag_start_mouse_pos;
+ Vector2 drag_last_mouse_pos;
+ Map<Vector2i, TileMapCell> drag_modified;
+
+ TileMapCell _pick_random_tile(const TileMapPattern *p_pattern);
+ Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos);
+ Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell);
+ Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous);
+ void _stop_dragging();
+
+ ///// Selection system. /////
+ Set<Vector2i> tile_map_selection;
+ TileMapPattern *tile_map_clipboard = memnew(TileMapPattern);
+ TileMapPattern *selection_pattern = memnew(TileMapPattern);
+ void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection);
+ TypedArray<Vector2i> _get_tile_map_selection() const;
+
+ Set<TileMapCell> tile_set_selection;
+
+ void _update_selection_pattern_from_tilemap_selection();
+ void _update_selection_pattern_from_tileset_selection();
+ void _update_tileset_selection_from_selection_pattern();
+ void _update_fix_selected_and_hovered();
+
+ ///// Bottom panel. ////.
+ Label *missing_source_label;
+ Label *invalid_source_label;
+
+ ItemList *sources_list;
+
+ Ref<Texture2D> missing_atlas_texture_icon;
+ void _update_tile_set_sources_list();
+
+ void _update_bottom_panel();
+
+ // Atlas sources.
+ TileMapCell hovered_tile;
+ TileAtlasView *tile_atlas_view;
+ HSplitContainer *atlas_sources_split_container;
+
+ bool tile_set_dragging_selection = false;
+ Vector2i tile_set_drag_start_mouse_pos;
+
+ Control *tile_atlas_control;
+ void _tile_atlas_control_mouse_exited();
+ void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event);
+ void _tile_atlas_control_draw();
+
+ Control *alternative_tiles_control;
+ void _tile_alternatives_control_draw();
+ void _tile_alternatives_control_mouse_exited();
+ void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event);
+
+ void _update_atlas_view();
+
+ // Scenes collection sources.
+ ItemList *scene_tiles_list;
+
+ void _update_scenes_collection_view();
+ void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud);
+ void _scenes_list_multi_selected(int p_index, bool p_selected);
+ void _scenes_list_nothing_selected();
+
+ // Update callback
+ virtual void tile_set_changed() override;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ virtual Control *get_toolbar() const override;
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
+ virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
+
+ TileMapEditorTilesPlugin();
+ ~TileMapEditorTilesPlugin();
+};
+
+class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin {
+ GDCLASS(TileMapEditorTerrainsPlugin, TileMapEditorPlugin);
+
+private:
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+ ObjectID tile_map_id;
+ virtual void edit(ObjectID p_tile_map_id) override;
+
+ // Toolbar.
+ HBoxContainer *toolbar;
+
+ Ref<ButtonGroup> tool_buttons_group;
+ Button *paint_tool_button;
+
+ HBoxContainer *tools_settings;
+ VSeparator *tools_settings_vsep;
+ Button *picker_button;
+ Button *erase_button;
+
+ void _update_toolbar();
+
+ // TileMap editing.
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_PAINT,
+ DRAG_TYPE_PICK,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2 drag_start_mouse_pos;
+ Vector2 drag_last_mouse_pos;
+ Map<Vector2i, TileMapCell> drag_modified;
+
+ // Painting
+ class Constraint {
+ private:
+ const TileMap *tile_map;
+ Vector2i base_cell_coords = Vector2i();
+ int bit = -1;
+ int terrain = -1;
+
+ public:
+ // TODO implement difference operator.
+ bool operator<(const Constraint &p_other) const {
+ if (base_cell_coords == p_other.base_cell_coords) {
+ return bit < p_other.bit;
+ }
+ return base_cell_coords < p_other.base_cell_coords;
+ }
+
+ String to_string() const {
+ return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain);
+ }
+
+ Vector2i get_base_cell_coords() const {
+ return base_cell_coords;
+ }
+
+ Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const;
+
+ void set_terrain(int p_terrain) {
+ terrain = p_terrain;
+ }
+
+ int get_terrain() const {
+ return terrain;
+ }
+
+ Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain);
+ Constraint() {}
+ };
+
+ typedef Array TerrainsTilePattern;
+
+ Set<TerrainsTilePattern> _get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const;
+ Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const;
+ Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const;
+ Map<Vector2i, TerrainsTilePattern> _wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const;
+ TileMapCell _get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const;
+ Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const;
+
+ // Cached data.
+
+ TerrainsTilePattern _build_terrains_tile_pattern(TileData *p_tile_data);
+ LocalVector<Map<TerrainsTilePattern, Set<TileMapCell>>> per_terrain_terrains_tile_patterns_tiles;
+ LocalVector<LocalVector<Set<TerrainsTilePattern>>> per_terrain_terrains_tile_patterns;
+
+ Map<TileMapCell, TileData *> terrain_tiles;
+ LocalVector<TileSet::CellNeighbor> tile_sides;
+
+ // Bottom panel.
+ Tree *terrains_tree;
+ ItemList *terrains_tile_list;
+
+ // Update functions.
+ void _update_terrains_cache();
+ void _update_terrains_tree();
+ void _update_tiles_list();
+
+ // Update callback
+ virtual void tile_set_changed() override;
+
+protected:
+ void _notification(int p_what);
+ // static void _bind_methods();
+
+public:
+ virtual Control *get_toolbar() const override;
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override;
+ //virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;
+
+ TileMapEditorTerrainsPlugin();
+ ~TileMapEditorTerrainsPlugin();
+};
+
+class TileMapEditor : public VBoxContainer {
+ GDCLASS(TileMapEditor, VBoxContainer);
+
+private:
+ bool tileset_changed_needs_update = false;
+ ObjectID tile_map_id;
+
+ // Vector to keep plugins.
+ Vector<TileMapEditorPlugin *> tile_map_editor_plugins;
+
+ // Toolbar.
+ HBoxContainer *tilemap_toolbar;
+
+ // Bottom panel
+ Label *missing_tileset_label;
+ Tabs *tabs;
+ void _update_bottom_panel();
+
+ // TileMap
+ Ref<Texture2D> missing_tile_texture;
+ Ref<Texture2D> warning_pattern_texture;
+
+ // CallBack
+ void _tile_map_changed();
+ void _tab_changed(int p_tab_changed);
+
+protected:
+ void _notification(int p_what);
+ void _draw_shape(Control *p_control, Rect2 p_region, TileSet::TileShape p_shape, TileSet::TileOffsetAxis p_offset_axis, Color p_color);
+
+public:
+ bool forward_canvas_gui_input(const Ref<InputEvent> &p_event);
+ void forward_canvas_draw_over_viewport(Control *p_overlay);
+
+ void edit(TileMap *p_tile_map);
+ Control *get_toolbar() { return tilemap_toolbar; };
+
+ TileMapEditor();
+ ~TileMapEditor();
+
+ // Static functions.
+ static Vector<Vector2i> get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell);
+};
+
+#endif // TILE_MAP_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
new file mode 100644
index 0000000000..9d849a0df5
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -0,0 +1,2322 @@
+/*************************************************************************/
+/* tile_set_atlas_source_editor.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 "tile_set_atlas_source_editor.h"
+
+#include "tiles_editor_plugin.h"
+
+#include "editor/editor_inspector.h"
+#include "editor/editor_scale.h"
+#include "editor/progress_dialog.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/control.h"
+#include "scene/gui/item_list.h"
+#include "scene/gui/separator.h"
+#include "scene/gui/split_container.h"
+#include "scene/gui/tab_container.h"
+
+#include "core/core_string_names.h"
+#include "core/math/geometry_2d.h"
+#include "core/os/keyboard.h"
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id(int p_id) {
+ ERR_FAIL_COND(p_id < 0);
+ if (source_id == p_id) {
+ return;
+ }
+ ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Atlas Source ID. Another source exists with id %d.", p_id));
+
+ int previous_source = source_id;
+ source_id = p_id; // source_id must be updated before, because it's used by the source list update.
+ tile_set->set_source_id(previous_source, p_id);
+ emit_signal("changed", "id");
+}
+
+int TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id() {
+ return source_id;
+}
+
+bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+ bool valid = false;
+ tile_set_atlas_source->set(p_name, p_value, &valid);
+ if (valid) {
+ emit_signal("changed", String(p_name).utf8().get_data());
+ }
+ return valid;
+}
+
+bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+ if (!tile_set_atlas_source) {
+ return false;
+ }
+ bool valid = false;
+ r_ret = tile_set_atlas_source->get(p_name, &valid);
+ return valid;
+}
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, ""));
+}
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() {
+ // -- Shape and layout --
+ ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id);
+ ClassDB::bind_method(D_METHOD("get_id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id");
+
+ ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set.is_valid());
+ ERR_FAIL_COND(!p_tile_set_atlas_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
+
+ // Disconnect to changes.
+ if (tile_set_atlas_source) {
+ tile_set_atlas_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+
+ tile_set = p_tile_set;
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ source_id = p_source_id;
+
+ // Connect to changes.
+ if (tile_set_atlas_source) {
+ if (!tile_set_atlas_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+ tile_set_atlas_source->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+ }
+
+ notify_property_list_changed();
+}
+
+// -- Proxy object used by the tile inspector --
+bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+ if (!tile_set_atlas_source) {
+ return false;
+ }
+
+ if (tiles.size() == 1) {
+ const Vector2i &coords = tiles.front()->get().tile;
+ const int &alternative = tiles.front()->get().alternative;
+
+ if (alternative == 0 && p_name == "atlas_coords") {
+ Vector2i as_vector2i = Vector2i(p_value);
+ ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, as_vector2i), false);
+
+ if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) {
+ tiles_set_atlas_source_editor->selection.clear();
+ tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 });
+ tiles_set_atlas_source_editor->_update_tile_id_label();
+ }
+
+ tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i);
+ tiles.clear();
+ tiles.insert({ as_vector2i, 0 });
+ emit_signal("changed", "atlas_coords");
+ return true;
+ } else if (alternative == 0 && p_name == "size_in_atlas") {
+ Vector2i as_vector2i = Vector2i(p_value);
+ ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i), false);
+
+ tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i);
+ emit_signal("changed", "size_in_atlas");
+ return true;
+ } else if (alternative > 0 && p_name == "alternative_id") {
+ int as_int = int(p_value);
+ ERR_FAIL_COND_V(as_int < 0, false);
+ ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords));
+
+ if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) {
+ tiles_set_atlas_source_editor->selection.clear();
+ tiles_set_atlas_source_editor->selection.insert({ coords, as_int });
+ }
+
+ int previous_alternative_tile = alternative;
+ tiles.clear();
+ tiles.insert({ coords, as_int }); // tiles must be updated before.
+ tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int);
+
+ emit_signal("changed", "alternative_id");
+ return true;
+ }
+ }
+
+ bool any_valid = false;
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ bool valid = false;
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ ERR_FAIL_COND_V(!tile_data, false);
+ tile_data->set(p_name, p_value, &valid);
+
+ any_valid |= valid;
+ }
+
+ if (any_valid) {
+ emit_signal("changed", String(p_name).utf8().get_data());
+ }
+
+ return any_valid;
+}
+
+bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+ if (!tile_set_atlas_source) {
+ return false;
+ }
+
+ if (tiles.size() == 1) {
+ const Vector2i &coords = tiles.front()->get().tile;
+ const int &alternative = tiles.front()->get().alternative;
+
+ if (alternative == 0 && p_name == "atlas_coords") {
+ r_ret = coords;
+ return true;
+ } else if (alternative == 0 && p_name == "size_in_atlas") {
+ r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords);
+ return true;
+ } else if (alternative > 0 && p_name == "alternative_id") {
+ r_ret = alternative;
+ return true;
+ }
+ }
+
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ // Return the first tile with a property matching the name.
+ // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit.
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ ERR_FAIL_COND_V(!tile_data, false);
+
+ bool valid = false;
+ r_ret = tile_data->get(p_name, &valid);
+ if (valid) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (!tile_set_atlas_source) {
+ return;
+ }
+
+ if (tiles.size() == 1) {
+ if (tiles.front()->get().alternative == 0) {
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, ""));
+ } else {
+ p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, ""));
+ }
+ }
+
+ // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit).
+ struct PropertyId {
+ int occurence_id = 0;
+ String property;
+ bool operator<(const PropertyId &p_other) const {
+ return occurence_id == p_other.occurence_id ? property < p_other.property : occurence_id < p_other.occurence_id;
+ }
+ };
+ struct PLData {
+ int uses = 0;
+ PropertyInfo property_info;
+ };
+ Map<PropertyId, PLData> usage;
+
+ List<PLData *> data_list;
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ ERR_FAIL_COND(!tile_data);
+
+ List<PropertyInfo> list;
+ tile_data->get_property_list(&list);
+
+ Map<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once)
+ for (List<PropertyInfo>::Element *E_property = list.front(); E_property; E_property = E_property->next()) {
+ const String &property_string = E_property->get().name;
+ if (!tile_data->is_allowing_transform() && (property_string == "flip_h" || property_string == "flip_v" || property_string == "transpose")) {
+ continue;
+ }
+
+ if (!counts.has(property_string)) {
+ counts[property_string] = 1;
+ } else {
+ counts[property_string] += 1;
+ }
+
+ PropertyInfo stored_property_info = E_property->get();
+ stored_property_info.usage |= PROPERTY_USAGE_STORAGE; // Ignore the storage flag in comparing properties.
+
+ PropertyId id = { counts[property_string], property_string };
+ if (!usage.has(id)) {
+ usage[id] = { 1, stored_property_info };
+ data_list.push_back(&usage[id]);
+ } else if (usage[id].property_info == stored_property_info) {
+ usage[id].uses += 1;
+ }
+ }
+ }
+
+ // Add only properties that are common to all tiles.
+ for (List<PLData *>::Element *E = data_list.front(); E; E = E->next()) {
+ if (E->get()->uses == tiles.size()) {
+ p_list->push_back(E->get()->property_info);
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles) {
+ ERR_FAIL_COND(!p_tile_set_atlas_source);
+ ERR_FAIL_COND(p_tiles.is_empty());
+ for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) {
+ ERR_FAIL_COND(E->get().tile == TileSetSource::INVALID_ATLAS_COORDS);
+ ERR_FAIL_COND(E->get().alternative < 0);
+ }
+
+ // Disconnect to changes.
+ for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ if (tile_set_atlas_source && tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ if (tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+ tile_data->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+ }
+ }
+
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ tiles = Set<TileSelection>(p_tiles);
+
+ // Connect to changes.
+ for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) {
+ const Vector2i &coords = E->get().tile;
+ const int &alternative = E->get().alternative;
+
+ if (tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
+ TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative));
+ if (!tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+ tile_data->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+ }
+ }
+
+ notify_property_list_changed();
+}
+
+void TileSetAtlasSourceEditor::AtlasTileProxyObject::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetAtlasSourceEditor::_inspector_property_selected(String p_property) {
+ selected_property = p_property;
+ _update_atlas_view();
+ _update_current_tile_data_editor();
+}
+
+void TileSetAtlasSourceEditor::_update_tile_id_label() {
+ if (selection.size() == 1) {
+ TileSelection selected = selection.front()->get();
+ tool_tile_id_label->set_text(vformat("%d, %s, %d", tile_set_atlas_source_id, selected.tile, selected.alternative));
+ tool_tile_id_label->set_tooltip(vformat(TTR("Selected tile:\nSource: %d\nAtlas coordinates: %s\nAlternative: %d"), tile_set_atlas_source_id, selected.tile, selected.alternative));
+ tool_tile_id_label->show();
+ } else {
+ tool_tile_id_label->hide();
+ }
+}
+
+void TileSetAtlasSourceEditor::_update_source_inspector() {
+ // Update the proxy object.
+ atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id);
+
+ // Update the "clear outside texture" button.
+ tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture());
+}
+
+void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() {
+ // Fix selected.
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (!tile_set_atlas_source->has_tile(selected.tile) || !tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) {
+ selection.erase(E);
+ }
+ }
+
+ // Fix hovered.
+ if (!tile_set_atlas_source->has_tile(hovered_base_tile_coords)) {
+ hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
+ }
+ Vector2i coords = Vector2i(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y);
+ int alternative = hovered_alternative_tile_coords.z;
+ if (!tile_set_atlas_source->has_tile(coords) || !tile_set_atlas_source->has_alternative_tile(coords, alternative)) {
+ hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ }
+}
+
+void TileSetAtlasSourceEditor::_update_atlas_source_inspector() {
+ // Update visibility.
+ bool visible = tools_button_group->get_pressed_button() == tool_setup_atlas_source_button;
+ atlas_source_inspector_label->set_visible(visible);
+ atlas_source_inspector->set_visible(visible);
+}
+
+void TileSetAtlasSourceEditor::_update_tile_inspector() {
+ // Update visibility.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ if (!selection.is_empty()) {
+ tile_proxy_object->edit(tile_set_atlas_source, selection);
+ }
+ tile_inspector_label->show();
+ tile_inspector->set_visible(!selection.is_empty());
+ tile_inspector_no_tile_selected_label->set_visible(selection.is_empty());
+ } else {
+ tile_inspector_label->hide();
+ tile_inspector->hide();
+ tile_inspector_no_tile_selected_label->hide();
+ }
+}
+
+void TileSetAtlasSourceEditor::_update_tile_data_editors() {
+ String previously_selected;
+ if (tile_data_editors_tree && tile_data_editors_tree->get_selected()) {
+ previously_selected = tile_data_editors_tree->get_selected()->get_metadata(0);
+ }
+
+ tile_data_editors_tree->clear();
+
+ TreeItem *root = tile_data_editors_tree->create_item();
+
+ TreeItem *group;
+#define ADD_TILE_DATA_EDITOR_GROUP(text) \
+ group = tile_data_editors_tree->create_item(root); \
+ group->set_custom_bg_color(0, group_color); \
+ group->set_selectable(0, false); \
+ group->set_disable_folding(true); \
+ group->set_text(0, text);
+
+ TreeItem *item;
+#define ADD_TILE_DATA_EDITOR(parent, text, property) \
+ item = tile_data_editors_tree->create_item(parent); \
+ item->set_text(0, text); \
+ item->set_metadata(0, property); \
+ if (property == previously_selected) { \
+ item->select(0); \
+ }
+
+ // Theming.
+ tile_data_editors_tree->add_theme_constant_override("vseparation", 1);
+ tile_data_editors_tree->add_theme_constant_override("hseparation", 3);
+
+ Color group_color = get_theme_color("prop_category", "Editor");
+
+ // List of editors.
+ // --- Rendering ---
+ ADD_TILE_DATA_EDITOR_GROUP("Rendering");
+
+ ADD_TILE_DATA_EDITOR(group, "Texture Offset", "texture_offset");
+ if (!tile_data_editors.has("texture_offset")) {
+ TileDataTextureOffsetEditor *tile_data_texture_offset_editor = memnew(TileDataTextureOffsetEditor);
+ tile_data_texture_offset_editor->hide();
+ tile_data_texture_offset_editor->setup_property_editor(Variant::VECTOR2, "texture_offset");
+ tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors["texture_offset"] = tile_data_texture_offset_editor;
+ }
+
+ ADD_TILE_DATA_EDITOR(group, "Modulate", "modulate");
+ if (!tile_data_editors.has("modulate")) {
+ TileDataDefaultEditor *tile_data_modulate_editor = memnew(TileDataDefaultEditor());
+ tile_data_modulate_editor->hide();
+ tile_data_modulate_editor->setup_property_editor(Variant::COLOR, "modulate", "", Color(1.0, 1.0, 1.0, 1.0));
+ tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors["modulate"] = tile_data_modulate_editor;
+ }
+
+ ADD_TILE_DATA_EDITOR(group, "Z Index", "z_index");
+ if (!tile_data_editors.has("z_index")) {
+ TileDataDefaultEditor *tile_data_z_index_editor = memnew(TileDataDefaultEditor());
+ tile_data_z_index_editor->hide();
+ tile_data_z_index_editor->setup_property_editor(Variant::INT, "z_index");
+ tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors["z_index"] = tile_data_z_index_editor;
+ }
+
+ ADD_TILE_DATA_EDITOR(group, "Y Sort Origin", "y_sort_origin");
+ if (!tile_data_editors.has("y_sort_origin")) {
+ TileDataYSortEditor *tile_data_y_sort_editor = memnew(TileDataYSortEditor);
+ tile_data_y_sort_editor->hide();
+ tile_data_y_sort_editor->setup_property_editor(Variant::INT, "y_sort_origin");
+ tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors["y_sort_origin"] = tile_data_y_sort_editor;
+ }
+
+ for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) {
+ ADD_TILE_DATA_EDITOR(group, vformat("Occlusion Layer %d", i), vformat("occlusion_layer_%d", i));
+ if (!tile_data_editors.has(vformat("occlusion_layer_%d", i))) {
+ TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor());
+ tile_data_occlusion_shape_editor->hide();
+ tile_data_occlusion_shape_editor->set_occlusion_layer(i);
+ tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors[vformat("occlusion_layer_%d", i)] = tile_data_occlusion_shape_editor;
+ }
+ }
+ for (int i = tile_set->get_occlusion_layers_count(); tile_data_editors.has(vformat("occlusion_layer_%d", i)); i++) {
+ tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_delete();
+ tile_data_editors.erase(vformat("occlusion_layer_%d", i));
+ }
+
+ // --- Rendering ---
+ ADD_TILE_DATA_EDITOR(root, "Terrains", "terrain_set");
+ if (!tile_data_editors.has("terrain_set")) {
+ TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor);
+ tile_data_terrains_editor->hide();
+ tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors["terrain_set"] = tile_data_terrains_editor;
+ }
+
+ // --- Miscellaneous ---
+ ADD_TILE_DATA_EDITOR(root, "Probability", "probability");
+ if (!tile_data_editors.has("probability")) {
+ TileDataDefaultEditor *tile_data_probability_editor = memnew(TileDataDefaultEditor());
+ tile_data_probability_editor->hide();
+ tile_data_probability_editor->setup_property_editor(Variant::FLOAT, "probability", "", 1.0);
+ tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors["probability"] = tile_data_probability_editor;
+ }
+
+ // --- Physics ---
+ ADD_TILE_DATA_EDITOR_GROUP("Physics");
+ for (int i = 0; i < tile_set->get_physics_layers_count(); i++) {
+ ADD_TILE_DATA_EDITOR(group, vformat("Physics Layer %d", i), vformat("physics_layer_%d", i));
+ if (!tile_data_editors.has(vformat("physics_layer_%d", i))) {
+ TileDataCollisionEditor *tile_data_collision_editor = memnew(TileDataCollisionEditor());
+ tile_data_collision_editor->hide();
+ tile_data_collision_editor->set_physics_layer(i);
+ tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors[vformat("physics_layer_%d", i)] = tile_data_collision_editor;
+ }
+ }
+ for (int i = tile_set->get_physics_layers_count(); tile_data_editors.has(vformat("physics_layer_%d", i)); i++) {
+ tile_data_editors[vformat("physics_layer_%d", i)]->queue_delete();
+ tile_data_editors.erase(vformat("physics_layer_%d", i));
+ }
+
+ // --- Navigation ---
+ ADD_TILE_DATA_EDITOR_GROUP("Navigation");
+ for (int i = 0; i < tile_set->get_navigation_layers_count(); i++) {
+ ADD_TILE_DATA_EDITOR(group, vformat("Navigation Layer %d", i), vformat("navigation_layer_%d", i));
+ if (!tile_data_editors.has(vformat("navigation_layer_%d", i))) {
+ TileDataNavigationEditor *tile_data_navigation_editor = memnew(TileDataNavigationEditor());
+ tile_data_navigation_editor->hide();
+ tile_data_navigation_editor->set_navigation_layer(i);
+ tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors[vformat("navigation_layer_%d", i)] = tile_data_navigation_editor;
+ }
+ }
+ for (int i = tile_set->get_navigation_layers_count(); tile_data_editors.has(vformat("navigation_layer_%d", i)); i++) {
+ tile_data_editors[vformat("navigation_layer_%d", i)]->queue_delete();
+ tile_data_editors.erase(vformat("navigation_layer_%d", i));
+ }
+
+ // --- Custom Data ---
+ ADD_TILE_DATA_EDITOR_GROUP("Custom Data");
+ for (int i = 0; i < tile_set->get_custom_data_layers_count(); i++) {
+ if (tile_set->get_custom_data_name(i).is_empty()) {
+ ADD_TILE_DATA_EDITOR(group, vformat("Custom Data %d", i), vformat("custom_data_%d", i));
+ } else {
+ ADD_TILE_DATA_EDITOR(group, tile_set->get_custom_data_name(i), vformat("custom_data_%d", i));
+ }
+ if (!tile_data_editors.has(vformat("custom_data_%d", i))) {
+ TileDataDefaultEditor *tile_data_custom_data_editor = memnew(TileDataDefaultEditor());
+ tile_data_custom_data_editor->hide();
+ tile_data_custom_data_editor->setup_property_editor(tile_set->get_custom_data_type(i), vformat("custom_data_%d", i), tile_set->get_custom_data_name(i));
+ tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update));
+ tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update));
+ tile_data_editors[vformat("custom_data_%d", i)] = tile_data_custom_data_editor;
+ }
+ }
+ for (int i = tile_set->get_custom_data_layers_count(); tile_data_editors.has(vformat("custom_data_%d", i)); i++) {
+ tile_data_editors[vformat("custom_data_%d", i)]->queue_delete();
+ tile_data_editors.erase(vformat("custom_data_%d", i));
+ }
+
+#undef ADD_TILE_DATA_EDITOR_GROUP
+#undef ADD_TILE_DATA_EDITOR
+
+ // Add tile data editors as children.
+ for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) {
+ // Tile Data Editor.
+ TileDataEditor *tile_data_editor = E->get();
+ if (!tile_data_editor->is_inside_tree()) {
+ tile_data_painting_editor_container->add_child(tile_data_editor);
+ }
+ tile_data_editor->set_tile_set(tile_set);
+
+ // Toolbar.
+ Control *toolbar = tile_data_editor->get_toolbar();
+ if (!toolbar->is_inside_tree()) {
+ tool_settings_tile_data_toolbar_container->add_child(toolbar);
+ }
+ toolbar->hide();
+ }
+
+ // Update visibility.
+ bool is_visible = tools_button_group->get_pressed_button() == tool_paint_button;
+ tile_data_editor_dropdown_button->set_visible(is_visible);
+ tile_data_editor_dropdown_button->set_text(TTR("Select a property editor"));
+ tile_data_editors_label->set_visible(is_visible);
+}
+
+void TileSetAtlasSourceEditor::_update_current_tile_data_editor() {
+ // Find the property to use.
+ String property;
+ if (tools_button_group->get_pressed_button() == tool_select_button && tile_inspector->is_visible() && !tile_inspector->get_selected_path().is_empty()) {
+ Vector<String> components = tile_inspector->get_selected_path().split("/");
+ if (components.size() >= 1) {
+ property = components[0];
+
+ // Workaround for terrains as they don't have a common first component.
+ if (property.begins_with("terrains_")) {
+ property = "terrain_set";
+ }
+ }
+ } else if (tools_button_group->get_pressed_button() == tool_paint_button && tile_data_editors_tree->get_selected()) {
+ property = tile_data_editors_tree->get_selected()->get_metadata(0);
+ tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0));
+ }
+
+ // Hide all editors but the current one.
+ for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) {
+ E->get()->hide();
+ E->get()->get_toolbar()->hide();
+ }
+ if (tile_data_editors.has(property)) {
+ current_tile_data_editor = tile_data_editors[property];
+ } else {
+ current_tile_data_editor = nullptr;
+ }
+
+ // Get the correct editor for the TileData's property.
+ if (current_tile_data_editor) {
+ current_tile_data_editor_toolbar = current_tile_data_editor->get_toolbar();
+ current_property = property;
+ current_tile_data_editor->set_visible(tools_button_group->get_pressed_button() == tool_paint_button);
+ current_tile_data_editor_toolbar->set_visible(tools_button_group->get_pressed_button() == tool_paint_button);
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw() {
+ if (!has_theme_icon("arrow", "OptionButton")) {
+ return;
+ }
+
+ RID ci = tile_data_editor_dropdown_button->get_canvas_item();
+ Ref<Texture2D> arrow = Control::get_theme_icon("arrow", "OptionButton");
+ Color clr = Color(1, 1, 1);
+ if (get_theme_constant("modulate_arrow")) {
+ switch (tile_data_editor_dropdown_button->get_draw_mode()) {
+ case BaseButton::DRAW_PRESSED:
+ clr = get_theme_color("font_pressed_color");
+ break;
+ case BaseButton::DRAW_HOVER:
+ clr = get_theme_color("font_hover_color");
+ break;
+ case BaseButton::DRAW_DISABLED:
+ clr = get_theme_color("font_disabled_color");
+ break;
+ default:
+ clr = get_theme_color("font_color");
+ }
+ }
+
+ Size2 size = tile_data_editor_dropdown_button->get_size();
+
+ Point2 ofs;
+ if (is_layout_rtl()) {
+ ofs = Point2(get_theme_constant("arrow_margin", "OptionButton"), int(Math::abs((size.height - arrow->get_height()) / 2)));
+ } else {
+ ofs = Point2(size.width - arrow->get_width() - get_theme_constant("arrow_margin", "OptionButton"), int(Math::abs((size.height - arrow->get_height()) / 2)));
+ }
+ arrow->draw(ci, ofs, clr);
+}
+
+void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed() {
+ Size2 size = tile_data_editor_dropdown_button->get_size();
+ tile_data_editors_popup->set_position(tile_data_editor_dropdown_button->get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
+ tile_data_editors_popup->set_size(Size2(size.width, 0));
+ tile_data_editors_popup->popup();
+}
+
+void TileSetAtlasSourceEditor::_tile_data_editors_tree_selected() {
+ tile_data_editors_popup->call_deferred("hide");
+ _update_current_tile_data_editor();
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+}
+
+void TileSetAtlasSourceEditor::_update_atlas_view() {
+ // Update the atlas display.
+ tile_atlas_view->set_atlas_source(*tile_set, tile_set_atlas_source, tile_set_atlas_source_id);
+
+ // Create a bunch of buttons to add alternative tiles.
+ for (int i = 0; i < alternative_tiles_control->get_child_count(); i++) {
+ alternative_tiles_control->get_child(i)->queue_delete();
+ }
+
+ Vector2i pos;
+ Vector2 texture_region_base_size = tile_set_atlas_source->get_texture_region_size();
+ int texture_region_base_size_min = MIN(texture_region_base_size.x, texture_region_base_size.y);
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i tile_id = tile_set_atlas_source->get_tile_id(i);
+ int alternative_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id);
+ if (alternative_count > 1) {
+ // Compute the right extremity of alternative.
+ int y_increment = 0;
+ pos.x = 0;
+ for (int j = 1; j < alternative_count; j++) {
+ int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j);
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(tile_id, alternative_id);
+ pos.x = MAX(pos.x, rect.get_end().x);
+ y_increment = MAX(y_increment, rect.size.y);
+ }
+
+ // Create and position the button.
+ Button *button = memnew(Button);
+ alternative_tiles_control->add_child(button);
+ button->set_flat(true);
+ button->set_icon(get_theme_icon("Add", "EditorIcons"));
+ button->add_theme_style_override("normal", memnew(StyleBoxEmpty));
+ button->add_theme_style_override("hover", memnew(StyleBoxEmpty));
+ button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
+ button->add_theme_style_override("pressed", memnew(StyleBoxEmpty));
+ button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, -1));
+ button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min)));
+ button->set_expand_icon(true);
+
+ pos.y += y_increment;
+ }
+ }
+ tile_atlas_view->set_padding(Side::SIDE_RIGHT, texture_region_base_size_min);
+
+ // Redraw everything.
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+
+ // Synchronize atlas view.
+ TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view);
+}
+
+void TileSetAtlasSourceEditor::_update_toolbar() {
+ // Show the tools and settings.
+ if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
+ if (current_tile_data_editor_toolbar) {
+ current_tile_data_editor_toolbar->hide();
+ }
+ tool_settings_vsep->show();
+ tools_settings_erase_button->show();
+ tool_advanced_menu_buttom->show();
+ } else if (tools_button_group->get_pressed_button() == tool_select_button) {
+ if (current_tile_data_editor_toolbar) {
+ current_tile_data_editor_toolbar->hide();
+ }
+ tool_settings_vsep->hide();
+ tools_settings_erase_button->hide();
+ tool_advanced_menu_buttom->hide();
+ } else if (tools_button_group->get_pressed_button() == tool_paint_button) {
+ if (current_tile_data_editor_toolbar) {
+ current_tile_data_editor_toolbar->show();
+ }
+ tool_settings_vsep->hide();
+ tools_settings_erase_button->hide();
+ tool_advanced_menu_buttom->hide();
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() {
+ hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ tile_atlas_view->update();
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed() {
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) {
+ // Update the hovered coords.
+ hovered_base_tile_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+
+ // Forward the event to the current tile data editor if we are in the painting mode.
+ if (tools_button_group->get_pressed_button() == tool_paint_button) {
+ if (current_tile_data_editor) {
+ current_tile_data_editor->forward_painting_atlas_gui_input(tile_atlas_view, tile_set_atlas_source, p_event);
+ }
+ // Update only what's needed.
+ tile_set_atlas_source_changed_needs_update = false;
+
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ } else {
+ // Handle the event.
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+
+ Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ if (drag_type == DRAG_TYPE_NONE) {
+ if (selection.size() == 1) {
+ // Change the cursor depending on the hovered thing.
+ TileSelection selected = selection.front()->get();
+ if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
+ Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position();
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
+ Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
+ Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
+ Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
+ const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
+ const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
+ CursorShape cursor_shape = CURSOR_ARROW;
+ bool can_grow[4];
+ for (int i = 0; i < 4; i++) {
+ can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+ can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
+ }
+ for (int i = 0; i < 4; i++) {
+ Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
+ if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) {
+ cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE;
+ }
+ Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
+ if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) {
+ cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE;
+ }
+ }
+ tile_atlas_control->set_default_cursor_shape(cursor_shape);
+ }
+ }
+ } else if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) {
+ // Create big tile.
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+
+ Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ new_rect.size += Vector2i(1, 1);
+ // Check if the new tile can fit in the new rect.
+ if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) {
+ // Move and resize the tile.
+ tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
+ drag_current_tile = new_rect.position;
+ }
+ } else if (drag_type == DRAG_TYPE_CREATE_TILES) {
+ // Create tiles.
+ last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+
+ Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords);
+ for (int i = 0; i < line.size(); i++) {
+ if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetSource::INVALID_ATLAS_COORDS) {
+ tile_set_atlas_source->create_tile(line[i]);
+ drag_modified_tiles.insert(line[i]);
+ }
+ }
+
+ drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position();
+
+ } else if (drag_type == DRAG_TYPE_REMOVE_TILES) {
+ // Remove tiles.
+ last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+
+ Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords);
+ for (int i = 0; i < line.size(); i++) {
+ Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(line[i]);
+ if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ drag_modified_tiles.insert(base_tile_coords);
+ }
+ }
+
+ drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position();
+ } else if (drag_type == DRAG_TYPE_MOVE_TILE) {
+ // Move tile.
+ Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size();
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset);
+ coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1));
+ if (drag_current_tile != coords && tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, coords)) {
+ tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords);
+ selection.clear();
+ selection.insert({ coords, 0 });
+ drag_current_tile = coords;
+
+ // Update only what's needed.
+ tile_set_atlas_source_changed_needs_update = false;
+ _update_tile_inspector();
+ _update_atlas_view();
+ _update_tile_id_label();
+ _update_current_tile_data_editor();
+ }
+ } else if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) {
+ if (Vector2(drag_start_mouse_pos).distance_to(tile_atlas_control->get_local_mouse_position()) > 5.0 * EDSCALE) {
+ drag_type = DRAG_TYPE_NONE;
+ }
+ } else if (drag_type >= DRAG_TYPE_RESIZE_TOP_LEFT && drag_type <= DRAG_TYPE_RESIZE_LEFT) {
+ // Resizing a tile.
+ new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(-1, -1)).min(grid_size);
+
+ Rect2i old_rect = Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ Rect2i new_rect = old_rect;
+
+ if (drag_type == DRAG_TYPE_RESIZE_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT) {
+ new_rect.position.x = MIN(new_base_tiles_coords.x + 1, old_rect.get_end().x - 1);
+ new_rect.size.x = old_rect.get_end().x - new_rect.position.x;
+ }
+ if (drag_type == DRAG_TYPE_RESIZE_TOP || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT) {
+ new_rect.position.y = MIN(new_base_tiles_coords.y + 1, old_rect.get_end().y - 1);
+ new_rect.size.y = old_rect.get_end().y - new_rect.position.y;
+ }
+
+ if (drag_type == DRAG_TYPE_RESIZE_RIGHT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) {
+ new_rect.set_end(Vector2i(MAX(new_base_tiles_coords.x, old_rect.position.x + 1), new_rect.get_end().y));
+ }
+ if (drag_type == DRAG_TYPE_RESIZE_BOTTOM || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) {
+ new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1)));
+ }
+
+ if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) {
+ tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size);
+ selection.clear();
+ selection.insert({ new_rect.position, 0 });
+ drag_current_tile = new_rect.position;
+
+ // Update only what's needed.
+ tile_set_atlas_source_changed_needs_update = false;
+ _update_tile_inspector();
+ _update_atlas_view();
+ _update_tile_id_label();
+ _update_current_tile_data_editor();
+ }
+ }
+
+ // Redraw for the hovered tile.
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position();
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Left click pressed.
+ if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
+ if (tools_settings_erase_button->is_pressed()) {
+ // Erasing
+ if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) {
+ // Remove tiles using rect.
+
+ // Setup the dragging info.
+ drag_type = DRAG_TYPE_REMOVE_TILES_USING_RECT;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ } else {
+ // Remove tiles.
+
+ // Setup the dragging info.
+ drag_type = DRAG_TYPE_REMOVE_TILES;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+
+ // Remove a first tile.
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ }
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ drag_modified_tiles.insert(coords);
+ }
+ }
+ } else {
+ // Creating
+ if (mb->is_shift_pressed()) {
+ // Create a big tile.
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
+ // Setup the dragging info, only if we start on an empty tile.
+ drag_type = DRAG_TYPE_CREATE_BIG_TILE;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = coords;
+
+ // Create a tile.
+ tile_set_atlas_source->create_tile(coords);
+ }
+ } else if (mb->is_ctrl_pressed()) {
+ // Create tiles using rect.
+ drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ } else {
+ // Create tiles.
+
+ // Setup the dragging info.
+ drag_type = DRAG_TYPE_CREATE_TILES;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+
+ // Create a first tile if needed.
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
+ tile_set_atlas_source->create_tile(coords);
+ drag_modified_tiles.insert(coords);
+ }
+ }
+ }
+ } else if (tools_button_group->get_pressed_button() == tool_select_button) {
+ // Dragging a handle.
+ drag_type = DRAG_TYPE_NONE;
+ if (selection.size() == 1) {
+ TileSelection selected = selection.front()->get();
+ if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) {
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
+ Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
+ Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
+ Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
+ const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
+ const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
+ CursorShape cursor_shape = CURSOR_ARROW;
+ bool can_grow[4];
+ for (int i = 0; i < 4; i++) {
+ can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+ can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
+ }
+ for (int i = 0; i < 4; i++) {
+ Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
+ if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) {
+ drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2);
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = selected.tile;
+ drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
+ cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE;
+ }
+ Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
+ if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) {
+ drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2);
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = selected.tile;
+ drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
+ cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE;
+ }
+ }
+ tile_atlas_control->set_default_cursor_shape(cursor_shape);
+ }
+ }
+
+ // Selecting then dragging a tile.
+ if (drag_type == DRAG_TYPE_NONE) {
+ TileSelection selected = { TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE };
+ Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ selected = { coords, 0 };
+ }
+ }
+
+ bool shift = mb->is_shift_pressed();
+ if (!shift && selection.size() == 1 && selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
+ // Start move dragging.
+ drag_type = DRAG_TYPE_MOVE_TILE;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ drag_current_tile = selected.tile;
+ drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile));
+ tile_atlas_control->set_default_cursor_shape(CURSOR_MOVE);
+ } else {
+ // Start selection dragging.
+ drag_type = DRAG_TYPE_RECT_SELECT;
+ drag_start_mouse_pos = mouse_local_pos;
+ drag_last_mouse_pos = drag_start_mouse_pos;
+ }
+ }
+ }
+ } else {
+ // Left click released.
+ _end_dragging();
+ }
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ // Right click pressed.
+ if (mb->is_pressed()) {
+ drag_type = DRAG_TYPE_MAY_POPUP_MENU;
+ drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position();
+ } else {
+ // Right click released.
+ _end_dragging();
+ }
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ }
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_end_dragging() {
+ switch (drag_type) {
+ case DRAG_TYPE_CREATE_TILES:
+ undo_redo->create_action(TTR("Create tiles"));
+ for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", E->get());
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", E->get());
+ }
+ undo_redo->commit_action(false);
+ break;
+ case DRAG_TYPE_CREATE_BIG_TILE:
+ undo_redo->create_action(TTR("Create a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", drag_current_tile);
+ undo_redo->commit_action(false);
+ break;
+ case DRAG_TYPE_REMOVE_TILES: {
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+ undo_redo->create_action(TTR("Remove tiles"));
+ for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
+ Vector2i coords = E->get();
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
+ if (per_tile.has(coords)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_CREATE_TILES_USING_RECT: {
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ undo_redo->create_action(TTR("Create tiles"));
+ for (int x = area.get_position().x; x < area.get_end().x; x++) {
+ for (int y = area.get_position().y; y < area.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords);
+ }
+ }
+ }
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_REMOVE_TILES_USING_RECT: {
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+
+ Set<Vector2i> to_delete;
+ for (int x = area.get_position().x; x < area.get_end().x; x++) {
+ for (int y = area.get_position().y; y < area.get_end().y; y++) {
+ Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ to_delete.insert(coords);
+ }
+ }
+ }
+
+ undo_redo->create_action(TTR("Remove tiles"));
+ undo_redo->add_do_method(this, "_set_selection_from_array", Array());
+ for (Set<Vector2i>::Element *E = to_delete.front(); E; E = E->next()) {
+ Vector2i coords = E->get();
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
+ if (per_tile.has(coords)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action();
+ } break;
+ case DRAG_TYPE_MOVE_TILE:
+ if (drag_current_tile != drag_start_tile_shape.position) {
+ undo_redo->create_action(TTR("Move a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size);
+ Array array;
+ array.push_back(drag_start_tile_shape.position);
+ array.push_back(0);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", array);
+ undo_redo->commit_action(false);
+ }
+ break;
+ case DRAG_TYPE_RECT_SELECT: {
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ ERR_FAIL_COND(start_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS);
+ ERR_FAIL_COND(new_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS);
+
+ Rect2i region = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ region.size += Vector2i(1, 1);
+
+ undo_redo->create_action(TTR("Select tiles"));
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+
+ // Determine if we clear, then add or remove to the selection.
+ bool add_to_selection = true;
+ if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
+ Vector2i coords = tile_set_atlas_source->get_tile_at_coords(start_base_tiles_coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ if (selection.has({ coords, 0 })) {
+ add_to_selection = false;
+ }
+ }
+ } else {
+ selection.clear();
+ }
+
+ // Modify the selection.
+ for (int x = region.position.x; x < region.get_end().x; x++) {
+ for (int y = region.position.y; y < region.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ coords = tile_set_atlas_source->get_tile_at_coords(coords);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ if (add_to_selection && !selection.has({ coords, 0 })) {
+ selection.insert({ coords, 0 });
+ } else if (!add_to_selection && selection.has({ coords, 0 })) {
+ selection.erase({ coords, 0 });
+ }
+ }
+ }
+ }
+ _update_tile_inspector();
+ _update_tile_id_label();
+ _update_current_tile_data_editor();
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action(false);
+ } break;
+ case DRAG_TYPE_MAY_POPUP_MENU: {
+ Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position();
+ TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 };
+ if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
+ selected.tile = tile_set_atlas_source->get_tile_at_coords(selected.tile);
+ }
+
+ // Set the selection if needed.
+ if (selection.size() <= 1) {
+ if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
+ undo_redo->create_action(TTR("Select tiles"));
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ selection.clear();
+ selection.insert(selected);
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action(false);
+ _update_tile_inspector();
+ _update_tile_id_label();
+ _update_current_tile_data_editor();
+ }
+ }
+
+ // Pops up the correct menu, depending on whether we have a tile or not.
+ if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) {
+ // We have a tile.
+ menu_option_coords = selected.tile;
+ menu_option_alternative = 0;
+ base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i()));
+ } else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ // We don't have a tile, but can create one.
+ menu_option_coords = hovered_base_tile_coords;
+ menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i()));
+ }
+ } break;
+ case DRAG_TYPE_RESIZE_TOP_LEFT:
+ case DRAG_TYPE_RESIZE_TOP:
+ case DRAG_TYPE_RESIZE_TOP_RIGHT:
+ case DRAG_TYPE_RESIZE_RIGHT:
+ case DRAG_TYPE_RESIZE_BOTTOM_RIGHT:
+ case DRAG_TYPE_RESIZE_BOTTOM:
+ case DRAG_TYPE_RESIZE_BOTTOM_LEFT:
+ case DRAG_TYPE_RESIZE_LEFT:
+ if (drag_start_tile_shape != Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile))) {
+ undo_redo->create_action(TTR("Resize a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile));
+ undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size);
+ Array array;
+ array.push_back(drag_start_tile_shape.position);
+ array.push_back(0);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", array);
+ undo_redo->commit_action(false);
+ }
+ break;
+ default:
+ break;
+ }
+
+ drag_modified_tiles.clear();
+ drag_type = DRAG_TYPE_NONE;
+ tile_atlas_control->set_default_cursor_shape(CURSOR_ARROW);
+}
+
+Map<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) {
+ // Group properties per tile.
+ Map<Vector2i, List<const PropertyInfo *>> per_tile;
+ for (const List<PropertyInfo>::Element *E_property = r_list.front(); E_property; E_property = E_property->next()) {
+ Vector<String> components = String(E_property->get().name).split("/", true, 1);
+ if (components.size() >= 1) {
+ Vector<String> coord_arr = components[0].split(":");
+ if (coord_arr.size() == 2 && coord_arr[0].is_valid_int() && coord_arr[1].is_valid_int()) {
+ Vector2i coords = Vector2i(coord_arr[0].to_int(), coord_arr[1].to_int());
+ per_tile[coords].push_back(&(E_property->get()));
+ }
+ }
+ }
+ return per_tile;
+}
+
+void TileSetAtlasSourceEditor::_menu_option(int p_option) {
+ switch (p_option) {
+ case TILE_DELETE: {
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+ undo_redo->create_action(TTR("Remove tile"));
+
+ // Remove tiles
+ Set<Vector2i> removed;
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative == 0) {
+ // Remove a tile.
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", selected.tile);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", selected.tile);
+ removed.insert(selected.tile);
+ if (per_tile.has(selected.tile)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ }
+
+ // Remove alternatives
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative > 0 && !removed.has(selected.tile)) {
+ // Remove an alternative tile.
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_alternative_tile", selected.tile, selected.alternative);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_alternative_tile", selected.tile, selected.alternative);
+ if (per_tile.has(selected.tile)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) {
+ Vector<String> components = E_property->get()->name.split("/", true, 2);
+ if (components.size() >= 2 && components[1].is_valid_int() && components[1].to_int() == selected.alternative) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ _update_fix_selected_and_hovered_tiles();
+ _update_tile_id_label();
+ } break;
+ case TILE_CREATE: {
+ undo_redo->create_action(TTR("Create a tile"));
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", menu_option_coords);
+ Array array;
+ array.push_back(menu_option_coords);
+ array.push_back(0);
+ undo_redo->add_do_method(this, "_set_selection_from_array", array);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", menu_option_coords);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action();
+ _update_tile_id_label();
+ } break;
+ case TILE_CREATE_ALTERNATIVE: {
+ undo_redo->create_action(TTR("Create tile alternatives"));
+ Array array;
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ if (E->get().alternative == 0) {
+ int next_id = tile_set_atlas_source->get_next_alternative_tile_id(E->get().tile);
+ undo_redo->add_do_method(tile_set_atlas_source, "create_alternative_tile", E->get().tile, next_id);
+ array.push_back(E->get().tile);
+ array.push_back(next_id);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_alternative_tile", E->get().tile, next_id);
+ }
+ }
+ undo_redo->add_do_method(this, "_set_selection_from_array", array);
+ undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array());
+ undo_redo->commit_action();
+ _update_tile_id_label();
+ } break;
+ case ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE: {
+ tile_set_atlas_source->clear_tiles_outside_texture();
+ } break;
+ case ADVANCED_AUTO_CREATE_TILES: {
+ _auto_create_tiles();
+ } break;
+ case ADVANCED_AUTO_REMOVE_TILES: {
+ _auto_remove_tiles();
+ } break;
+ }
+}
+
+void TileSetAtlasSourceEditor::_unhandled_key_input(const Ref<InputEvent> &p_event) {
+ // Check for shortcuts.
+ if (ED_IS_SHORTCUT("tiles_editor/delete_tile", p_event)) {
+ if (tools_button_group->get_pressed_button() == tool_select_button && !selection.is_empty()) {
+ _menu_option(TILE_DELETE);
+ accept_event();
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_set_selection_from_array(Array p_selection) {
+ ERR_FAIL_COND((p_selection.size() % 2) != 0);
+ selection.clear();
+ for (int i = 0; i < p_selection.size() / 2; i++) {
+ TileSelection selected = { p_selection[i * 2], p_selection[i * 2 + 1] };
+ if (tile_set_atlas_source->has_tile(selected.tile) && tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) {
+ selection.insert(selected);
+ }
+ }
+ _update_tile_inspector();
+ _update_tile_id_label();
+ _update_atlas_view();
+ _update_current_tile_data_editor();
+}
+
+Array TileSetAtlasSourceEditor::_get_selection_as_array() {
+ Array output;
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ output.push_back(E->get().tile);
+ output.push_back(E->get().alternative);
+ }
+ return output;
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_draw() {
+ // Colors.
+ 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);
+
+ // Draw the selected tile.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative == 0) {
+ // Draw the rect.
+ Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
+ tile_atlas_control->draw_rect(region, selection_color, false);
+ }
+ }
+
+ if (selection.size() == 1) {
+ // Draw the resize handles (only when it's possible to expand).
+ TileSelection selected = selection.front()->get();
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile);
+ Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom();
+ Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile);
+ Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0);
+ Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) };
+ Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) };
+ bool can_grow[4];
+ for (int i = 0; i < 4; i++) {
+ can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]);
+ can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1;
+ }
+ for (int i = 0; i < 4; i++) {
+ Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i];
+ if (can_grow[i] && can_grow[(i + 3) % 4]) {
+ tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false);
+ } else {
+ tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false);
+ }
+ Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4];
+ if (can_grow[i]) {
+ tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
+ } else {
+ tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false);
+ }
+ }
+ }
+ }
+
+ if (drag_type == DRAG_TYPE_REMOVE_TILES) {
+ // Draw the tiles to be removed.
+ for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) {
+ tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get()), Color(0.0, 0.0, 0.0), false);
+ }
+ } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) {
+ // Draw tiles to be removed.
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+
+ Color color = Color(0.0, 0.0, 0.0);
+ if (drag_type == DRAG_TYPE_RECT_SELECT) {
+ color = selection_color.lightened(0.2);
+ }
+
+ Set<Vector2i> to_paint;
+ for (int x = area.get_position().x; x < area.get_end().x; x++) {
+ for (int y = area.get_position().y; y < area.get_end().y; y++) {
+ Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y));
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ to_paint.insert(coords);
+ }
+ }
+ }
+
+ for (Set<Vector2i>::Element *E = to_paint.front(); E; E = E->next()) {
+ Vector2i coords = E->get();
+ tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(coords), color, false);
+ }
+ } else if (drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) {
+ // Draw tiles to be created.
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
+
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ for (int x = area.get_position().x; x < area.get_end().x; x++) {
+ for (int y = area.get_position().y; y < area.get_end().y; y++) {
+ Vector2i coords = Vector2i(x, y);
+ if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
+ Vector2i origin = margins + (coords * (tile_size + separation));
+ tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false);
+ }
+ }
+ }
+ }
+
+ // Draw the hovered tile.
+ if (drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT || drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) {
+ // Draw the rect.
+ Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos);
+ Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position());
+ Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs();
+ area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size()));
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
+ Vector2i origin = margins + (area.position * (tile_size + separation));
+ tile_atlas_control->draw_rect(Rect2i(origin, area.size * tile_size), Color(1.0, 1.0, 1.0), false);
+ } else {
+ Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+ if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) {
+ Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords);
+ if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) {
+ // Draw existing hovered tile.
+ tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile), Color(1.0, 1.0, 1.0), false);
+ } else {
+ // Draw empty tile, only in add/remove tiles mode.
+ if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i tile_size = tile_set_atlas_source->get_texture_region_size();
+ Vector2i origin = margins + (hovered_base_tile_coords * (tile_size + separation));
+ tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false);
+ }
+ }
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() {
+ if (current_tile_data_editor) {
+ // Draw the preview of the selected property.
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i coords = tile_set_atlas_source->get_tile_id(i);
+ Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(coords);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0);
+
+ Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
+ xform.translate(position);
+
+ if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, 0 })) {
+ continue;
+ }
+
+ TileMapCell cell;
+ cell.source_id = tile_set_atlas_source_id;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = 0;
+ current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell);
+ }
+
+ // Draw the selection on top of other.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ if (E->get().alternative != 0) {
+ continue;
+ }
+ Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(E->get().tile);
+ Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(E->get().tile, 0);
+
+ Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
+ xform.translate(position);
+
+ TileMapCell cell;
+ cell.source_id = tile_set_atlas_source_id;
+ cell.set_atlas_coords(E->get().tile);
+ cell.alternative_tile = 0;
+ current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell, true);
+ }
+ }
+
+ // Call the TileData's editor custom draw function.
+ if (tools_button_group->get_pressed_button() == tool_paint_button) {
+ Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
+ current_tile_data_editor->forward_draw_over_atlas(tile_atlas_view, tile_set_atlas_source, tile_atlas_control_unscaled, xform);
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) {
+ // Update the hovered alternative tile.
+ hovered_alternative_tile_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position());
+
+ // Forward the event to the current tile data editor if we are in the painting mode.
+ if (tools_button_group->get_pressed_button() == tool_paint_button) {
+ if (current_tile_data_editor) {
+ current_tile_data_editor->forward_painting_alternatives_gui_input(tile_atlas_view, tile_set_atlas_source, p_event);
+ }
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ tile_atlas_view->update();
+ return;
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ drag_type = DRAG_TYPE_NONE;
+
+ Vector2 mouse_local_pos = alternative_tiles_control->get_local_mouse_position();
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (mb->is_pressed()) {
+ // Left click pressed.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos);
+
+ selection.clear();
+ TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
+ if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
+ selection.insert(selected);
+ }
+
+ _update_tile_inspector();
+ _update_tile_id_label();
+ }
+ }
+ } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
+ if (mb->is_pressed()) {
+ // Right click pressed
+ Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos);
+
+ selection.clear();
+ TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) };
+ if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) {
+ selection.insert(selected);
+ }
+
+ _update_tile_inspector();
+ _update_tile_id_label();
+
+ if (selection.size() == 1) {
+ selected = selection.front()->get();
+ menu_option_coords = selected.tile;
+ menu_option_alternative = selected.alternative;
+ alternative_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i()));
+ }
+ }
+ }
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() {
+ hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ tile_atlas_control->update();
+ tile_atlas_control_unscaled->update();
+ alternative_tiles_control->update();
+ alternative_tiles_control_unscaled->update();
+}
+
+void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() {
+ 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);
+
+ // Update the hovered alternative tile.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ // Draw hovered tile.
+ Vector2i coords = Vector2(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y);
+ if (coords != TileSetSource::INVALID_ATLAS_COORDS) {
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, hovered_alternative_tile_coords.z);
+ if (rect != Rect2i()) {
+ alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false);
+ }
+ }
+
+ // Draw selected tile.
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ TileSelection selected = E->get();
+ if (selected.alternative >= 1) {
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(selected.tile, selected.alternative);
+ if (rect != Rect2i()) {
+ alternative_tiles_control->draw_rect(rect, selection_color, false);
+ }
+ }
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() {
+ // Draw the preview of the selected property.
+ if (current_tile_data_editor) {
+ // Draw the preview of the currently selected property.
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i coords = tile_set_atlas_source->get_tile_id(i);
+ for (int j = 0; j < tile_set_atlas_source->get_alternative_tiles_count(coords); j++) {
+ int alternative_tile = tile_set_atlas_source->get_alternative_tile_id(coords, j);
+ if (alternative_tile == 0) {
+ continue;
+ }
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile);
+ Vector2 position = (rect.get_position() + rect.get_end()) / 2;
+
+ Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform();
+ xform.translate(position);
+
+ if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, alternative_tile })) {
+ continue;
+ }
+
+ TileMapCell cell;
+ cell.source_id = tile_set_atlas_source_id;
+ cell.set_atlas_coords(coords);
+ cell.alternative_tile = alternative_tile;
+ current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell);
+ }
+ }
+
+ // Draw the selection on top of other.
+ if (tools_button_group->get_pressed_button() == tool_select_button) {
+ for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) {
+ if (E->get().alternative == 0) {
+ continue;
+ }
+ Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().tile, E->get().alternative);
+ Vector2 position = (rect.get_position() + rect.get_end()) / 2;
+
+ Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform();
+ xform.translate(position);
+
+ TileMapCell cell;
+ cell.source_id = tile_set_atlas_source_id;
+ cell.set_atlas_coords(E->get().tile);
+ cell.alternative_tile = E->get().alternative;
+ current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell, true);
+ }
+ }
+
+ // Call the TileData's editor custom draw function.
+ if (tools_button_group->get_pressed_button() == tool_paint_button) {
+ Transform2D xform = tile_atlas_control->get_parent_control()->get_transform();
+ current_tile_data_editor->forward_draw_over_alternatives(tile_atlas_view, tile_set_atlas_source, alternative_tiles_control_unscaled, xform);
+ }
+ }
+}
+
+void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() {
+ tile_set_atlas_source_changed_needs_update = true;
+}
+
+void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) {
+ if (p_what == "texture" && !atlas_source_proxy_object->get("texture").is_null()) {
+ confirm_auto_create_tiles->popup_centered();
+ } else if (p_what == "id") {
+ emit_signal("source_id_changed", atlas_source_proxy_object->get_id());
+ }
+}
+
+void TileSetAtlasSourceEditor::_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));
+
+ AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited);
+ if (tile_data) {
+ Vector<String> components = String(p_property).split("/", true, 2);
+ if (components.size() == 2 && components[1] == "polygons_count") {
+ int layer_index = components[0].trim_prefix("physics_layer_").to_int();
+ int new_polygons_count = p_new_value;
+ int old_polygons_count = tile_data->get(vformat("physics_layer_%d/polygons_count", layer_index));
+ if (new_polygons_count < old_polygons_count) {
+ for (int i = new_polygons_count - 1; i < old_polygons_count; i++) {
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, i));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i));
+ ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i));
+ }
+ }
+ } else if (p_property == "terrain_set") {
+ int current_terrain_set = tile_data->get("terrain_set");
+ for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) {
+ TileSet::CellNeighbor bit = TileSet::CellNeighbor(i);
+ if (tile_set->is_valid_peering_bit_terrain(current_terrain_set, bit)) {
+ ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]));
+ }
+ }
+ }
+ }
+#undef ADD_UNDO
+}
+
+void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set.is_valid());
+ ERR_FAIL_COND(!p_tile_set_atlas_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
+
+ if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id) {
+ return;
+ }
+
+ // Remove listener for old objects.
+ if (tile_set_atlas_source) {
+ tile_set_atlas_source->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed));
+ }
+
+ // Clear the selection.
+ selection.clear();
+
+ // Change the edited object.
+ tile_set = p_tile_set;
+ tile_set_atlas_source = p_tile_set_atlas_source;
+ tile_set_atlas_source_id = p_source_id;
+
+ // Add the listener again.
+ if (tile_set_atlas_source) {
+ tile_set_atlas_source->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed));
+ }
+
+ // Update everything.
+ _update_source_inspector();
+
+ // Update the selected tile.
+ _update_fix_selected_and_hovered_tiles();
+ _update_tile_id_label();
+ _update_atlas_view();
+ _update_atlas_source_inspector();
+ _update_tile_inspector();
+ _update_tile_data_editors();
+ _update_current_tile_data_editor();
+}
+
+void TileSetAtlasSourceEditor::init_source() {
+ confirm_auto_create_tiles->popup_centered();
+}
+
+void TileSetAtlasSourceEditor::_auto_create_tiles() {
+ if (!tile_set_atlas_source) {
+ return;
+ }
+
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+ Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+ undo_redo->create_action(TTR("Create tiles in non-transparent texture regions"));
+ for (int y = 0; y < grid_size.y; y++) {
+ for (int x = 0; x < grid_size.x; x++) {
+ // Check if we have a tile at the coord
+ Vector2i coords = Vector2i(x, y);
+ if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) {
+ // Check if the texture is empty at the given coords.
+ Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size);
+ bool is_opaque = false;
+ for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) {
+ for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) {
+ if (texture->is_pixel_opaque(region_x, region_y)) {
+ is_opaque = true;
+ break;
+ }
+ }
+ if (is_opaque) {
+ break;
+ }
+ }
+
+ // If we do have opaque pixels, create a tile.
+ if (is_opaque) {
+ undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords);
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ }
+}
+
+void TileSetAtlasSourceEditor::_auto_remove_tiles() {
+ if (!tile_set_atlas_source) {
+ return;
+ }
+
+ Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
+ if (texture.is_valid()) {
+ Vector2i margins = tile_set_atlas_source->get_margins();
+ Vector2i separation = tile_set_atlas_source->get_separation();
+ Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size();
+ Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size();
+
+ undo_redo->create_action(TTR("Remove tiles in fully transparent texture regions"));
+
+ List<PropertyInfo> list;
+ tile_set_atlas_source->get_property_list(&list);
+ Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source);
+
+ for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
+ Vector2i coords = tile_set_atlas_source->get_tile_id(i);
+ Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(coords);
+
+ // Skip tiles outside texture.
+ if ((coords.x + size_in_atlas.x) > grid_size.x || (coords.y + size_in_atlas.y) > grid_size.y) {
+ continue;
+ }
+
+ // Check if the texture is empty at the given coords.
+ Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size * size_in_atlas);
+ bool is_opaque = false;
+ for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) {
+ for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) {
+ if (texture->is_pixel_opaque(region_x, region_y)) {
+ is_opaque = true;
+ break;
+ }
+ }
+ if (is_opaque) {
+ break;
+ }
+ }
+
+ // If we do have opaque pixels, create a tile.
+ if (!is_opaque) {
+ undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords);
+ undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords);
+ if (per_tile.has(coords)) {
+ for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) {
+ String property = E_property->get()->name;
+ Variant value = tile_set_atlas_source->get(property);
+ if (value.get_type() != Variant::NIL) {
+ undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value);
+ }
+ }
+ }
+ }
+ }
+ undo_redo->commit_action();
+ }
+}
+
+void TileSetAtlasSourceEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ tool_setup_atlas_source_button->set_icon(get_theme_icon("Tools", "EditorIcons"));
+ tool_select_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons"));
+ tool_paint_button->set_icon(get_theme_icon("CanvasItem", "EditorIcons"));
+
+ tools_settings_erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons"));
+
+ tool_advanced_menu_buttom->set_icon(get_theme_icon("GuiTabMenu", "EditorIcons"));
+
+ resize_handle = get_theme_icon("EditorHandle", "EditorIcons");
+ resize_handle_disabled = get_theme_icon("EditorHandleDisabled", "EditorIcons");
+ break;
+ case NOTIFICATION_INTERNAL_PROCESS:
+ if (tile_set_atlas_source_changed_needs_update) {
+ // Update everything.
+ _update_source_inspector();
+
+ // Update the selected tile.
+ _update_fix_selected_and_hovered_tiles();
+ _update_tile_id_label();
+ _update_atlas_view();
+ _update_atlas_source_inspector();
+ _update_tile_inspector();
+ _update_tile_data_editors();
+ _update_current_tile_data_editor();
+
+ tile_set_atlas_source_changed_needs_update = false;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void TileSetAtlasSourceEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileSetAtlasSourceEditor::_unhandled_key_input);
+ ClassDB::bind_method(D_METHOD("_set_selection_from_array"), &TileSetAtlasSourceEditor::_set_selection_from_array);
+
+ ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
+}
+
+TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
+ set_process_unhandled_key_input(true);
+ set_process_internal(true);
+
+ // -- Right side --
+ HSplitContainer *split_container_right_side = memnew(HSplitContainer);
+ split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
+ add_child(split_container_right_side);
+
+ // Middle panel.
+ ScrollContainer *middle_panel = memnew(ScrollContainer);
+ middle_panel->set_enable_h_scroll(false);
+ middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE);
+ split_container_right_side->add_child(middle_panel);
+
+ VBoxContainer *middle_vbox_container = memnew(VBoxContainer);
+ middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ middle_panel->add_child(middle_vbox_container);
+
+ // Tile inspector.
+ tile_inspector_label = memnew(Label);
+ tile_inspector_label->set_text(TTR("Tile Properties:"));
+ middle_vbox_container->add_child(tile_inspector_label);
+
+ tile_proxy_object = memnew(AtlasTileProxyObject(this));
+ tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1));
+
+ tile_inspector = memnew(EditorInspector);
+ tile_inspector->set_undo_redo(undo_redo);
+ tile_inspector->set_enable_v_scroll(false);
+ tile_inspector->edit(tile_proxy_object);
+ tile_inspector->set_use_folding(true);
+ tile_inspector->connect("property_selected", callable_mp(this, &TileSetAtlasSourceEditor::_inspector_property_selected));
+ middle_vbox_container->add_child(tile_inspector);
+
+ tile_inspector_no_tile_selected_label = memnew(Label);
+ tile_inspector_no_tile_selected_label->set_align(Label::ALIGN_CENTER);
+ tile_inspector_no_tile_selected_label->set_text(TTR("No tile selected."));
+ middle_vbox_container->add_child(tile_inspector_no_tile_selected_label);
+
+ // Property values palette.
+ tile_data_editors_popup = memnew(Popup);
+
+ tile_data_editors_label = memnew(Label);
+ tile_data_editors_label->set_text(TTR("Paint Properties:"));
+ middle_vbox_container->add_child(tile_data_editors_label);
+
+ tile_data_editor_dropdown_button = memnew(Button);
+ tile_data_editor_dropdown_button->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw));
+ tile_data_editor_dropdown_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed));
+ middle_vbox_container->add_child(tile_data_editor_dropdown_button);
+ tile_data_editor_dropdown_button->add_child(tile_data_editors_popup);
+
+ tile_data_editors_tree = memnew(Tree);
+ tile_data_editors_tree->set_hide_root(true);
+ tile_data_editors_tree->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ tile_data_editors_tree->set_h_scroll_enabled(false);
+ tile_data_editors_tree->set_v_scroll_enabled(false);
+ tile_data_editors_tree->connect("item_selected", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editors_tree_selected));
+ tile_data_editors_popup->add_child(tile_data_editors_tree);
+
+ tile_data_painting_editor_container = memnew(VBoxContainer);
+ tile_data_painting_editor_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ middle_vbox_container->add_child(tile_data_painting_editor_container);
+
+ // Atlas source inspector.
+ atlas_source_inspector_label = memnew(Label);
+ atlas_source_inspector_label->set_text(TTR("Atlas Properties:"));
+ middle_vbox_container->add_child(atlas_source_inspector_label);
+
+ atlas_source_proxy_object = memnew(TileSetAtlasSourceProxyObject());
+ atlas_source_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed));
+
+ atlas_source_inspector = memnew(EditorInspector);
+ atlas_source_inspector->set_undo_redo(undo_redo);
+ atlas_source_inspector->set_enable_v_scroll(false);
+ atlas_source_inspector->edit(atlas_source_proxy_object);
+ middle_vbox_container->add_child(atlas_source_inspector);
+
+ // Right panel.
+ VBoxContainer *right_panel = memnew(VBoxContainer);
+ right_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ right_panel->set_v_size_flags(SIZE_EXPAND_FILL);
+ split_container_right_side->add_child(right_panel);
+
+ // -- Dialogs --
+ confirm_auto_create_tiles = memnew(AcceptDialog);
+ confirm_auto_create_tiles->set_title(TTR("Create tiles automatically in non-transparent texture regions?"));
+ confirm_auto_create_tiles->set_text(TTR("The atlas's texture was modified.\nWould you like to automatically create tiles in the atlas?"));
+ confirm_auto_create_tiles->get_ok_button()->set_text(TTR("Yes"));
+ confirm_auto_create_tiles->add_cancel_button()->set_text(TTR("No"));
+ confirm_auto_create_tiles->connect("confirmed", callable_mp(this, &TileSetAtlasSourceEditor::_auto_create_tiles));
+ add_child(confirm_auto_create_tiles);
+
+ // -- Toolbox --
+ tools_button_group.instantiate();
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles).unbind(1));
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label).unbind(1));
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_source_inspector).unbind(1));
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector).unbind(1));
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_data_editors).unbind(1));
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_current_tile_data_editor).unbind(1));
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1));
+ tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar).unbind(1));
+
+ toolbox = memnew(HBoxContainer);
+ right_panel->add_child(toolbox);
+
+ tool_setup_atlas_source_button = memnew(Button);
+ tool_setup_atlas_source_button->set_flat(true);
+ tool_setup_atlas_source_button->set_toggle_mode(true);
+ tool_setup_atlas_source_button->set_pressed(true);
+ tool_setup_atlas_source_button->set_button_group(tools_button_group);
+ tool_setup_atlas_source_button->set_tooltip(TTR("Atlas Setup. Add/Remove tiles tool (use the shift key to create big tiles, control for rectangle editing)."));
+ toolbox->add_child(tool_setup_atlas_source_button);
+
+ tool_select_button = memnew(Button);
+ tool_select_button->set_flat(true);
+ tool_select_button->set_toggle_mode(true);
+ tool_select_button->set_pressed(false);
+ tool_select_button->set_button_group(tools_button_group);
+ tool_select_button->set_tooltip(TTR("Select tiles."));
+ toolbox->add_child(tool_select_button);
+
+ tool_paint_button = memnew(Button);
+ tool_paint_button->set_flat(true);
+ tool_paint_button->set_toggle_mode(true);
+ tool_paint_button->set_button_group(tools_button_group);
+ tool_paint_button->set_tooltip(TTR("Paint properties."));
+ toolbox->add_child(tool_paint_button);
+
+ // Tool settings.
+ tool_settings = memnew(HBoxContainer);
+ toolbox->add_child(tool_settings);
+
+ tool_settings_vsep = memnew(VSeparator);
+ tool_settings->add_child(tool_settings_vsep);
+
+ tool_settings_tile_data_toolbar_container = memnew(HBoxContainer);
+ tool_settings->add_child(tool_settings_tile_data_toolbar_container);
+
+ tools_settings_erase_button = memnew(Button);
+ tools_settings_erase_button->set_flat(true);
+ tools_settings_erase_button->set_toggle_mode(true);
+ tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E));
+ tools_settings_erase_button->set_shortcut_context(this);
+ tool_settings->add_child(tools_settings_erase_button);
+
+ tool_advanced_menu_buttom = memnew(MenuButton);
+ tool_advanced_menu_buttom->set_flat(true);
+ tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE);
+ tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true);
+ tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES);
+ tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES);
+ tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ toolbox->add_child(tool_advanced_menu_buttom);
+
+ _update_toolbar();
+
+ // Right side of toolbar.
+ Control *middle_space = memnew(Control);
+ middle_space->set_h_size_flags(SIZE_EXPAND_FILL);
+ toolbox->add_child(middle_space);
+
+ tool_tile_id_label = memnew(Label);
+ tool_tile_id_label->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ toolbox->add_child(tool_tile_id_label);
+ _update_tile_id_label();
+
+ // Tile atlas view.
+ tile_atlas_view = memnew(TileAtlasView);
+ tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform));
+ tile_atlas_view->connect("transform_changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed).unbind(2));
+ right_panel->add_child(tile_atlas_view);
+
+ base_tile_popup_menu = memnew(PopupMenu);
+ base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE), TILE_DELETE);
+ base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE);
+ base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ tile_atlas_view->add_child(base_tile_popup_menu);
+
+ empty_base_tile_popup_menu = memnew(PopupMenu);
+ empty_base_tile_popup_menu->add_item(TTR("Create a Tile"), TILE_CREATE);
+ empty_base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ tile_atlas_view->add_child(empty_base_tile_popup_menu);
+
+ tile_atlas_control = memnew(Control);
+ tile_atlas_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_draw));
+ tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited));
+ tile_atlas_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_gui_input));
+ tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control);
+
+ tile_atlas_control_unscaled = memnew(Control);
+ tile_atlas_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ tile_atlas_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw));
+ tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control_unscaled, false);
+ tile_atlas_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+
+ alternative_tile_popup_menu = memnew(PopupMenu);
+ alternative_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete_tile", TTR("Delete"), KEY_DELETE), TILE_DELETE);
+ alternative_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ tile_atlas_view->add_child(alternative_tile_popup_menu);
+
+ alternative_tiles_control = memnew(Control);
+ alternative_tiles_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_draw));
+ alternative_tiles_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited));
+ alternative_tiles_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input));
+ tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control);
+
+ alternative_tiles_control_unscaled = memnew(Control);
+ alternative_tiles_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
+ alternative_tiles_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw));
+ tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control_unscaled, false);
+ alternative_tiles_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+
+ tile_atlas_view_missing_source_label = memnew(Label);
+ tile_atlas_view_missing_source_label->set_text(TTR("Add or select an atlas texture to the left panel."));
+ tile_atlas_view_missing_source_label->set_align(Label::ALIGN_CENTER);
+ tile_atlas_view_missing_source_label->set_valign(Label::VALIGN_CENTER);
+ tile_atlas_view_missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_atlas_view_missing_source_label->hide();
+ right_panel->add_child(tile_atlas_view_missing_source_label);
+}
+
+TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() {
+ memdelete(tile_proxy_object);
+ memdelete(atlas_source_proxy_object);
+}
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h
new file mode 100644
index 0000000000..dbb0756a16
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h
@@ -0,0 +1,283 @@
+/*************************************************************************/
+/* tile_set_atlas_source_editor.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 TILE_SET_ATLAS_SOURCE_EDITOR_H
+#define TILE_SET_ATLAS_SOURCE_EDITOR_H
+
+#include "tile_atlas_view.h"
+#include "tile_data_editors.h"
+
+#include "editor/editor_node.h"
+#include "scene/gui/split_container.h"
+#include "scene/resources/tile_set.h"
+
+class TileSet;
+
+class TileSetAtlasSourceEditor : public HBoxContainer {
+ GDCLASS(TileSetAtlasSourceEditor, HBoxContainer);
+
+private:
+ // A class to store which tiles are selected.
+ struct TileSelection {
+ Vector2i tile = TileSetSource::INVALID_ATLAS_COORDS;
+ int alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
+
+ bool operator<(const TileSelection &p_other) const {
+ if (tile == p_other.tile) {
+ return alternative < p_other.alternative;
+ } else {
+ return tile < p_other.tile;
+ }
+ }
+ };
+
+ // -- Proxy object for an atlas source, needed by the inspector --
+ class TileSetAtlasSourceProxyObject : public Object {
+ GDCLASS(TileSetAtlasSourceProxyObject, Object);
+
+ private:
+ Ref<TileSet> tile_set;
+ TileSetAtlasSource *tile_set_atlas_source = nullptr;
+ int source_id = -1;
+
+ protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+ static void _bind_methods();
+
+ public:
+ void set_id(int p_id);
+ int get_id();
+
+ void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id);
+ };
+
+ // -- Proxy object for a tile, needed by the inspector --
+ class AtlasTileProxyObject : public Object {
+ GDCLASS(AtlasTileProxyObject, Object);
+
+ private:
+ TileSetAtlasSourceEditor *tiles_set_atlas_source_editor;
+
+ TileSetAtlasSource *tile_set_atlas_source = nullptr;
+ Set<TileSelection> tiles = Set<TileSelection>();
+
+ protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+ static void _bind_methods();
+
+ public:
+ // Update the proxyed object.
+ void edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles = Set<TileSelection>());
+
+ AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) {
+ tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor;
+ }
+ };
+
+ Ref<TileSet> tile_set;
+ TileSetAtlasSource *tile_set_atlas_source = nullptr;
+ int tile_set_atlas_source_id = -1;
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ bool tile_set_atlas_source_changed_needs_update = false;
+
+ // -- Properties painting --
+ VBoxContainer *tile_data_painting_editor_container;
+ Label *tile_data_editors_label;
+ Button *tile_data_editor_dropdown_button;
+ Popup *tile_data_editors_popup;
+ Tree *tile_data_editors_tree;
+ void _tile_data_editor_dropdown_button_draw();
+ void _tile_data_editor_dropdown_button_pressed();
+
+ // -- Tile data editors --
+ String current_property;
+ Control *current_tile_data_editor_toolbar = nullptr;
+ Map<String, TileDataEditor *> tile_data_editors;
+ TileDataEditor *current_tile_data_editor = nullptr;
+ void _tile_data_editors_tree_selected();
+
+ // -- Inspector --
+ AtlasTileProxyObject *tile_proxy_object;
+ Label *tile_inspector_label;
+ EditorInspector *tile_inspector;
+ Label *tile_inspector_no_tile_selected_label;
+ String selected_property;
+ void _inspector_property_selected(String p_property);
+
+ TileSetAtlasSourceProxyObject *atlas_source_proxy_object;
+ Label *atlas_source_inspector_label;
+ EditorInspector *atlas_source_inspector;
+
+ // -- Atlas view --
+ HBoxContainer *toolbox;
+ Label *tile_atlas_view_missing_source_label;
+ TileAtlasView *tile_atlas_view;
+
+ // Dragging
+ enum DragType {
+ DRAG_TYPE_NONE = 0,
+ DRAG_TYPE_CREATE_TILES,
+ DRAG_TYPE_CREATE_TILES_USING_RECT,
+ DRAG_TYPE_CREATE_BIG_TILE,
+ DRAG_TYPE_REMOVE_TILES,
+ DRAG_TYPE_REMOVE_TILES_USING_RECT,
+
+ DRAG_TYPE_MOVE_TILE,
+
+ DRAG_TYPE_RECT_SELECT,
+
+ DRAG_TYPE_MAY_POPUP_MENU,
+
+ // Warning: keep in this order.
+ DRAG_TYPE_RESIZE_TOP_LEFT,
+ DRAG_TYPE_RESIZE_TOP,
+ DRAG_TYPE_RESIZE_TOP_RIGHT,
+ DRAG_TYPE_RESIZE_RIGHT,
+ DRAG_TYPE_RESIZE_BOTTOM_RIGHT,
+ DRAG_TYPE_RESIZE_BOTTOM,
+ DRAG_TYPE_RESIZE_BOTTOM_LEFT,
+ DRAG_TYPE_RESIZE_LEFT,
+ };
+ DragType drag_type = DRAG_TYPE_NONE;
+ Vector2i drag_start_mouse_pos;
+ Vector2i drag_last_mouse_pos;
+ Vector2i drag_current_tile;
+
+ Rect2i drag_start_tile_shape;
+ Set<Vector2i> drag_modified_tiles;
+ void _end_dragging();
+
+ Map<Vector2i, List<const PropertyInfo *>> _group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas);
+
+ // Popup functions.
+ enum MenuOptions {
+ TILE_CREATE,
+ TILE_CREATE_ALTERNATIVE,
+ TILE_DELETE,
+
+ ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE,
+ ADVANCED_AUTO_CREATE_TILES,
+ ADVANCED_AUTO_REMOVE_TILES,
+ };
+ Vector2i menu_option_coords;
+ int menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE;
+ void _menu_option(int p_option);
+
+ // Tool buttons.
+ Ref<ButtonGroup> tools_button_group;
+ Button *tool_setup_atlas_source_button;
+ Button *tool_select_button;
+ Button *tool_paint_button;
+ Label *tool_tile_id_label;
+
+ // Tool settings.
+ HBoxContainer *tool_settings;
+ VSeparator *tool_settings_vsep;
+ HBoxContainer *tool_settings_tile_data_toolbar_container;
+ Button *tools_settings_erase_button;
+ MenuButton *tool_advanced_menu_buttom;
+
+ // Selection.
+ Set<TileSelection> selection;
+
+ void _set_selection_from_array(Array p_selection);
+ Array _get_selection_as_array();
+
+ // A control on the tile atlas to draw and handle input events.
+ Vector2i hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS;
+
+ PopupMenu *base_tile_popup_menu;
+ PopupMenu *empty_base_tile_popup_menu;
+ Ref<Texture2D> resize_handle;
+ Ref<Texture2D> resize_handle_disabled;
+ Control *tile_atlas_control;
+ Control *tile_atlas_control_unscaled;
+ void _tile_atlas_control_draw();
+ void _tile_atlas_control_unscaled_draw();
+ void _tile_atlas_control_mouse_exited();
+ void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event);
+ void _tile_atlas_view_transform_changed();
+
+ // A control over the alternative tiles.
+ Vector3i hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE);
+
+ PopupMenu *alternative_tile_popup_menu;
+ Control *alternative_tiles_control;
+ Control *alternative_tiles_control_unscaled;
+ void _tile_alternatives_control_draw();
+ void _tile_alternatives_control_unscaled_draw();
+ void _tile_alternatives_control_mouse_exited();
+ void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event);
+
+ // -- Update functions --
+ void _update_tile_id_label();
+ void _update_source_inspector();
+ void _update_fix_selected_and_hovered_tiles();
+ void _update_atlas_source_inspector();
+ void _update_tile_inspector();
+ void _update_tile_data_editors();
+ void _update_current_tile_data_editor();
+ void _update_manage_tile_properties_button();
+ void _update_atlas_view();
+ void _update_toolbar();
+
+ // -- input events --
+ void _unhandled_key_input(const Ref<InputEvent> &p_event);
+
+ // -- Misc --
+ void _auto_create_tiles();
+ void _auto_remove_tiles();
+ AcceptDialog *confirm_auto_create_tiles;
+
+ void _tile_set_atlas_source_changed();
+ void _atlas_source_proxy_object_changed(String p_what);
+
+ void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id);
+ void init_source();
+
+ TileSetAtlasSourceEditor();
+ ~TileSetAtlasSourceEditor();
+};
+
+#endif // TILE_SET_ATLAS_SOURCE_EDITOR_H
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
new file mode 100644
index 0000000000..2c2ebd107f
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -0,0 +1,512 @@
+/*************************************************************************/
+/* tile_set_editor.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 "tile_set_editor.h"
+
+#include "tile_data_editors.h"
+#include "tiles_editor_plugin.h"
+
+#include "editor/editor_scale.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/control.h"
+#include "scene/gui/tab_container.h"
+
+TileSetEditor *TileSetEditor::singleton = nullptr;
+
+void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ if (!can_drop_data_fw(p_point, p_data, p_from)) {
+ return;
+ }
+
+ if (p_from == sources_list) {
+ // Handle dropping a texture in the list of atlas resources.
+ int source_id = -1;
+ int added = 0;
+ Dictionary d = p_data;
+ Vector<String> files = d["files"];
+ for (int i = 0; i < files.size(); i++) {
+ Ref<Texture2D> resource = ResourceLoader::load(files[i]);
+ if (resource.is_valid()) {
+ // Retrieve the id for the next created source.
+ source_id = tile_set->get_next_source_id();
+
+ // Actually create the new source.
+ Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
+ atlas_source->set_texture(resource);
+ undo_redo->create_action(TTR("Add a new atlas source"));
+ undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id);
+ undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size());
+ undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
+ undo_redo->commit_action();
+ added += 1;
+ }
+ }
+
+ if (added == 1) {
+ tile_set_atlas_source_editor->init_source();
+ }
+
+ // Update the selected source (thus triggering an update).
+ _update_atlas_sources_list(source_id);
+ }
+}
+
+bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
+ ERR_FAIL_COND_V(!tile_set.is_valid(), false);
+
+ if (p_from == sources_list) {
+ Dictionary d = p_data;
+
+ if (!d.has("type")) {
+ return false;
+ }
+
+ // Check if we have a Texture2D.
+ if (String(d["type"]) == "files") {
+ Vector<String> files = d["files"];
+
+ if (files.size() == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < files.size(); i++) {
+ String file = files[i];
+ String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
+
+ if (!ClassDB::is_parent_class(ftype, "Texture2D")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void TileSetEditor::_update_atlas_sources_list(int force_selected_id) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Get the previously selected id.
+ int old_selected = -1;
+ if (sources_list->get_current() >= 0) {
+ int source_id = sources_list->get_item_metadata(sources_list->get_current());
+ if (tile_set->has_source(source_id)) {
+ old_selected = source_id;
+ }
+ }
+
+ int to_select = -1;
+ if (force_selected_id >= 0) {
+ to_select = force_selected_id;
+ } else if (old_selected >= 0) {
+ to_select = old_selected;
+ }
+
+ // Clear the list.
+ sources_list->clear();
+
+ // Update the atlas sources.
+ for (int i = 0; i < tile_set->get_source_count(); i++) {
+ int source_id = tile_set->get_source_id(i);
+
+ TileSetSource *source = *tile_set->get_source(source_id);
+
+ Ref<Texture2D> texture;
+ String item_text;
+
+ // Atlas source.
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ texture = atlas_source->get_texture();
+ if (texture.is_valid()) {
+ item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id);
+ } else {
+ item_text = vformat(TTR("No Texture Atlas Source (id:%d)"), source_id);
+ }
+ }
+
+ // Scene collection source.
+ TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source);
+ if (scene_collection_source) {
+ texture = get_theme_icon("PackedScene", "EditorIcons");
+ item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id);
+ }
+
+ // Use default if not valid.
+ if (item_text.is_empty()) {
+ item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id);
+ }
+ if (!texture.is_valid()) {
+ texture = missing_texture_texture;
+ }
+
+ sources_list->add_item(item_text, texture);
+ sources_list->set_item_metadata(i, source_id);
+ }
+
+ // Set again the current selected item if needed.
+ if (to_select >= 0) {
+ for (int i = 0; i < sources_list->get_item_count(); i++) {
+ if ((int)sources_list->get_item_metadata(i) == to_select) {
+ sources_list->set_current(i);
+ if (old_selected != to_select) {
+ sources_list->emit_signal("item_selected", sources_list->get_current());
+ }
+ break;
+ }
+ }
+ }
+
+ // If nothing is selected, select the first entry.
+ if (sources_list->get_current() < 0 && sources_list->get_item_count() > 0) {
+ sources_list->set_current(0);
+ if (old_selected != int(sources_list->get_item_metadata(0))) {
+ sources_list->emit_signal("item_selected", sources_list->get_current());
+ }
+ }
+
+ // If there is no source left, hide all editors and show the label.
+ _source_selected(sources_list->get_current());
+
+ // Synchronize the lists.
+ TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current());
+}
+
+void TileSetEditor::_source_selected(int p_source_index) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Update the selected source.
+ sources_delete_button->set_disabled(p_source_index < 0);
+
+ if (p_source_index >= 0) {
+ int source_id = sources_list->get_item_metadata(p_source_index);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id));
+ TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(*tile_set->get_source(source_id));
+ if (atlas_source) {
+ no_source_selected_label->hide();
+ tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id);
+ tile_set_atlas_source_editor->show();
+ tile_set_scenes_collection_source_editor->hide();
+ } else if (scenes_collection_source) {
+ no_source_selected_label->hide();
+ tile_set_atlas_source_editor->hide();
+ tile_set_scenes_collection_source_editor->edit(*tile_set, scenes_collection_source, source_id);
+ tile_set_scenes_collection_source_editor->show();
+ } else {
+ no_source_selected_label->show();
+ tile_set_atlas_source_editor->hide();
+ tile_set_scenes_collection_source_editor->hide();
+ }
+ } else {
+ no_source_selected_label->show();
+ tile_set_atlas_source_editor->hide();
+ tile_set_scenes_collection_source_editor->hide();
+ }
+}
+
+void TileSetEditor::_source_add_id_pressed(int p_id_pressed) {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ switch (p_id_pressed) {
+ case 0: {
+ int source_id = tile_set->get_next_source_id();
+
+ Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource);
+
+ // Add a new source.
+ undo_redo->create_action(TTR("Add atlas source"));
+ undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id);
+ undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size());
+ undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
+ undo_redo->commit_action();
+
+ _update_atlas_sources_list(source_id);
+ } break;
+ case 1: {
+ int source_id = tile_set->get_next_source_id();
+
+ Ref<TileSetScenesCollectionSource> scene_collection_source = memnew(TileSetScenesCollectionSource);
+
+ // Add a new source.
+ undo_redo->create_action(TTR("Add atlas source"));
+ undo_redo->add_do_method(*tile_set, "add_source", scene_collection_source, source_id);
+ undo_redo->add_undo_method(*tile_set, "remove_source", source_id);
+ undo_redo->commit_action();
+
+ _update_atlas_sources_list(source_id);
+ } break;
+ default:
+ ERR_FAIL();
+ }
+}
+
+void TileSetEditor::_source_delete_pressed() {
+ ERR_FAIL_COND(!tile_set.is_valid());
+
+ // Update the selected source.
+ int to_delete = sources_list->get_item_metadata(sources_list->get_current());
+
+ Ref<TileSetSource> source = tile_set->get_source(to_delete);
+
+ // Remove the source.
+ undo_redo->create_action(TTR("Remove source"));
+ undo_redo->add_do_method(*tile_set, "remove_source", to_delete);
+ undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete);
+ undo_redo->commit_action();
+
+ _update_atlas_sources_list();
+}
+
+void TileSetEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ sources_delete_button->set_icon(get_theme_icon("Remove", "EditorIcons"));
+ sources_add_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+ missing_texture_texture = get_theme_icon("TileSet", "EditorIcons");
+ break;
+ case NOTIFICATION_INTERNAL_PROCESS:
+ if (tile_set_changed_needs_update) {
+ if (tile_set.is_valid()) {
+ tile_set->set_edited(true);
+ }
+ _update_atlas_sources_list();
+ tile_set_changed_needs_update = false;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void TileSetEditor::_tile_set_changed() {
+ tile_set_changed_needs_update = true;
+}
+
+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));
+ TileSet *tile_set = Object::cast_to<TileSet>(p_edited);
+ if (tile_set) {
+ Vector<String> components = p_property.split("/", true, 3);
+ 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);
+
+ 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()))) {
+ ADD_UNDO(tile_data, "terrain_set");
+ 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 (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));
+ }
+ }
+ }
+ }
+ }
+ }
+#undef ADD_UNDO
+}
+
+void TileSetEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetEditor::drop_data_fw);
+}
+
+void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
+ if (p_tile_set == tile_set) {
+ return;
+ }
+
+ // Remove listener.
+ if (tile_set.is_valid()) {
+ tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
+ }
+
+ // Change the edited object.
+ tile_set = p_tile_set;
+
+ // Add the listener again.
+ if (tile_set.is_valid()) {
+ tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
+ _update_atlas_sources_list();
+ }
+
+ tile_set_atlas_source_editor->hide();
+ tile_set_scenes_collection_source_editor->hide();
+ no_source_selected_label->show();
+}
+
+TileSetEditor::TileSetEditor() {
+ singleton = this;
+
+ set_process_internal(true);
+
+ // Split container.
+ HSplitContainer *split_container = memnew(HSplitContainer);
+ split_container->set_name(TTR("Tiles"));
+ split_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_container->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(split_container);
+
+ // Sources list.
+ VBoxContainer *split_container_left_side = memnew(VBoxContainer);
+ split_container_left_side->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_container_left_side->set_v_size_flags(SIZE_EXPAND_FILL);
+ split_container_left_side->set_stretch_ratio(0.25);
+ split_container_left_side->set_custom_minimum_size(Size2i(70, 0) * EDSCALE);
+ split_container->add_child(split_container_left_side);
+
+ sources_list = memnew(ItemList);
+ sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE);
+ sources_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ sources_list->set_v_size_flags(SIZE_EXPAND_FILL);
+ sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected));
+ sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current));
+ sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list));
+ sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ sources_list->set_drag_forwarding(this);
+ split_container_left_side->add_child(sources_list);
+
+ HBoxContainer *sources_bottom_actions = memnew(HBoxContainer);
+ sources_bottom_actions->set_alignment(HBoxContainer::ALIGN_END);
+ split_container_left_side->add_child(sources_bottom_actions);
+
+ sources_delete_button = memnew(Button);
+ sources_delete_button->set_flat(true);
+ sources_delete_button->set_disabled(true);
+ sources_delete_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_delete_pressed));
+ sources_bottom_actions->add_child(sources_delete_button);
+
+ sources_add_button = memnew(MenuButton);
+ sources_add_button->set_flat(true);
+ sources_add_button->get_popup()->add_item(TTR("Atlas"));
+ sources_add_button->get_popup()->add_item(TTR("Scenes Collection"));
+ sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed));
+ sources_bottom_actions->add_child(sources_add_button);
+
+ // Right side container.
+ VBoxContainer *split_container_right_side = memnew(VBoxContainer);
+ split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_container_right_side->set_v_size_flags(SIZE_EXPAND_FILL);
+ split_container->add_child(split_container_right_side);
+
+ // No source selected.
+ no_source_selected_label = memnew(Label);
+ no_source_selected_label->set_text(TTR("No TileSet source selected. Select or create a TileSet source."));
+ no_source_selected_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ no_source_selected_label->set_v_size_flags(SIZE_EXPAND_FILL);
+ no_source_selected_label->set_align(Label::ALIGN_CENTER);
+ no_source_selected_label->set_valign(Label::VALIGN_CENTER);
+ split_container_right_side->add_child(no_source_selected_label);
+
+ // Atlases editor.
+ tile_set_atlas_source_editor = memnew(TileSetAtlasSourceEditor);
+ tile_set_atlas_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_set_atlas_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list));
+ split_container_right_side->add_child(tile_set_atlas_source_editor);
+ tile_set_atlas_source_editor->hide();
+
+ // Scenes collection editor.
+ tile_set_scenes_collection_source_editor = memnew(TileSetScenesCollectionSourceEditor);
+ tile_set_scenes_collection_source_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ tile_set_scenes_collection_source_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list));
+ split_container_right_side->add_child(tile_set_scenes_collection_source_editor);
+ tile_set_scenes_collection_source_editor->hide();
+
+ // Registers UndoRedo inspector callback.
+ EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback));
+}
+
+TileSetEditor::~TileSetEditor() {
+ if (tile_set.is_valid()) {
+ tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
+ }
+}
diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h
new file mode 100644
index 0000000000..9e50aca62f
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_editor.h
@@ -0,0 +1,84 @@
+/*************************************************************************/
+/* tile_set_editor.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 TILE_SET_EDITOR_H
+#define TILE_SET_EDITOR_H
+
+#include "scene/gui/box_container.h"
+#include "scene/resources/tile_set.h"
+#include "tile_set_atlas_source_editor.h"
+#include "tile_set_scenes_collection_source_editor.h"
+
+class TileSetEditor : public VBoxContainer {
+ GDCLASS(TileSetEditor, VBoxContainer);
+
+ static TileSetEditor *singleton;
+
+private:
+ Ref<TileSet> tile_set;
+ bool tile_set_changed_needs_update = false;
+
+ Label *no_source_selected_label;
+ TileSetAtlasSourceEditor *tile_set_atlas_source_editor;
+ TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor;
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ void _update_atlas_sources_list(int force_selected_id = -1);
+
+ // -- Sources management --
+ Button *sources_delete_button;
+ MenuButton *sources_add_button;
+ ItemList *sources_list;
+ Ref<Texture2D> missing_texture_texture;
+ void _source_selected(int p_source_index);
+ void _source_add_id_pressed(int p_id_pressed);
+ void _source_delete_pressed();
+
+ void _tile_set_changed();
+
+ void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ _FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; }
+
+ void edit(Ref<TileSet> p_tile_set);
+ 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;
+
+ TileSetEditor();
+ ~TileSetEditor();
+};
+
+#endif // TILE_SET_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp
new file mode 100644
index 0000000000..568d4ca8d7
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp
@@ -0,0 +1,511 @@
+/*************************************************************************/
+/* tile_set_scenes_collection_source_editor.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 "tile_set_scenes_collection_source_editor.h"
+
+#include "editor/editor_resource_preview.h"
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+
+#include "scene/gui/item_list.h"
+
+#include "core/core_string_names.h"
+
+void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id(int p_id) {
+ ERR_FAIL_COND(p_id < 0);
+ if (source_id == p_id) {
+ return;
+ }
+ ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Scenes Collection source ID. Another TileSet source exists with id %d.", p_id));
+
+ int previous_source = source_id;
+ source_id = p_id; // source_id must be updated before, because it's used by the source list update.
+ tile_set->set_source_id(previous_source, p_id);
+ emit_signal("changed", "id");
+}
+
+int TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id() {
+ return source_id;
+}
+
+bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+ bool valid = false;
+ tile_set_scenes_collection_source->set(p_name, p_value, &valid);
+ if (valid) {
+ emit_signal("changed", String(p_name).utf8().get_data());
+ }
+ return valid;
+}
+
+bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+ if (!tile_set_scenes_collection_source) {
+ return false;
+ }
+ bool valid = false;
+ r_ret = tile_set_scenes_collection_source->get(p_name, &valid);
+ return valid;
+}
+
+void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_bind_methods() {
+ // -- Shape and layout --
+ ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id);
+ ClassDB::bind_method(D_METHOD("get_id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id");
+
+ ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set.is_valid());
+ ERR_FAIL_COND(!p_tile_set_scenes_collection_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source);
+
+ // Disconnect to changes.
+ if (tile_set_scenes_collection_source) {
+ tile_set_scenes_collection_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+
+ tile_set = p_tile_set;
+ tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
+ source_id = p_source_id;
+
+ // Connect to changes.
+ if (tile_set_scenes_collection_source) {
+ if (!tile_set_scenes_collection_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) {
+ tile_set_scenes_collection_source->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ }
+ }
+
+ notify_property_list_changed();
+}
+
+// -- Proxy object used by the tile inspector --
+bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_set(const StringName &p_name, const Variant &p_value) {
+ if (!tile_set_scenes_collection_source) {
+ return false;
+ }
+
+ if (p_name == "id") {
+ int as_int = int(p_value);
+ ERR_FAIL_COND_V(as_int < 0, false);
+ ERR_FAIL_COND_V(tile_set_scenes_collection_source->has_scene_tile_id(as_int), false);
+ tile_set_scenes_collection_source->set_scene_tile_id(scene_id, as_int);
+ scene_id = as_int;
+ emit_signal("changed", "id");
+ for (int i = 0; i < tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_count(); i++) {
+ if (int(tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_metadata(i)) == scene_id) {
+ tile_set_scenes_collection_source_editor->scene_tiles_list->select(i);
+ break;
+ }
+ }
+ return true;
+ } else if (p_name == "scene") {
+ tile_set_scenes_collection_source->set_scene_tile_scene(scene_id, p_value);
+ emit_signal("changed", "scene");
+ return true;
+ } else if (p_name == "display_placeholder") {
+ tile_set_scenes_collection_source->set_scene_tile_display_placeholder(scene_id, p_value);
+ emit_signal("changed", "display_placeholder");
+ return true;
+ }
+
+ return false;
+}
+
+bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const {
+ if (!tile_set_scenes_collection_source) {
+ return false;
+ }
+
+ if (p_name == "id") {
+ r_ret = scene_id;
+ return true;
+ } else if (p_name == "scene") {
+ r_ret = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id);
+ return true;
+ } else if (p_name == "display_placeholder") {
+ r_ret = tile_set_scenes_collection_source->get_scene_tile_display_placeholder(scene_id);
+ return true;
+ }
+
+ return false;
+}
+
+void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (!tile_set_scenes_collection_source) {
+ return;
+ }
+
+ p_list->push_back(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "display_placeholder", PROPERTY_HINT_NONE, ""));
+}
+
+void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::edit(TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_scene_id) {
+ ERR_FAIL_COND(!p_tile_set_scenes_collection_source);
+ ERR_FAIL_COND(!p_tile_set_scenes_collection_source->has_scene_tile_id(p_scene_id));
+
+ tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
+ scene_id = p_scene_id;
+
+ notify_property_list_changed();
+}
+
+void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what")));
+}
+
+void TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed(String p_what) {
+ if (p_what == "id") {
+ emit_signal("source_id_changed", scenes_collection_source_proxy_object->get_id());
+ }
+}
+
+void TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed() {
+ tile_set_scenes_collection_source_changed_needs_update = true;
+}
+
+void TileSetScenesCollectionSourceEditor::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) {
+ int index = p_ud;
+
+ if (index >= 0 && index < scene_tiles_list->get_item_count()) {
+ scene_tiles_list->set_item_icon(index, p_preview);
+ }
+}
+
+void TileSetScenesCollectionSourceEditor::_scenes_list_item_activated(int p_index) {
+ Ref<PackedScene> packed_scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_tiles_list->get_item_metadata(p_index));
+ if (packed_scene.is_valid()) {
+ EditorNode::get_singleton()->open_request(packed_scene->get_path());
+ }
+}
+
+void TileSetScenesCollectionSourceEditor::_source_add_pressed() {
+ int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
+ undo_redo->create_action(TTR("Add a Scene Tile"));
+ undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", Ref<PackedScene>(), scene_id);
+ undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
+ undo_redo->commit_action();
+ _update_scenes_list();
+ _update_action_buttons();
+ _update_tile_inspector();
+}
+
+void TileSetScenesCollectionSourceEditor::_source_delete_pressed() {
+ Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+ ERR_FAIL_COND(selected_indices.size() <= 0);
+ int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]);
+
+ undo_redo->create_action(TTR("Remove a Scene Tile"));
+ undo_redo->add_do_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
+ undo_redo->add_undo_method(tile_set_scenes_collection_source, "create_scene_tile", tile_set_scenes_collection_source->get_scene_tile_scene(scene_id), scene_id);
+ undo_redo->commit_action();
+ _update_scenes_list();
+ _update_action_buttons();
+ _update_tile_inspector();
+}
+
+void TileSetScenesCollectionSourceEditor::_update_source_inspector() {
+ // Update the proxy object.
+ scenes_collection_source_proxy_object->edit(tile_set, tile_set_scenes_collection_source, tile_set_source_id);
+}
+
+void TileSetScenesCollectionSourceEditor::_update_tile_inspector() {
+ Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+ bool has_atlas_tile_selected = (selected_indices.size() > 0);
+
+ // Update the proxy object.
+ if (has_atlas_tile_selected) {
+ int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]);
+ tile_proxy_object->edit(tile_set_scenes_collection_source, scene_id);
+ }
+
+ // Update visibility.
+ tile_inspector_label->set_visible(has_atlas_tile_selected);
+ tile_inspector->set_visible(has_atlas_tile_selected);
+}
+
+void TileSetScenesCollectionSourceEditor::_update_action_buttons() {
+ Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+ scene_tile_delete_button->set_disabled(selected_indices.size() <= 0);
+}
+
+void TileSetScenesCollectionSourceEditor::_update_scenes_list() {
+ if (!tile_set_scenes_collection_source) {
+ return;
+ }
+
+ // Get the previously selected id.
+ Vector<int> selected_indices = scene_tiles_list->get_selected_items();
+ int old_selected_scene_id = (selected_indices.size() > 0) ? int(scene_tiles_list->get_item_metadata(selected_indices[0])) : -1;
+
+ // Clear the list.
+ scene_tiles_list->clear();
+
+ // Rebuild the list.
+ int to_reselect = -1;
+ for (int i = 0; i < tile_set_scenes_collection_source->get_scene_tiles_count(); i++) {
+ int scene_id = tile_set_scenes_collection_source->get_scene_tile_id(i);
+
+ Ref<PackedScene> scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id);
+
+ int item_index = 0;
+ if (scene.is_valid()) {
+ item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id));
+ Variant udata = i;
+ EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata);
+ } else {
+ item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons"));
+ }
+ scene_tiles_list->set_item_metadata(item_index, scene_id);
+
+ if (old_selected_scene_id >= 0 && scene_id == old_selected_scene_id) {
+ to_reselect = i;
+ }
+ }
+
+ // Reselect if needed.
+ if (to_reselect >= 0) {
+ scene_tiles_list->select(to_reselect);
+ }
+
+ // Icon size update.
+ int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE;
+ scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size));
+}
+
+void TileSetScenesCollectionSourceEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED:
+ scene_tile_add_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+ scene_tile_delete_button->set_icon(get_theme_icon("Remove", "EditorIcons"));
+ _update_scenes_list();
+ break;
+ case NOTIFICATION_INTERNAL_PROCESS:
+ if (tile_set_scenes_collection_source_changed_needs_update) {
+ // Update everything.
+ _update_source_inspector();
+ _update_scenes_list();
+ _update_action_buttons();
+ _update_tile_inspector();
+ tile_set_scenes_collection_source_changed_needs_update = false;
+ }
+ break;
+ case NOTIFICATION_VISIBILITY_CHANGED:
+ // Update things just in case.
+ _update_scenes_list();
+ _update_action_buttons();
+ break;
+ default:
+ break;
+ }
+}
+
+void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) {
+ ERR_FAIL_COND(!p_tile_set.is_valid());
+ ERR_FAIL_COND(!p_tile_set_scenes_collection_source);
+ ERR_FAIL_COND(p_source_id < 0);
+ ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source);
+
+ if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id) {
+ return;
+ }
+
+ // Remove listener for old objects.
+ if (tile_set_scenes_collection_source) {
+ tile_set_scenes_collection_source->disconnect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed));
+ }
+
+ // Change the edited object.
+ tile_set = p_tile_set;
+ tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
+ tile_set_source_id = p_source_id;
+
+ // Add the listener again.
+ if (tile_set_scenes_collection_source) {
+ tile_set_scenes_collection_source->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed));
+ }
+
+ // Update everything.
+ _update_source_inspector();
+ _update_scenes_list();
+ _update_action_buttons();
+ _update_tile_inspector();
+}
+
+void TileSetScenesCollectionSourceEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
+ if (!can_drop_data_fw(p_point, p_data, p_from)) {
+ return;
+ }
+
+ if (p_from == scene_tiles_list) {
+ // Handle dropping a texture in the list of atlas resources.
+ int scene_id = -1;
+ Dictionary d = p_data;
+ Vector<String> files = d["files"];
+ for (int i = 0; i < files.size(); i++) {
+ Ref<PackedScene> resource = ResourceLoader::load(files[i]);
+ if (resource.is_valid()) {
+ scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id();
+ undo_redo->create_action(TTR("Add a Scene Tile"));
+ undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", resource, scene_id);
+ undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id);
+ undo_redo->commit_action();
+ }
+ }
+
+ _update_scenes_list();
+ _update_action_buttons();
+ _update_tile_inspector();
+ }
+}
+
+bool TileSetScenesCollectionSourceEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
+ if (p_from == scene_tiles_list) {
+ Dictionary d = p_data;
+
+ if (!d.has("type")) {
+ return false;
+ }
+
+ // Check if we have a Texture2D.
+ if (String(d["type"]) == "files") {
+ Vector<String> files = d["files"];
+
+ if (files.size() == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < files.size(); i++) {
+ String file = files[i];
+ String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
+
+ if (!ClassDB::is_parent_class(ftype, "PackedScene")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void TileSetScenesCollectionSourceEditor::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id")));
+
+ ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileSetScenesCollectionSourceEditor::_scene_thumbnail_done);
+ ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetScenesCollectionSourceEditor::drop_data_fw);
+}
+
+TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() {
+ // -- Right side --
+ HSplitContainer *split_container_right_side = memnew(HSplitContainer);
+ split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL);
+ add_child(split_container_right_side);
+
+ // Middle panel.
+ ScrollContainer *middle_panel = memnew(ScrollContainer);
+ middle_panel->set_enable_h_scroll(false);
+ middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE);
+ split_container_right_side->add_child(middle_panel);
+
+ VBoxContainer *middle_vbox_container = memnew(VBoxContainer);
+ middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL);
+ middle_panel->add_child(middle_vbox_container);
+
+ // Scenes collection source inspector.
+ scenes_collection_source_inspector_label = memnew(Label);
+ scenes_collection_source_inspector_label->set_text(TTR("Scenes collection properties:"));
+ middle_vbox_container->add_child(scenes_collection_source_inspector_label);
+
+ scenes_collection_source_proxy_object = memnew(TileSetScenesCollectionProxyObject());
+ scenes_collection_source_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed));
+
+ scenes_collection_source_inspector = memnew(EditorInspector);
+ scenes_collection_source_inspector->set_undo_redo(undo_redo);
+ scenes_collection_source_inspector->set_enable_v_scroll(false);
+ scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object);
+ middle_vbox_container->add_child(scenes_collection_source_inspector);
+
+ // Tile inspector.
+ tile_inspector_label = memnew(Label);
+ tile_inspector_label->set_text(TTR("Tile properties:"));
+ tile_inspector_label->hide();
+ middle_vbox_container->add_child(tile_inspector_label);
+
+ tile_proxy_object = memnew(SceneTileProxyObject(this));
+ tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_scenes_list).unbind(1));
+ tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1));
+
+ tile_inspector = memnew(EditorInspector);
+ tile_inspector->set_undo_redo(undo_redo);
+ tile_inspector->set_enable_v_scroll(false);
+ tile_inspector->edit(tile_proxy_object);
+ tile_inspector->set_use_folding(true);
+ middle_vbox_container->add_child(tile_inspector);
+
+ // Scenes list.
+ VBoxContainer *right_vbox_container = memnew(VBoxContainer);
+ split_container_right_side->add_child(right_vbox_container);
+
+ scene_tiles_list = memnew(ItemList);
+ scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL);
+ scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL);
+ scene_tiles_list->set_drag_forwarding(this);
+ scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_tile_inspector).unbind(1));
+ scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1));
+ scene_tiles_list->connect("item_activated", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_list_item_activated));
+ scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ right_vbox_container->add_child(scene_tiles_list);
+
+ HBoxContainer *scenes_bottom_actions = memnew(HBoxContainer);
+ right_vbox_container->add_child(scenes_bottom_actions);
+
+ scene_tile_add_button = memnew(Button);
+ scene_tile_add_button->set_flat(true);
+ scene_tile_add_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_add_pressed));
+ scenes_bottom_actions->add_child(scene_tile_add_button);
+
+ scene_tile_delete_button = memnew(Button);
+ scene_tile_delete_button->set_flat(true);
+ scene_tile_delete_button->set_disabled(true);
+ scene_tile_delete_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_delete_pressed));
+ scenes_bottom_actions->add_child(scene_tile_delete_button);
+}
+
+TileSetScenesCollectionSourceEditor::~TileSetScenesCollectionSourceEditor() {
+ memdelete(scenes_collection_source_proxy_object);
+ memdelete(tile_proxy_object);
+}
diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h
new file mode 100644
index 0000000000..195aa79bc4
--- /dev/null
+++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h
@@ -0,0 +1,139 @@
+/*************************************************************************/
+/* tile_set_scenes_collection_source_editor.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 TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H
+#define TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "scene/gui/box_container.h"
+#include "scene/resources/tile_set.h"
+
+class TileSetScenesCollectionSourceEditor : public HBoxContainer {
+ GDCLASS(TileSetScenesCollectionSourceEditor, HBoxContainer);
+
+private:
+ // -- Proxy object for an atlas source, needed by the inspector --
+ class TileSetScenesCollectionProxyObject : public Object {
+ GDCLASS(TileSetScenesCollectionProxyObject, Object);
+
+ private:
+ Ref<TileSet> tile_set;
+ TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
+ int source_id = -1;
+
+ protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ static void _bind_methods();
+
+ public:
+ void set_id(int p_id);
+ int get_id();
+
+ void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id);
+ };
+
+ // -- Proxy object for a tile, needed by the inspector --
+ class SceneTileProxyObject : public Object {
+ GDCLASS(SceneTileProxyObject, Object);
+
+ private:
+ TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor;
+
+ TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
+ int source_id;
+ int scene_id;
+
+ protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+ static void _bind_methods();
+
+ public:
+ // Update the proxyed object.
+ void edit(TileSetScenesCollectionSource *p_tile_set_atlas_source, int p_scene_id);
+
+ SceneTileProxyObject(TileSetScenesCollectionSourceEditor *p_tiles_set_scenes_collection_source_editor) {
+ tile_set_scenes_collection_source_editor = p_tiles_set_scenes_collection_source_editor;
+ }
+ };
+
+private:
+ Ref<TileSet> tile_set;
+ TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
+ int tile_set_source_id = -1;
+
+ UndoRedo *undo_redo = EditorNode::get_undo_redo();
+
+ bool tile_set_scenes_collection_source_changed_needs_update = false;
+
+ // Source inspector.
+ TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object;
+ Label *scenes_collection_source_inspector_label;
+ EditorInspector *scenes_collection_source_inspector;
+
+ // Tile inspector.
+ SceneTileProxyObject *tile_proxy_object;
+ Label *tile_inspector_label;
+ EditorInspector *tile_inspector;
+
+ ItemList *scene_tiles_list;
+ Button *scene_tile_add_button;
+ Button *scene_tile_delete_button;
+
+ void _tile_set_scenes_collection_source_changed();
+ void _scenes_collection_source_proxy_object_changed(String p_what);
+ void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud);
+ void _scenes_list_item_activated(int p_index);
+
+ void _source_add_pressed();
+ void _source_delete_pressed();
+
+ // Update methods.
+ void _update_source_inspector();
+ void _update_tile_inspector();
+ void _update_scenes_list();
+ void _update_action_buttons();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id);
+ 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;
+ TileSetScenesCollectionSourceEditor();
+ ~TileSetScenesCollectionSourceEditor();
+};
+
+#endif
diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp
new file mode 100644
index 0000000000..fb111efc17
--- /dev/null
+++ b/editor/plugins/tiles/tiles_editor_plugin.cpp
@@ -0,0 +1,277 @@
+/*************************************************************************/
+/* tiles_editor_plugin.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 "tiles_editor_plugin.h"
+
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+#include "editor/plugins/canvas_item_editor_plugin.h"
+
+#include "scene/2d/tile_map.h"
+#include "scene/resources/tile_set.h"
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/control.h"
+#include "scene/gui/separator.h"
+
+#include "tile_set_editor.h"
+
+TilesEditor *TilesEditor::singleton = nullptr;
+
+void TilesEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ tileset_tilemap_switch_button->set_icon(get_theme_icon("TileSet", "EditorIcons"));
+ } break;
+ case NOTIFICATION_INTERNAL_PROCESS: {
+ if (tile_map_changed_needs_update) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tile_set = tile_map->get_tileset();
+ }
+ _update_switch_button();
+ _update_editors();
+ }
+ } break;
+ }
+}
+
+void TilesEditor::_tile_map_changed() {
+ tile_map_changed_needs_update = true;
+}
+
+void TilesEditor::_update_switch_button() {
+ // Force the buttons status if needed.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map && !tile_set.is_valid()) {
+ tileset_tilemap_switch_button->set_pressed(false);
+ } else if (!tile_map && tile_set.is_valid()) {
+ tileset_tilemap_switch_button->set_pressed(true);
+ }
+}
+
+void TilesEditor::_update_editors() {
+ // Set editors visibility.
+ tilemap_toolbar->set_visible(!tileset_tilemap_switch_button->is_pressed());
+ tilemap_editor->set_visible(!tileset_tilemap_switch_button->is_pressed());
+ tileset_editor->set_visible(tileset_tilemap_switch_button->is_pressed());
+
+ // Enable/disable the switch button.
+ if (!tileset_tilemap_switch_button->is_pressed()) {
+ if (!tile_set.is_valid()) {
+ tileset_tilemap_switch_button->set_disabled(true);
+ tileset_tilemap_switch_button->set_tooltip(TTR("This TileMap has no assigned TileSet, assign a TileSet to this TileMap to edit it."));
+ } else {
+ tileset_tilemap_switch_button->set_disabled(false);
+ tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor."));
+ }
+ } else {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ tileset_tilemap_switch_button->set_disabled(true);
+ tileset_tilemap_switch_button->set_tooltip(TTR("You are editing a TileSet resource. Select a TileMap node to paint."));
+ } else {
+ tileset_tilemap_switch_button->set_disabled(false);
+ tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor."));
+ }
+ }
+
+ // If tile_map is not edited, we change the edited only if we are not editing a tile_set.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tilemap_editor->edit(tile_map);
+ } else {
+ tilemap_editor->edit(nullptr);
+ }
+ tileset_editor->edit(tile_set);
+
+ // Update the viewport
+ CanvasItemEditor::get_singleton()->update_viewport();
+}
+
+void TilesEditor::set_atlas_sources_lists_current(int p_current) {
+ atlas_sources_lists_current = p_current;
+}
+
+void TilesEditor::synchronize_atlas_sources_list(Object *p_current) {
+ ItemList *item_list = Object::cast_to<ItemList>(p_current);
+ ERR_FAIL_COND(!item_list);
+
+ if (item_list->is_visible_in_tree()) {
+ if (atlas_sources_lists_current < 0 || atlas_sources_lists_current >= item_list->get_item_count()) {
+ item_list->deselect_all();
+ } else {
+ item_list->set_current(atlas_sources_lists_current);
+ item_list->emit_signal("item_selected", atlas_sources_lists_current);
+ }
+ }
+}
+
+void TilesEditor::set_atlas_view_transform(float p_zoom, Vector2 p_scroll) {
+ atlas_view_zoom = p_zoom;
+ atlas_view_scroll = p_scroll;
+}
+
+void TilesEditor::synchronize_atlas_view(Object *p_current) {
+ TileAtlasView *tile_atlas_view = Object::cast_to<TileAtlasView>(p_current);
+ ERR_FAIL_COND(!tile_atlas_view);
+
+ if (tile_atlas_view->is_visible_in_tree()) {
+ tile_atlas_view->set_transform(atlas_view_zoom, Vector2(atlas_view_scroll.x, atlas_view_scroll.y));
+ }
+}
+
+void TilesEditor::edit(Object *p_object) {
+ // Disconnect to changes.
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ tile_map->disconnect("changed", callable_mp(this, &TilesEditor::_tile_map_changed));
+ }
+
+ // Update edited objects.
+ tile_set = Ref<TileSet>();
+ if (p_object) {
+ if (p_object->is_class("TileMap")) {
+ tile_map_id = p_object->get_instance_id();
+ tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ tile_set = tile_map->get_tileset();
+ } else if (p_object->is_class("TileSet")) {
+ tile_set = Ref<TileSet>(p_object);
+ if (tile_map) {
+ if (tile_map->get_tileset() != tile_set) {
+ tile_map = nullptr;
+ }
+ }
+ }
+
+ // Update pressed status button.
+ if (p_object->is_class("TileMap")) {
+ tileset_tilemap_switch_button->set_pressed(false);
+ } else if (p_object->is_class("TileSet")) {
+ tileset_tilemap_switch_button->set_pressed(true);
+ }
+ }
+
+ // Update the editors.
+ _update_switch_button();
+ _update_editors();
+
+ // Add change listener.
+ if (tile_map) {
+ tile_map->connect("changed", callable_mp(this, &TilesEditor::_tile_map_changed));
+ }
+}
+
+void TilesEditor::_bind_methods() {
+}
+
+TilesEditor::TilesEditor(EditorNode *p_editor) {
+ set_process_internal(true);
+
+ // Update the singleton.
+ singleton = this;
+
+ // Toolbar.
+ HBoxContainer *toolbar = memnew(HBoxContainer);
+ toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
+ add_child(toolbar);
+
+ // Switch button.
+ tileset_tilemap_switch_button = memnew(Button);
+ tileset_tilemap_switch_button->set_flat(true);
+ tileset_tilemap_switch_button->set_toggle_mode(true);
+ tileset_tilemap_switch_button->connect("toggled", callable_mp(this, &TilesEditor::_update_editors).unbind(1));
+ toolbar->add_child(tileset_tilemap_switch_button);
+
+ // Tilemap editor.
+ tilemap_editor = memnew(TileMapEditor);
+ tilemap_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ tilemap_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ tilemap_editor->hide();
+ add_child(tilemap_editor);
+
+ tilemap_toolbar = tilemap_editor->get_toolbar();
+ toolbar->add_child(tilemap_toolbar);
+
+ // Tileset editor.
+ tileset_editor = memnew(TileSetEditor);
+ tileset_editor->set_h_size_flags(SIZE_EXPAND_FILL);
+ tileset_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ tileset_editor->hide();
+ add_child(tileset_editor);
+
+ // Initialization.
+ _update_switch_button();
+ _update_editors();
+}
+
+TilesEditor::~TilesEditor() {
+}
+
+///////////////////////////////////////////////////////////////
+
+void TilesEditorPlugin::_notification(int p_what) {
+}
+
+void TilesEditorPlugin::make_visible(bool p_visible) {
+ if (p_visible) {
+ tiles_editor_button->show();
+ editor_node->make_bottom_panel_item_visible(tiles_editor);
+ //get_tree()->connect_compat("idle_frame", tileset_editor, "_on_workspace_process");
+ } else {
+ editor_node->hide_bottom_panel();
+ tiles_editor_button->hide();
+ //get_tree()->disconnect_compat("idle_frame", tileset_editor, "_on_workspace_process");
+ }
+}
+
+void TilesEditorPlugin::edit(Object *p_object) {
+ tiles_editor->edit(p_object);
+}
+
+bool TilesEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("TileMap") || p_object->is_class("TileSet");
+}
+
+TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) {
+ editor_node = p_node;
+
+ tiles_editor = memnew(TilesEditor(p_node));
+ tiles_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
+ tiles_editor->hide();
+
+ tiles_editor_button = p_node->add_bottom_panel_item(TTR("Tiles"), tiles_editor);
+ tiles_editor_button->hide();
+}
+
+TilesEditorPlugin::~TilesEditorPlugin() {
+}
diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h
new file mode 100644
index 0000000000..6cc6f51598
--- /dev/null
+++ b/editor/plugins/tiles/tiles_editor_plugin.h
@@ -0,0 +1,114 @@
+/*************************************************************************/
+/* tiles_editor_plugin.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 TILES_EDITOR_PLUGIN_H
+#define TILES_EDITOR_PLUGIN_H
+
+#include "editor/editor_plugin.h"
+#include "scene/gui/box_container.h"
+
+#include "tile_atlas_view.h"
+#include "tile_map_editor.h"
+#include "tile_set_editor.h"
+
+class TilesEditor : public VBoxContainer {
+ GDCLASS(TilesEditor, VBoxContainer);
+
+ static TilesEditor *singleton;
+
+private:
+ bool tile_map_changed_needs_update = false;
+ ObjectID tile_map_id;
+ Ref<TileSet> tile_set;
+
+ Button *tileset_tilemap_switch_button;
+
+ Control *tilemap_toolbar;
+ TileMapEditor *tilemap_editor;
+
+ TileSetEditor *tileset_editor;
+
+ void _update_switch_button();
+ void _update_editors();
+
+ // For synchronization.
+ int atlas_sources_lists_current = 0;
+ float atlas_view_zoom = 1.0;
+ Vector2 atlas_view_scroll = Vector2();
+
+ void _tile_map_changed();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ _FORCE_INLINE_ static TilesEditor *get_singleton() { return singleton; }
+
+ bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return tilemap_editor->forward_canvas_gui_input(p_event); }
+ void forward_canvas_draw_over_viewport(Control *p_overlay) { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); }
+
+ // To synchronize the atlas sources lists.
+ void set_atlas_sources_lists_current(int p_current);
+ void synchronize_atlas_sources_list(Object *p_current);
+
+ void set_atlas_view_transform(float p_zoom, Vector2 p_scroll);
+ void synchronize_atlas_view(Object *p_current);
+
+ void edit(Object *p_object);
+
+ TilesEditor(EditorNode *p_editor);
+ ~TilesEditor();
+};
+
+class TilesEditorPlugin : public EditorPlugin {
+ GDCLASS(TilesEditorPlugin, EditorPlugin);
+
+private:
+ EditorNode *editor_node;
+ TilesEditor *tiles_editor;
+ Button *tiles_editor_button;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tiles_editor->forward_canvas_gui_input(p_event); }
+ virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tiles_editor->forward_canvas_draw_over_viewport(p_overlay); }
+
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+ virtual void make_visible(bool p_visible) override;
+
+ TilesEditorPlugin(EditorNode *p_node);
+ ~TilesEditorPlugin();
+};
+
+#endif // TILES_EDITOR_PLUGIN_H
diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp
index 0af3b936cb..10679ad6f2 100644
--- a/editor/plugins/version_control_editor_plugin.cpp
+++ b/editor/plugins/version_control_editor_plugin.cpp
@@ -180,7 +180,7 @@ void VersionControlEditorPlugin::_stage_selected() {
staged_files_count = 0;
TreeItem *root = stage_files->get_root();
if (root) {
- TreeItem *file_entry = root->get_children();
+ TreeItem *file_entry = root->get_first_child();
while (file_entry) {
if (file_entry->is_checked(0)) {
EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0));
@@ -207,7 +207,7 @@ void VersionControlEditorPlugin::_stage_all() {
staged_files_count = 0;
TreeItem *root = stage_files->get_root();
if (root) {
- TreeItem *file_entry = root->get_children();
+ TreeItem *file_entry = root->get_first_child();
while (file_entry) {
EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0));
file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color("success_color", "Editor"));
@@ -476,6 +476,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() {
version_control_dock = memnew(PanelContainer);
version_control_dock->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ version_control_dock->set_custom_minimum_size(Size2(0, 300) * EDSCALE);
version_control_dock->hide();
diff_vbc = memnew(VBoxContainer);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index acc77bd098..fb91cbb866 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -44,6 +44,7 @@
#include "scene/gui/panel.h"
#include "scene/main/window.h"
#include "scene/resources/visual_shader_nodes.h"
+#include "scene/resources/visual_shader_particle_nodes.h"
#include "scene/resources/visual_shader_sdf_nodes.h"
#include "servers/display_server.h"
#include "servers/rendering/shader_types.h"
@@ -71,13 +72,13 @@ const int MAX_FLOAT_CONST_DEFS = sizeof(float_constant_defs) / sizeof(FloatConst
Control *VisualShaderNodePlugin::create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) {
if (get_script_instance()) {
- return get_script_instance()->call("create_editor", p_parent_resource, p_node);
+ return get_script_instance()->call("_create_editor", p_parent_resource, p_node);
}
return nullptr;
}
void VisualShaderNodePlugin::_bind_methods() {
- BIND_VMETHOD(MethodInfo(Variant::OBJECT, "create_editor", PropertyInfo(Variant::OBJECT, "parent_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::OBJECT, "for_node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode")));
+ BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_create_editor", PropertyInfo(Variant::OBJECT, "parent_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::OBJECT, "for_node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode")));
}
///////////////////
@@ -109,6 +110,7 @@ void VisualShaderGraphPlugin::_bind_methods() {
ClassDB::bind_method("set_uniform_name", &VisualShaderGraphPlugin::set_uniform_name);
ClassDB::bind_method("set_expression", &VisualShaderGraphPlugin::set_expression);
ClassDB::bind_method("update_curve", &VisualShaderGraphPlugin::update_curve);
+ ClassDB::bind_method("update_curve3", &VisualShaderGraphPlugin::update_curve3);
ClassDB::bind_method("update_constant", &VisualShaderGraphPlugin::update_constant);
}
@@ -210,9 +212,19 @@ void VisualShaderGraphPlugin::set_uniform_name(VisualShader::Type p_type, int p_
}
void VisualShaderGraphPlugin::update_curve(int p_node_id) {
- if (links.has(p_node_id) && links[p_node_id].curve_editor) {
+ if (links.has(p_node_id) && links[p_node_id].curve_editors[0]) {
if (((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture().is_valid()) {
- links[p_node_id].curve_editor->set_curve(((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture()->get_curve());
+ links[p_node_id].curve_editors[0]->set_curve(((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture()->get_curve());
+ }
+ }
+}
+
+void VisualShaderGraphPlugin::update_curve3(int p_node_id) {
+ if (links.has(p_node_id) && links[p_node_id].curve_editors[0] && links[p_node_id].curve_editors[1] && links[p_node_id].curve_editors[2]) {
+ if (((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture().is_valid()) {
+ links[p_node_id].curve_editors[0]->set_curve(((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture()->get_curve_x());
+ links[p_node_id].curve_editors[1]->set_curve(((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture()->get_curve_y());
+ links[p_node_id].curve_editors[2]->set_curve(((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture()->get_curve_z());
}
}
}
@@ -264,8 +276,8 @@ void VisualShaderGraphPlugin::register_expression_edit(int p_node_id, CodeEdit *
links[p_node_id].expression_edit = p_expression_edit;
}
-void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, CurveEditor *p_curve_editor) {
- links[p_node_id].curve_editor = p_curve_editor;
+void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, int p_index, CurveEditor *p_curve_editor) {
+ links[p_node_id].curve_editors[p_index] = p_curve_editor;
}
void VisualShaderGraphPlugin::update_uniform_refs() {
@@ -311,7 +323,7 @@ void VisualShaderGraphPlugin::make_dirty(bool p_enabled) {
}
void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node) {
- links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, Map<int, InputPort>(), Map<int, Port>(), nullptr, nullptr, nullptr, nullptr, nullptr });
+ links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, Map<int, InputPort>(), Map<int, Port>(), nullptr, nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } });
}
void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, TextureButton *p_button) {
@@ -322,6 +334,12 @@ void VisualShaderGraphPlugin::register_uniform_name(int p_node_id, LineEdit *p_u
links[p_node_id].uniform_name = p_uniform_name;
}
+void VisualShaderGraphPlugin::update_theme() {
+ vector_expanded_color[0] = VisualShaderEditor::get_singleton()->get_theme_color("axis_x_color", "Editor"); // red
+ vector_expanded_color[1] = VisualShaderEditor::get_singleton()->get_theme_color("axis_y_color", "Editor"); // green
+ vector_expanded_color[2] = VisualShaderEditor::get_singleton()->get_theme_color("axis_z_color", "Editor"); // blue
+}
+
void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
if (p_type != visual_shader->get_shader_type()) {
return;
@@ -340,6 +358,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
Color(1.0, 1.0, 0.0), // sampler
};
+ static const String vector_expanded_name[3] = {
+ "red",
+ "green",
+ "blue"
+ };
+
Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_id);
Ref<VisualShaderNodeResizableBase> resizable_node = Object::cast_to<VisualShaderNodeResizableBase>(vsnode.ptr());
@@ -349,6 +373,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
Ref<VisualShaderNodeGroupBase> group_node = Object::cast_to<VisualShaderNodeGroupBase>(vsnode.ptr());
bool is_group = !group_node.is_null();
+ bool is_comment = false;
+
Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(group_node.ptr());
bool is_expression = !expression_node.is_null();
String expression = "";
@@ -392,17 +418,22 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
if (is_resizable) {
Ref<VisualShaderNodeComment> comment_node = Object::cast_to<VisualShaderNodeComment>(vsnode.ptr());
if (comment_node.is_valid()) {
+ is_comment = true;
node->set_comment(true);
Label *comment_label = memnew(Label);
node->add_child(comment_label);
comment_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
comment_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- comment_label->set_mouse_filter(Control::MouseFilter::MOUSE_FILTER_STOP);
comment_label->set_text(comment_node->get_description());
}
}
+ Ref<VisualShaderNodeParticleEmit> emit = vsnode;
+ if (emit.is_valid()) {
+ node->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
+ }
+
Ref<VisualShaderNodeUniform> uniform = vsnode;
if (uniform.is_valid()) {
VisualShaderEditor::get_singleton()->graph->add_child(node);
@@ -412,7 +443,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
register_uniform_name(p_id, uniform_name);
uniform_name->set_text(uniform->get_uniform_name());
node->add_child(uniform_name);
- uniform_name->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_changed), varray(p_id));
+ uniform_name->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_changed), varray(p_id));
uniform_name->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_focus_out), varray(uniform_name, p_id));
if (vsnode->get_input_port_count() == 0 && vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") {
@@ -452,6 +483,18 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
custom_editor = hbox;
}
+ Ref<VisualShaderNodeCurve3Texture> curve3 = vsnode;
+ if (curve3.is_valid()) {
+ if (curve3->get_texture().is_valid() && !curve3->get_texture()->is_connected("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve3))) {
+ curve3->get_texture()->connect("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve3), varray(p_id));
+ }
+
+ HBoxContainer *hbox = memnew(HBoxContainer);
+ custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ hbox->add_child(custom_editor);
+ custom_editor = hbox;
+ }
+
Ref<VisualShaderNodeFloatConstant> float_const = vsnode;
if (float_const.is_valid()) {
HBoxContainer *hbox = memnew(HBoxContainer);
@@ -475,19 +518,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
port_offset++;
node->add_child(custom_editor);
- if (curve.is_valid()) {
+ bool is_curve = curve.is_valid() || curve3.is_valid();
+
+ if (is_curve) {
VisualShaderEditor::get_singleton()->graph->add_child(node);
VisualShaderEditor::get_singleton()->_update_created_node(node);
- CurveEditor *curve_editor = memnew(CurveEditor);
- node->add_child(curve_editor);
- register_curve_editor(p_id, curve_editor);
- curve_editor->set_custom_minimum_size(Size2(300, 0));
- curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- if (curve->get_texture().is_valid()) {
- curve_editor->set_curve(curve->get_texture()->get_curve());
- }
-
TextureButton *preview = memnew(TextureButton);
preview->set_toggle_mode(true);
preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityHidden", "EditorIcons"));
@@ -499,12 +535,59 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, 0), CONNECT_DEFERRED);
custom_editor->add_child(preview);
+ if (vsnode->get_output_port_for_preview() >= 0) {
+ show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview());
+ }
+ }
+
+ if (curve.is_valid()) {
+ CurveEditor *curve_editor = memnew(CurveEditor);
+ node->add_child(curve_editor);
+ register_curve_editor(p_id, 0, curve_editor);
+ curve_editor->set_custom_minimum_size(Size2(300, 0));
+ curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ if (curve->get_texture().is_valid()) {
+ curve_editor->set_curve(curve->get_texture()->get_curve());
+ }
+ }
+
+ if (curve3.is_valid()) {
+ CurveEditor *curve_editor_x = memnew(CurveEditor);
+ node->add_child(curve_editor_x);
+ register_curve_editor(p_id, 0, curve_editor_x);
+ curve_editor_x->set_custom_minimum_size(Size2(300, 0));
+ curve_editor_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ if (curve3->get_texture().is_valid()) {
+ curve_editor_x->set_curve(curve3->get_texture()->get_curve_x());
+ }
+
+ CurveEditor *curve_editor_y = memnew(CurveEditor);
+ node->add_child(curve_editor_y);
+ register_curve_editor(p_id, 1, curve_editor_y);
+ curve_editor_y->set_custom_minimum_size(Size2(300, 0));
+ curve_editor_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ if (curve3->get_texture().is_valid()) {
+ curve_editor_y->set_curve(curve3->get_texture()->get_curve_y());
+ }
+
+ CurveEditor *curve_editor_z = memnew(CurveEditor);
+ node->add_child(curve_editor_z);
+ register_curve_editor(p_id, 2, curve_editor_z);
+ curve_editor_z->set_custom_minimum_size(Size2(300, 0));
+ curve_editor_z->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ if (curve3->get_texture().is_valid()) {
+ curve_editor_z->set_curve(curve3->get_texture()->get_curve_z());
+ }
+ }
+
+ if (is_curve) {
VisualShaderNode::PortType port_left = vsnode->get_input_port_type(0);
VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0);
node->set_slot(0, true, port_left, type_color[port_left], true, port_right, type_color[port_right]);
VisualShaderEditor::get_singleton()->call_deferred("_set_node_size", (int)p_type, p_id, size);
}
+
if (vsnode->is_use_prop_slots()) {
return;
}
@@ -551,13 +634,32 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
}
}
- for (int i = 0; i < MAX(vsnode->get_input_port_count(), vsnode->get_output_port_count()); i++) {
+ int output_port_count = 0;
+ for (int i = 0; i < vsnode->get_output_port_count(); i++) {
+ if (vsnode->_is_output_port_expanded(i)) {
+ if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) {
+ output_port_count += 3;
+ }
+ }
+ output_port_count++;
+ }
+ int max_ports = MAX(vsnode->get_input_port_count(), output_port_count);
+ VisualShaderNode::PortType expanded_type = VisualShaderNode::PORT_TYPE_SCALAR;
+ int expanded_port_counter = 0;
+
+ for (int i = 0, j = 0; i < max_ports; i++, j++) {
+ if (expanded_type == VisualShaderNode::PORT_TYPE_VECTOR && expanded_port_counter >= 3) {
+ expanded_type = VisualShaderNode::PORT_TYPE_SCALAR;
+ expanded_port_counter = 0;
+ i -= 3;
+ }
+
if (vsnode->is_port_separator(i)) {
node->add_child(memnew(HSeparator));
port_offset++;
}
- bool valid_left = i < vsnode->get_input_port_count();
+ bool valid_left = j < vsnode->get_input_port_count();
VisualShaderNode::PortType port_left = VisualShaderNode::PORT_TYPE_SCALAR;
bool port_left_used = false;
String name_left;
@@ -565,18 +667,24 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
name_left = vsnode->get_input_port_name(i);
port_left = vsnode->get_input_port_type(i);
for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
- if (E->get().to_node == p_id && E->get().to_port == i) {
+ if (E->get().to_node == p_id && E->get().to_port == j) {
port_left_used = true;
}
}
}
- bool valid_right = i < vsnode->get_output_port_count();
+ bool valid_right = true;
VisualShaderNode::PortType port_right = VisualShaderNode::PORT_TYPE_SCALAR;
String name_right;
- if (valid_right) {
- name_right = vsnode->get_output_port_name(i);
- port_right = vsnode->get_output_port_type(i);
+
+ if (expanded_type == VisualShaderNode::PORT_TYPE_SCALAR) {
+ valid_right = i < vsnode->get_output_port_count();
+ if (valid_right) {
+ name_right = vsnode->get_output_port_name(i);
+ port_right = vsnode->get_output_port_type(i);
+ }
+ } else {
+ name_right = vector_expanded_name[expanded_port_counter++];
}
HBoxContainer *hb = memnew(HBoxContainer);
@@ -621,7 +729,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0));
name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
name_box->set_text(name_left);
- name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED);
+ name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED);
name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, false), CONNECT_DEFERRED);
Button *remove_btn = memnew(Button);
@@ -662,7 +770,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0));
name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
name_box->set_text(name_right);
- name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED);
+ name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED);
name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, true), CONNECT_DEFERRED);
OptionButton *type_box = memnew(OptionButton);
@@ -684,17 +792,29 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
}
}
- if (valid_right && visual_shader->get_shader_type() == VisualShader::TYPE_FRAGMENT && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) {
- TextureButton *preview = memnew(TextureButton);
- preview->set_toggle_mode(true);
- preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityHidden", "EditorIcons"));
- preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityVisible", "EditorIcons"));
- preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ if (valid_right) {
+ if (vsnode->is_output_port_expandable(i)) {
+ TextureButton *expand = memnew(TextureButton);
+ expand->set_toggle_mode(true);
+ expand->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiTreeArrowDown", "EditorIcons"));
+ expand->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiTreeArrowRight", "EditorIcons"));
+ expand->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ expand->set_pressed(vsnode->_is_output_port_expanded(i));
+ expand->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED);
+ hb->add_child(expand);
+ }
+ if (visual_shader->get_shader_type() == VisualShader::TYPE_FRAGMENT && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) {
+ TextureButton *preview = memnew(TextureButton);
+ preview->set_toggle_mode(true);
+ preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityHidden", "EditorIcons"));
+ preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityVisible", "EditorIcons"));
+ preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
- register_output_port(p_id, i, preview);
+ register_output_port(p_id, j, preview);
- preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, i), CONNECT_DEFERRED);
- hb->add_child(preview);
+ preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, j), CONNECT_DEFERRED);
+ hb->add_child(preview);
+ }
}
if (is_group) {
@@ -706,7 +826,40 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
node->add_child(hb);
+ if (expanded_type != VisualShaderNode::PORT_TYPE_SCALAR) {
+ continue;
+ }
+
node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]);
+
+ if (vsnode->_is_output_port_expanded(i)) {
+ if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) {
+ port_offset++;
+ valid_left = (i + 1) < vsnode->get_input_port_count();
+ port_left = VisualShaderNode::PORT_TYPE_SCALAR;
+ if (valid_left) {
+ port_left = vsnode->get_input_port_type(i + 1);
+ }
+ node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]);
+ port_offset++;
+
+ valid_left = (i + 2) < vsnode->get_input_port_count();
+ port_left = VisualShaderNode::PORT_TYPE_SCALAR;
+ if (valid_left) {
+ port_left = vsnode->get_input_port_type(i + 2);
+ }
+ node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]);
+ port_offset++;
+
+ valid_left = (i + 3) < vsnode->get_input_port_count();
+ port_left = VisualShaderNode::PORT_TYPE_SCALAR;
+ if (valid_left) {
+ port_left = vsnode->get_input_port_type(i + 3);
+ }
+ node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]);
+ expanded_type = VisualShaderNode::PORT_TYPE_VECTOR;
+ }
+ }
}
if (vsnode->get_output_port_for_preview() >= 0) {
@@ -728,14 +881,15 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
if (is_expression) {
CodeEdit *expression_box = memnew(CodeEdit);
Ref<CodeHighlighter> expression_syntax_highlighter;
- expression_syntax_highlighter.instance();
- expression_node->set_control(expression_box, 0);
+ expression_syntax_highlighter.instantiate();
+ expression_node->set_ctrl_pressed(expression_box, 0);
node->add_child(expression_box);
register_expression_edit(p_id, expression_box);
Color background_color = EDITOR_GET("text_editor/highlighting/background_color");
Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
+ Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color");
Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color");
Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color");
Color function_color = EDITOR_GET("text_editor/highlighting/function_color");
@@ -746,7 +900,11 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
expression_box->add_theme_color_override("background_color", background_color);
for (List<String>::Element *E = VisualShaderEditor::get_singleton()->keyword_list.front(); E; E = E->next()) {
- expression_syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ if (ShaderLanguage::is_control_flow_keyword(E->get())) {
+ expression_syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ } else {
+ expression_syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ }
}
expression_box->add_theme_font_override("font", VisualShaderEditor::get_singleton()->get_theme_font("expression", "EditorFonts"));
@@ -759,6 +917,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
expression_syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
expression_syntax_highlighter->add_color_region("//", "", comment_color, true);
+ expression_box->clear_comment_delimiters();
+ expression_box->add_comment_delimiter("/*", "*/", false);
+ expression_box->add_comment_delimiter("//", "", true);
+
expression_box->set_text(expression);
expression_box->set_context_menu_enabled(false);
expression_box->set_draw_line_numbers(true);
@@ -768,6 +930,9 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
if (!uniform.is_valid()) {
VisualShaderEditor::get_singleton()->graph->add_child(node);
+ if (is_comment) {
+ VisualShaderEditor::get_singleton()->graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content
+ }
VisualShaderEditor::get_singleton()->_update_created_node(node);
if (is_resizable) {
VisualShaderEditor::get_singleton()->call_deferred("_set_node_size", (int)p_type, p_id, size);
@@ -786,6 +951,7 @@ void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int p_id) {
void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
if (visual_shader->get_shader_type() == p_type) {
VisualShaderEditor::get_singleton()->graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port);
+ connections.push_back({ p_from_node, p_from_port, p_to_node, p_to_port });
if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr) {
links[p_to_node].input_ports[p_to_port].default_input_button->hide();
}
@@ -795,6 +961,12 @@ void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_fro
void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
if (visual_shader->get_shader_type() == p_type) {
VisualShaderEditor::get_singleton()->graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port);
+ for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
+ if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
+ connections.erase(E);
+ break;
+ }
+ }
if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr && links[p_to_node].visual_node->get_input_port_default_value(p_to_port).get_type() != Variant::NIL) {
links[p_to_node].input_ports[p_to_port].default_input_button->show();
set_input_port_default_value(p_type, p_to_node, p_to_port, links[p_to_node].visual_node->get_input_port_default_value(p_to_port));
@@ -917,13 +1089,13 @@ bool VisualShaderEditor::_is_available(int p_mode) {
if (p_mode != -1) {
switch (current_mode) {
- case 0: // Vertex or Emit
+ case 0: // Vertex / Emit
current_mode = 1;
break;
- case 1: // Fragment or Process
+ case 1: // Fragment / Process
current_mode = 2;
break;
- case 2: // Light or End
+ case 2: // Light / Collide
current_mode = 4;
break;
default:
@@ -951,7 +1123,7 @@ void VisualShaderEditor::update_custom_nodes() {
Ref<Script> script = Ref<Script>(res);
Ref<VisualShaderNodeCustom> ref;
- ref.instance();
+ ref.instantiate();
ref->set_script(script);
String name;
@@ -1125,7 +1297,7 @@ void VisualShaderEditor::_update_options_menu() {
item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("bool", "EditorIcons"));
break;
case VisualShaderNode::PORT_TYPE_TRANSFORM:
- item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform", "EditorIcons"));
+ item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform3D", "EditorIcons"));
break;
case VisualShaderNode::PORT_TYPE_SAMPLER:
item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons"));
@@ -1139,22 +1311,29 @@ void VisualShaderEditor::_update_options_menu() {
void VisualShaderEditor::_set_mode(int p_which) {
if (p_which == VisualShader::MODE_SKY) {
- edit_type_standart->set_visible(false);
+ edit_type_standard->set_visible(false);
edit_type_particles->set_visible(false);
edit_type_sky->set_visible(true);
edit_type = edit_type_sky;
+ custom_mode_box->set_visible(false);
mode = MODE_FLAGS_SKY;
} else if (p_which == VisualShader::MODE_PARTICLES) {
- edit_type_standart->set_visible(false);
+ edit_type_standard->set_visible(false);
edit_type_particles->set_visible(true);
edit_type_sky->set_visible(false);
edit_type = edit_type_particles;
+ if ((edit_type->get_selected() + 3) > VisualShader::TYPE_PROCESS) {
+ custom_mode_box->set_visible(false);
+ } else {
+ custom_mode_box->set_visible(true);
+ }
mode = MODE_FLAGS_PARTICLES;
} else {
edit_type_particles->set_visible(false);
- edit_type_standart->set_visible(true);
+ edit_type_standard->set_visible(true);
edit_type_sky->set_visible(false);
- edit_type = edit_type_standart;
+ edit_type = edit_type_standard;
+ custom_mode_box->set_visible(false);
mode = MODE_FLAGS_SPATIAL_CANVASITEM;
}
visual_shader->set_shader_type(get_current_shader_type());
@@ -1175,26 +1354,15 @@ void VisualShaderEditor::_draw_color_over_button(Object *obj, Color p_color) {
}
void VisualShaderEditor::_update_created_node(GraphNode *node) {
- if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) {
- Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode");
- Color c = sb->get_border_color();
- Color ic;
- Color mono_color;
- if (((c.r + c.g + c.b) / 3) < 0.7) {
- mono_color = Color(1.0, 1.0, 1.0);
- ic = Color(0.0, 0.0, 0.0, 0.7);
- } else {
- mono_color = Color(0.0, 0.0, 0.0);
- ic = Color(1.0, 1.0, 1.0, 0.7);
- }
- mono_color.a = 0.85;
- c = mono_color;
+ const Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode");
+ Color c = sb->get_border_color();
+ const Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0, 0.85) : Color(0.0, 0.0, 0.0, 0.85);
+ c = mono_color;
- node->add_theme_color_override("title_color", c);
- c.a = 0.7;
- node->add_theme_color_override("close_color", c);
- node->add_theme_color_override("resizer_color", ic);
- }
+ node->add_theme_color_override("title_color", c);
+ c.a = 0.7;
+ node->add_theme_color_override("close_color", c);
+ node->add_theme_color_override("resizer_color", c);
}
void VisualShaderEditor::_update_uniforms(bool p_update_refs) {
@@ -1294,6 +1462,7 @@ void VisualShaderEditor::_update_graph() {
graph_plugin->clear_links();
graph_plugin->make_dirty(true);
+ graph_plugin->update_theme();
for (int n_i = 0; n_i < nodes.size(); n_i++) {
graph_plugin->add_node(type, nodes[n_i]);
@@ -1317,9 +1486,9 @@ void VisualShaderEditor::_update_graph() {
VisualShader::Type VisualShaderEditor::get_current_shader_type() const {
VisualShader::Type type;
if (mode & MODE_FLAGS_PARTICLES) {
- type = VisualShader::Type(edit_type->get_selected() + 3);
+ type = VisualShader::Type(edit_type->get_selected() + 3 + (custom_mode_enabled ? 3 : 0));
} else if (mode & MODE_FLAGS_SKY) {
- type = VisualShader::Type(edit_type->get_selected() + 6);
+ type = VisualShader::Type(edit_type->get_selected() + 8);
} else {
type = VisualShader::Type(edit_type->get_selected());
}
@@ -1442,6 +1611,92 @@ void VisualShaderEditor::_change_output_port_name(const String &p_text, Object *
undo_redo->commit_action();
}
+void VisualShaderEditor::_expand_output_port(int p_node, int p_port, bool p_expand) {
+ VisualShader::Type type = get_current_shader_type();
+
+ Ref<VisualShaderNode> node = visual_shader->get_node(type, p_node);
+ ERR_FAIL_COND(!node.is_valid());
+
+ if (p_expand) {
+ undo_redo->create_action(TTR("Expand Output Port"));
+ } else {
+ undo_redo->create_action(TTR("Shrink Output Port"));
+ }
+
+ undo_redo->add_do_method(node.ptr(), "_set_output_port_expanded", p_port, p_expand);
+ undo_redo->add_undo_method(node.ptr(), "_set_output_port_expanded", p_port, !p_expand);
+
+ int type_size = 0;
+ if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_VECTOR) {
+ type_size = 3;
+ }
+
+ List<VisualShader::Connection> conns;
+ visual_shader->get_node_connections(type, &conns);
+
+ for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
+ int from_node = E->get().from_node;
+ int from_port = E->get().from_port;
+ int to_node = E->get().to_node;
+ int to_port = E->get().to_port;
+
+ if (from_node == p_node) {
+ if (p_expand) {
+ if (from_port > p_port) { // reconnect ports after expanded ports
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port);
+
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port);
+
+ undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port + type_size, to_node, to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port + type_size, to_node, to_port);
+
+ undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port + type_size, to_node, to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port + type_size, to_node, to_port);
+ }
+ } else {
+ if (from_port > p_port + type_size) { // reconnect ports after expanded ports
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port);
+
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port);
+
+ undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_port - type_size, to_node, to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port - type_size, to_node, to_port);
+
+ undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port - type_size, to_node, to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port - type_size, to_node, to_port);
+ } else if (from_port > p_port) { // disconnect component ports
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port);
+
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port);
+ }
+ }
+ }
+ }
+
+ int preview_port = node->get_output_port_for_preview();
+ if (p_expand) {
+ if (preview_port > p_port) {
+ undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", preview_port + type_size);
+ undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", preview_port);
+ }
+ } else {
+ if (preview_port > p_port + type_size) {
+ undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", preview_port - type_size);
+ undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", preview_port);
+ }
+ }
+
+ undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node);
+ undo_redo->commit_action();
+}
+
void VisualShaderEditor::_remove_input_port(int p_node, int p_port) {
VisualShader::Type type = get_current_shader_type();
Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node);
@@ -1590,7 +1845,7 @@ void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p
Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(node.ptr());
Control *text_box = nullptr;
if (!expression_node.is_null()) {
- text_box = expression_node->get_control(0);
+ text_box = expression_node->is_ctrl_pressed(0);
if (text_box) {
text_box->set_custom_minimum_size(Size2(0, 0));
}
@@ -1670,7 +1925,7 @@ void VisualShaderEditor::_comment_title_text_changed(const String &p_new_text) {
comment_title_change_popup->set_size(Size2(-1, -1));
}
-void VisualShaderEditor::_comment_title_text_entered(const String &p_new_text) {
+void VisualShaderEditor::_comment_title_text_submitted(const String &p_new_text) {
comment_title_change_popup->hide();
}
@@ -1818,47 +2073,6 @@ void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node,
editing_port = p_port;
}
-void VisualShaderEditor::_add_custom_node(const String &p_path) {
- int idx = -1;
-
- for (int i = custom_node_option_idx; i < add_options.size(); i++) {
- if (add_options[i].script.is_valid()) {
- if (add_options[i].script->get_path() == p_path) {
- idx = i;
- break;
- }
- }
- }
- if (idx != -1) {
- _add_node(idx);
- }
-}
-
-void VisualShaderEditor::_add_cubemap_node(const String &p_path) {
- VisualShaderNodeCubemap *cubemap = (VisualShaderNodeCubemap *)_add_node(cubemap_node_option_idx, -1);
- cubemap->set_cube_map(ResourceLoader::load(p_path));
-}
-
-void VisualShaderEditor::_add_texture2d_node(const String &p_path) {
- VisualShaderNodeTexture *texture2d = (VisualShaderNodeTexture *)_add_node(texture2d_node_option_idx, -1);
- texture2d->set_texture(ResourceLoader::load(p_path));
-}
-
-void VisualShaderEditor::_add_texture2d_array_node(const String &p_path) {
- VisualShaderNodeTexture2DArray *texture2d_array = (VisualShaderNodeTexture2DArray *)_add_node(texture2d_array_node_option_idx, -1);
- texture2d_array->set_texture_array(ResourceLoader::load(p_path));
-}
-
-void VisualShaderEditor::_add_texture3d_node(const String &p_path) {
- VisualShaderNodeTexture3D *texture3d = (VisualShaderNodeTexture3D *)_add_node(texture3d_node_option_idx, -1);
- texture3d->set_texture(ResourceLoader::load(p_path));
-}
-
-void VisualShaderEditor::_add_curve_node(const String &p_path) {
- VisualShaderNodeCurveTexture *curve = (VisualShaderNodeCurveTexture *)_add_node(curve_node_option_idx, -1);
- curve->set_texture(ResourceLoader::load(p_path));
-}
-
void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) {
// FLOAT_OP
{
@@ -1950,6 +2164,16 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) {
}
}
+ //UV_FUNC
+ {
+ VisualShaderNodeUVFunc *uvFunc = Object::cast_to<VisualShaderNodeUVFunc>(p_node);
+
+ if (uvFunc) {
+ uvFunc->set_function((VisualShaderNodeUVFunc::Function)p_op_idx);
+ return;
+ }
+ }
+
// IS
{
VisualShaderNodeIs *is = Object::cast_to<VisualShaderNodeIs>(p_node);
@@ -2047,16 +2271,18 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) {
}
}
-VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) {
- ERR_FAIL_INDEX_V(p_idx, add_options.size(), nullptr);
+void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_path, int p_node_idx) {
+ ERR_FAIL_INDEX(p_idx, add_options.size());
+
+ VisualShader::Type type = get_current_shader_type();
Ref<VisualShaderNode> vsnode;
bool is_custom = add_options[p_idx].is_custom;
if (!is_custom && add_options[p_idx].type != String()) {
- VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(add_options[p_idx].type));
- ERR_FAIL_COND_V(!vsn, nullptr);
+ VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(add_options[p_idx].type));
+ ERR_FAIL_COND(!vsn);
VisualShaderNodeFloatConstant *constant = Object::cast_to<VisualShaderNodeFloatConstant>(vsn);
@@ -2076,12 +2302,35 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) {
}
}
+ VisualShaderNodeUniformRef *uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(vsn);
+
+ if (uniform_ref && to_node != -1 && to_slot != -1) {
+ VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot);
+ bool success = false;
+
+ for (int i = 0; i < uniform_ref->get_uniforms_count(); i++) {
+ if (uniform_ref->get_port_type_by_index(i) == input_port_type) {
+ uniform_ref->set_uniform_name(uniform_ref->get_uniform_name_by_index(i));
+ success = true;
+ break;
+ }
+ }
+ if (!success) {
+ for (int i = 0; i < uniform_ref->get_uniforms_count(); i++) {
+ if (visual_shader->is_port_types_compatible(uniform_ref->get_port_type_by_index(i), input_port_type)) {
+ uniform_ref->set_uniform_name(uniform_ref->get_uniform_name_by_index(i));
+ break;
+ }
+ }
+ }
+ }
+
vsnode = Ref<VisualShaderNode>(vsn);
} else {
- ERR_FAIL_COND_V(add_options[p_idx].script.is_null(), nullptr);
+ ERR_FAIL_COND(add_options[p_idx].script.is_null());
String base_type = add_options[p_idx].script->get_instance_base_type();
- VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(base_type));
- ERR_FAIL_COND_V(!vsn, nullptr);
+ VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(base_type));
+ ERR_FAIL_COND(!vsn);
vsnode = Ref<VisualShaderNode>(vsn);
vsnode->set_script(add_options[p_idx].script);
}
@@ -2096,11 +2345,13 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) {
}
saved_node_pos_dirty = false;
- VisualShader::Type type = get_current_shader_type();
-
int id_to_use = visual_shader->get_valid_node_id(type);
- undo_redo->create_action(TTR("Add Node to Visual Shader"));
+ if (p_resource_path.is_empty()) {
+ undo_redo->create_action(TTR("Add Node to Visual Shader"));
+ } else {
+ id_to_use += p_node_idx;
+ }
undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, vsnode, position, id_to_use);
undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_to_use);
undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_to_use);
@@ -2157,6 +2408,13 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) {
undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, _from_node, _from_slot, to_node, to_slot);
undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, _from_node, _from_slot, to_node, to_slot);
} else {
+ // Need to setting up Input node properly before committing since `is_port_types_compatible` (calling below) is using `mode` and `shader_type`.
+ VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(vsnode.ptr());
+ if (input) {
+ input->set_shader_mode(visual_shader->get_mode());
+ input->set_shader_type(visual_shader->get_shader_type());
+ }
+
// Attempting to connect to the first correct port.
for (int i = 0; i < vsnode->get_output_port_count(); i++) {
if (visual_shader->is_port_types_compatible(vsnode->get_output_port_type(i), input_port_type)) {
@@ -2215,8 +2473,35 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) {
graph_plugin->call_deferred("update_curve", id_to_use);
}
- undo_redo->commit_action();
- return vsnode.ptr();
+ VisualShaderNodeCurve3Texture *curve3 = Object::cast_to<VisualShaderNodeCurve3Texture>(vsnode.ptr());
+ if (curve3) {
+ graph_plugin->call_deferred("update_curve3", id_to_use);
+ }
+
+ if (p_resource_path.is_empty()) {
+ undo_redo->commit_action();
+ } else {
+ //post-initialization
+
+ VisualShaderNodeTexture *texture2d = Object::cast_to<VisualShaderNodeTexture>(vsnode.ptr());
+ VisualShaderNodeTexture3D *texture3d = Object::cast_to<VisualShaderNodeTexture3D>(vsnode.ptr());
+
+ if (texture2d || texture3d || curve || curve3) {
+ undo_redo->add_do_method(vsnode.ptr(), "set_texture", ResourceLoader::load(p_resource_path));
+ return;
+ }
+
+ VisualShaderNodeCubemap *cubemap = Object::cast_to<VisualShaderNodeCubemap>(vsnode.ptr());
+ if (cubemap) {
+ undo_redo->add_do_method(vsnode.ptr(), "set_cube_map", ResourceLoader::load(p_resource_path));
+ return;
+ }
+
+ VisualShaderNodeTexture2DArray *texture2d_array = Object::cast_to<VisualShaderNodeTexture2DArray>(vsnode.ptr());
+ if (texture2d_array) {
+ undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path));
+ }
+ }
}
void VisualShaderEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node) {
@@ -2433,7 +2718,7 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) {
for (Set<int>::Element *E = current_set.front(); E; E = E->next()) {
int node_id = E->get();
Ref<VisualShaderNode> node = visual_shader->get_node(type_id, node_id);
- bool catched = false;
+ bool caught = false;
Variant var;
// float
@@ -2442,112 +2727,112 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) {
if (float_const.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeFloatConstant", "VisualShaderNodeFloatUniform");
var = float_const->get_constant();
- catched = true;
+ caught = true;
}
} else {
Ref<VisualShaderNodeFloatUniform> float_uniform = Object::cast_to<VisualShaderNodeFloatUniform>(node.ptr());
if (float_uniform.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeFloatUniform", "VisualShaderNodeFloatConstant");
var = float_uniform->get_default_value();
- catched = true;
+ caught = true;
}
}
// int
- if (!catched) {
+ if (!caught) {
if (!p_vice_versa) {
Ref<VisualShaderNodeIntConstant> int_const = Object::cast_to<VisualShaderNodeIntConstant>(node.ptr());
if (int_const.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeIntConstant", "VisualShaderNodeIntUniform");
var = int_const->get_constant();
- catched = true;
+ caught = true;
}
} else {
Ref<VisualShaderNodeIntUniform> int_uniform = Object::cast_to<VisualShaderNodeIntUniform>(node.ptr());
if (int_uniform.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeIntUniform", "VisualShaderNodeIntConstant");
var = int_uniform->get_default_value();
- catched = true;
+ caught = true;
}
}
}
// boolean
- if (!catched) {
+ if (!caught) {
if (!p_vice_versa) {
Ref<VisualShaderNodeBooleanConstant> boolean_const = Object::cast_to<VisualShaderNodeBooleanConstant>(node.ptr());
if (boolean_const.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeBooleanConstant", "VisualShaderNodeBooleanUniform");
var = boolean_const->get_constant();
- catched = true;
+ caught = true;
}
} else {
Ref<VisualShaderNodeBooleanUniform> boolean_uniform = Object::cast_to<VisualShaderNodeBooleanUniform>(node.ptr());
if (boolean_uniform.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeBooleanUniform", "VisualShaderNodeBooleanConstant");
var = boolean_uniform->get_default_value();
- catched = true;
+ caught = true;
}
}
}
// vec3
- if (!catched) {
+ if (!caught) {
if (!p_vice_versa) {
Ref<VisualShaderNodeVec3Constant> vec3_const = Object::cast_to<VisualShaderNodeVec3Constant>(node.ptr());
if (vec3_const.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeVec3Constant", "VisualShaderNodeVec3Uniform");
var = vec3_const->get_constant();
- catched = true;
+ caught = true;
}
} else {
Ref<VisualShaderNodeVec3Uniform> vec3_uniform = Object::cast_to<VisualShaderNodeVec3Uniform>(node.ptr());
if (vec3_uniform.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeVec3Uniform", "VisualShaderNodeVec3Constant");
var = vec3_uniform->get_default_value();
- catched = true;
+ caught = true;
}
}
}
// color
- if (!catched) {
+ if (!caught) {
if (!p_vice_versa) {
Ref<VisualShaderNodeColorConstant> color_const = Object::cast_to<VisualShaderNodeColorConstant>(node.ptr());
if (color_const.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeColorConstant", "VisualShaderNodeColorUniform");
var = color_const->get_constant();
- catched = true;
+ caught = true;
}
} else {
Ref<VisualShaderNodeColorUniform> color_uniform = Object::cast_to<VisualShaderNodeColorUniform>(node.ptr());
if (color_uniform.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeColorUniform", "VisualShaderNodeColorConstant");
var = color_uniform->get_default_value();
- catched = true;
+ caught = true;
}
}
}
// transform
- if (!catched) {
+ if (!caught) {
if (!p_vice_versa) {
Ref<VisualShaderNodeTransformConstant> transform_const = Object::cast_to<VisualShaderNodeTransformConstant>(node.ptr());
if (transform_const.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeTransformConstant", "VisualShaderNodeTransformUniform");
var = transform_const->get_constant();
- catched = true;
+ caught = true;
}
} else {
Ref<VisualShaderNodeTransformUniform> transform_uniform = Object::cast_to<VisualShaderNodeTransformUniform>(node.ptr());
if (transform_uniform.is_valid()) {
_replace_node(type_id, node_id, "VisualShaderNodeTransformUniform", "VisualShaderNodeTransformConstant");
var = transform_uniform->get_default_value();
- catched = true;
+ caught = true;
}
}
}
- ERR_CONTINUE(!catched);
+ ERR_CONTINUE(!caught);
int preview_port = node->get_output_port_for_preview();
if (!p_vice_versa) {
@@ -2768,10 +3053,10 @@ void VisualShaderEditor::_notification(int p_what) {
// collapse tree by default
- TreeItem *category = members->get_root()->get_children();
+ TreeItem *category = members->get_root()->get_first_child();
while (category) {
category->set_collapsed(true);
- TreeItem *sub_category = category->get_children();
+ TreeItem *sub_category = category->get_first_child();
while (sub_category) {
sub_category->set_collapsed(true);
sub_category = sub_category->get_next();
@@ -2792,9 +3077,6 @@ void VisualShaderEditor::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
highend_label->set_modulate(get_theme_color("vulkan_color", "Editor"));
- error_panel->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree"));
- error_label->add_theme_color_override("font_color", get_theme_color("error_color", "Editor"));
-
node_filter->set_right_icon(Control::get_theme_icon("Search", "EditorIcons"));
preview_shader->set_icon(Control::get_theme_icon("Shader", "EditorIcons"));
@@ -2803,6 +3085,7 @@ void VisualShaderEditor::_notification(int p_what) {
Color background_color = EDITOR_GET("text_editor/highlighting/background_color");
Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
+ Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color");
Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color");
Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color");
Color function_color = EDITOR_GET("text_editor/highlighting/function_color");
@@ -2812,7 +3095,11 @@ void VisualShaderEditor::_notification(int p_what) {
preview_text->add_theme_color_override("background_color", background_color);
for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) {
- syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ if (ShaderLanguage::is_control_flow_keyword(E->get())) {
+ syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ } else {
+ syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ }
}
preview_text->add_theme_font_override("font", get_theme_font("expression", "EditorFonts"));
@@ -2826,9 +3113,14 @@ void VisualShaderEditor::_notification(int p_what) {
syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
syntax_highlighter->add_color_region("//", "", comment_color, true);
- error_text->add_theme_font_override("font", get_theme_font("status_source", "EditorFonts"));
- error_text->add_theme_font_size_override("font_size", get_theme_font_size("status_source_size", "EditorFonts"));
- error_text->add_theme_color_override("font_color", get_theme_color("error_color", "Editor"));
+ preview_text->clear_comment_delimiters();
+ preview_text->add_comment_delimiter("/*", "*/", false);
+ preview_text->add_comment_delimiter("//", "", true);
+
+ error_panel->add_theme_style_override("panel", get_theme_stylebox("panel", "Panel"));
+ error_label->add_theme_font_override("font", get_theme_font("status_source", "EditorFonts"));
+ error_label->add_theme_font_size_override("font_size", get_theme_font_size("status_source_size", "EditorFonts"));
+ error_label->add_theme_color_override("font_color", get_theme_color("error_color", "Editor"));
}
tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Tools", "EditorIcons"));
@@ -3050,8 +3342,18 @@ void VisualShaderEditor::_mode_selected(int p_id) {
int offset = 0;
if (mode & MODE_FLAGS_PARTICLES) {
offset = 3;
+ if (p_id + offset > VisualShader::TYPE_PROCESS) {
+ custom_mode_box->set_visible(false);
+ custom_mode_enabled = false;
+ } else {
+ custom_mode_box->set_visible(true);
+ if (custom_mode_box->is_pressed()) {
+ custom_mode_enabled = true;
+ offset += 3;
+ }
+ }
} else if (mode & MODE_FLAGS_SKY) {
- offset = 6;
+ offset = 8;
}
visual_shader->set_shader_type(VisualShader::Type(p_id + offset));
@@ -3059,6 +3361,21 @@ void VisualShaderEditor::_mode_selected(int p_id) {
_update_graph();
}
+void VisualShaderEditor::_custom_mode_toggled(bool p_enabled) {
+ if (!(mode & MODE_FLAGS_PARTICLES)) {
+ return;
+ }
+ custom_mode_enabled = p_enabled;
+ int id = edit_type->get_selected() + 3;
+ if (p_enabled) {
+ visual_shader->set_shader_type(VisualShader::Type(id + 3));
+ } else {
+ visual_shader->set_shader_type(VisualShader::Type(id));
+ }
+ _update_options_menu();
+ _update_graph();
+}
+
void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input, String p_name) {
String prev_name = p_input->get_input_name();
@@ -3211,14 +3528,14 @@ void VisualShaderEditor::_member_cancel() {
}
void VisualShaderEditor::_tools_menu_option(int p_idx) {
- TreeItem *category = members->get_root()->get_children();
+ TreeItem *category = members->get_root()->get_first_child();
switch (p_idx) {
case EXPAND_ALL:
while (category) {
category->set_collapsed(false);
- TreeItem *sub_category = category->get_children();
+ TreeItem *sub_category = category->get_first_child();
while (sub_category) {
sub_category->set_collapsed(false);
sub_category = sub_category->get_next();
@@ -3232,7 +3549,7 @@ void VisualShaderEditor::_tools_menu_option(int p_idx) {
while (category) {
category->set_collapsed(true);
- TreeItem *sub_category = category->get_children();
+ TreeItem *sub_category = category->get_first_child();
while (sub_category) {
sub_category->set_collapsed(true);
sub_category = sub_category->get_next();
@@ -3334,47 +3651,60 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
saved_node_pos_dirty = true;
_add_node(idx, add_options[idx].sub_func);
} else if (d.has("files")) {
+ undo_redo->create_action(TTR("Add Node(s) to Visual Shader"));
+
if (d["files"].get_type() == Variant::PACKED_STRING_ARRAY) {
- int j = 0;
PackedStringArray arr = d["files"];
for (int i = 0; i < arr.size(); i++) {
String type = ResourceLoader::get_resource_type(arr[i]);
if (type == "GDScript") {
Ref<Script> script = ResourceLoader::load(arr[i]);
if (script->get_instance_base_type() == "VisualShaderNodeCustom") {
- saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE);
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
- _add_custom_node(arr[i]);
- j++;
+
+ int idx = -1;
+
+ for (int j = custom_node_option_idx; j < add_options.size(); j++) {
+ if (add_options[j].script.is_valid()) {
+ if (add_options[j].script->get_path() == arr[i]) {
+ idx = j;
+ break;
+ }
+ }
+ }
+ if (idx != -1) {
+ _add_node(idx, -1, arr[i], i);
+ }
}
} else if (type == "CurveTexture") {
- saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE);
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
- _add_curve_node(arr[i]);
- j++;
+ _add_node(curve_node_option_idx, -1, arr[i], i);
+ } else if (type == "Curve3Texture") {
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
+ saved_node_pos_dirty = true;
+ _add_node(curve3_node_option_idx, -1, arr[i], i);
} else if (ClassDB::get_parent_class(type) == "Texture2D") {
- saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE);
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
- _add_texture2d_node(arr[i]);
- j++;
+ _add_node(texture2d_node_option_idx, -1, arr[i], i);
} else if (type == "Texture2DArray") {
- saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE);
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
- _add_texture2d_array_node(arr[i]);
- j++;
+ _add_node(texture2d_array_node_option_idx, -1, arr[i], i);
} else if (ClassDB::get_parent_class(type) == "Texture3D") {
- saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE);
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
- _add_texture3d_node(arr[i]);
- j++;
+ _add_node(texture3d_node_option_idx, -1, arr[i], i);
} else if (type == "Cubemap") {
- saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE);
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
- _add_cubemap_node(arr[i]);
- j++;
+ _add_node(cubemap_node_option_idx, -1, arr[i], i);
}
}
}
+ undo_redo->commit_action();
}
}
}
@@ -3430,17 +3760,18 @@ void VisualShaderEditor::_update_preview() {
Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(visual_shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(visual_shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type);
for (int i = 0; i < preview_text->get_line_count(); i++) {
- preview_text->set_line_as_marked(i, false);
+ preview_text->set_line_background_color(i, Color(0, 0, 0, 0));
}
if (err != OK) {
- preview_text->set_line_as_marked(sl.get_error_line() - 1, true);
- error_text->set_visible(true);
+ Color error_line_color = EDITOR_GET("text_editor/highlighting/mark_color");
+ preview_text->set_line_background_color(sl.get_error_line() - 1, error_line_color);
+ error_panel->show();
String text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text();
- error_text->set_text(text);
+ error_label->set_text(text);
shader_error = true;
} else {
- error_text->set_visible(false);
+ error_panel->hide();
shader_error = false;
}
}
@@ -3470,10 +3801,11 @@ void VisualShaderEditor::_bind_methods() {
ClassDB::bind_method("_float_constant_selected", &VisualShaderEditor::_float_constant_selected);
ClassDB::bind_method("_update_constant", &VisualShaderEditor::_update_constant);
ClassDB::bind_method("_update_uniform", &VisualShaderEditor::_update_uniform);
+ ClassDB::bind_method("_expand_output_port", &VisualShaderEditor::_expand_output_port);
- ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("drop_data_fw"), &VisualShaderEditor::drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("_drop_data_fw"), &VisualShaderEditor::drop_data_fw);
ClassDB::bind_method("_is_available", &VisualShaderEditor::_is_available);
}
@@ -3546,17 +3878,23 @@ VisualShaderEditor::VisualShaderEditor() {
graph->get_zoom_hbox()->add_child(vs);
graph->get_zoom_hbox()->move_child(vs, 0);
- edit_type_standart = memnew(OptionButton);
- edit_type_standart->add_item(TTR("Vertex"));
- edit_type_standart->add_item(TTR("Fragment"));
- edit_type_standart->add_item(TTR("Light"));
- edit_type_standart->select(1);
- edit_type_standart->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
+ custom_mode_box = memnew(CheckBox);
+ custom_mode_box->set_text(TTR("Custom"));
+ custom_mode_box->set_pressed(false);
+ custom_mode_box->set_visible(false);
+ custom_mode_box->connect("toggled", callable_mp(this, &VisualShaderEditor::_custom_mode_toggled));
+
+ edit_type_standard = memnew(OptionButton);
+ edit_type_standard->add_item(TTR("Vertex"));
+ edit_type_standard->add_item(TTR("Fragment"));
+ edit_type_standard->add_item(TTR("Light"));
+ edit_type_standard->select(1);
+ edit_type_standard->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
edit_type_particles = memnew(OptionButton);
- edit_type_particles->add_item(TTR("Emit"));
+ edit_type_particles->add_item(TTR("Start"));
edit_type_particles->add_item(TTR("Process"));
- edit_type_particles->add_item(TTR("End"));
+ edit_type_particles->add_item(TTR("Collide"));
edit_type_particles->select(0);
edit_type_particles->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
@@ -3565,14 +3903,16 @@ VisualShaderEditor::VisualShaderEditor() {
edit_type_sky->select(0);
edit_type_sky->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
- edit_type = edit_type_standart;
+ edit_type = edit_type_standard;
+ graph->get_zoom_hbox()->add_child(custom_mode_box);
+ graph->get_zoom_hbox()->move_child(custom_mode_box, 0);
+ graph->get_zoom_hbox()->add_child(edit_type_standard);
+ graph->get_zoom_hbox()->move_child(edit_type_standard, 0);
graph->get_zoom_hbox()->add_child(edit_type_particles);
graph->get_zoom_hbox()->move_child(edit_type_particles, 0);
graph->get_zoom_hbox()->add_child(edit_type_sky);
graph->get_zoom_hbox()->move_child(edit_type_sky, 0);
- graph->get_zoom_hbox()->add_child(edit_type_standart);
- graph->get_zoom_hbox()->move_child(edit_type_standart, 0);
add_node = memnew(Button);
add_node->set_flat(true);
@@ -3601,19 +3941,23 @@ VisualShaderEditor::VisualShaderEditor() {
preview_vbox = memnew(VBoxContainer);
preview_window->add_child(preview_vbox);
+ preview_vbox->add_theme_constant_override("separation", 0);
preview_text = memnew(CodeEdit);
- syntax_highlighter.instance();
+ syntax_highlighter.instantiate();
preview_vbox->add_child(preview_text);
preview_text->set_v_size_flags(Control::SIZE_EXPAND_FILL);
preview_text->set_syntax_highlighter(syntax_highlighter);
preview_text->set_draw_line_numbers(true);
preview_text->set_readonly(true);
- error_text = memnew(Label);
- preview_vbox->add_child(error_text);
- error_text->set_autowrap(true);
- error_text->set_visible(false);
+ error_panel = memnew(PanelContainer);
+ preview_vbox->add_child(error_panel);
+ error_panel->set_visible(false);
+
+ error_label = memnew(Label);
+ error_panel->add_child(error_label);
+ error_label->set_autowrap(true);
///////////////////////////////////////
// POPUP MENU
@@ -3709,7 +4053,7 @@ VisualShaderEditor::VisualShaderEditor() {
comment_title_change_edit = memnew(LineEdit);
comment_title_change_edit->set_expand_to_text_length_enabled(true);
comment_title_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_comment_title_text_changed));
- comment_title_change_edit->connect("text_entered", callable_mp(this, &VisualShaderEditor::_comment_title_text_entered));
+ comment_title_change_edit->connect("text_submitted", callable_mp(this, &VisualShaderEditor::_comment_title_text_submitted));
comment_title_change_popup->add_child(comment_title_change_edit);
comment_title_change_edit->set_size(Size2(-1, -1));
comment_title_change_popup->set_size(Size2(-1, -1));
@@ -3788,9 +4132,10 @@ VisualShaderEditor::VisualShaderEditor() {
// INPUT
+ const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes.");
+
// SPATIAL-FOR-ALL
- const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes.");
add_options.push_back(AddOption("Camera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "camera"), "camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("InvCamera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_camera"), "inv_camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("InvProjection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection"), "inv_projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
@@ -3809,6 +4154,23 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM));
add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv"), "uv", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM));
+ // PARTICLES-FOR-ALL
+
+ add_options.push_back(AddOption("Active", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Alpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("AttractorForce", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "attractor_force"), "attractor_force", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Custom", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("CustomAlpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Delta", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("EmissionTransform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Index", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("LifeTime", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Restart", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Transform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("Velocity", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+
/////////////////
add_options.push_back(AddOption("Input", "Input", "Common", "VisualShaderNodeInput", TTR("Input parameter.")));
@@ -3821,11 +4183,12 @@ VisualShaderEditor::VisualShaderEditor() {
const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode.");
const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode.");
const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode.");
- const String input_param_for_emit_shader_mode = TTR("'%s' input parameter for emit shader mode.");
+ const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode.");
const String input_param_for_process_shader_mode = TTR("'%s' input parameter for process shader mode.");
- const String input_param_for_end_shader_mode = TTR("'%s' input parameter for end shader mode.");
- const String input_param_for_emit_and_process_shader_mode = TTR("'%s' input parameter for emit and process shader mode.");
- const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader mode.");
+ const String input_param_for_collide_shader_mode = TTR("'%s' input parameter for collide shader mode.");
+ const String input_param_for_start_and_process_shader_mode = TTR("'%s' input parameter for start and process shader modes.");
+ const String input_param_for_process_and_collide_shader_mode = TTR("'%s' input parameter for process and collide shader modes.");
+ const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader modes.");
add_options.push_back(AddOption("Alpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("Binormal", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL));
@@ -3902,50 +4265,6 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM));
add_options.push_back(AddOption("World", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM));
- // PARTICLES INPUTS
-
- add_options.push_back(AddOption("Active", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Alpha", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Color", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Custom", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("CustomAlpha", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Delta", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("EmissionTransform", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Index", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("LifeTime", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Restart", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Time", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Transform", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Velocity", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-
- add_options.push_back(AddOption("Active", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Alpha", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Color", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Custom", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("CustomAlpha", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Delta", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("EmissionTransform", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Index", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("LifeTime", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Restart", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Time", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Transform", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Velocity", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-
- add_options.push_back(AddOption("Active", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Alpha", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Color", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Custom", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("CustomAlpha", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Delta", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("EmissionTransform", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Index", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("LifeTime", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Restart", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Time", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Transform", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
- add_options.push_back(AddOption("Velocity", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-
// SKY INPUTS
add_options.push_back(AddOption("AtCubeMapPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_cubemap_pass"), "at_cubemap_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY));
@@ -3978,6 +4297,22 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("SkyCoords", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "sky_coords"), "sky_coords", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY));
add_options.push_back(AddOption("Time", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY));
+ // PARTICLES
+
+ add_options.push_back(AddOption("CollisionDepth", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_depth"), "collision_depth", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("CollisionNormal", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_normal"), "collision_normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+
+ add_options.push_back(AddOption("EmitParticle", "Particles", "", "VisualShaderNodeParticleEmit", "", -1, -1, -1, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("ParticleAccelerator", "Particles", "", "VisualShaderNodeParticleAccelerator", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("ParticleRandomness", "Particles", "", "VisualShaderNodeParticleRandomness", "", -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("MultiplyByAxisAngle", "Particles", "Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", "A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters.", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+
+ add_options.push_back(AddOption("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+ add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
+ add_options.push_back(AddOption("ConeVelocity", "Particles", "Velocity", "VisualShaderNodeParticleConeVelocity", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
// SCALAR
add_options.push_back(AddOption("FloatFunc", "Scalar", "Common", "VisualShaderNodeFloatFunc", TTR("Float function."), -1, VisualShaderNode::PORT_TYPE_SCALAR));
@@ -4065,16 +4400,22 @@ VisualShaderEditor::VisualShaderEditor() {
// TEXTURES
+ add_options.push_back(AddOption("UVFunc", "Textures", "Common", "VisualShaderNodeUVFunc", TTR("Function to be applied on texture coordinates."), -1, VisualShaderNode::PORT_TYPE_VECTOR));
+
cubemap_node_option_idx = add_options.size();
add_options.push_back(AddOption("CubeMap", "Textures", "Functions", "VisualShaderNodeCubemap", TTR("Perform the cubic texture lookup."), -1, -1));
curve_node_option_idx = add_options.size();
add_options.push_back(AddOption("CurveTexture", "Textures", "Functions", "VisualShaderNodeCurveTexture", TTR("Perform the curve texture lookup."), -1, -1));
+ curve3_node_option_idx = add_options.size();
+ add_options.push_back(AddOption("CurveTexture3", "Textures", "Functions", "VisualShaderNodeCurve3Texture", TTR("Perform the ternary curve texture lookup."), -1, -1));
texture2d_node_option_idx = add_options.size();
add_options.push_back(AddOption("Texture2D", "Textures", "Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."), -1, -1));
texture2d_array_node_option_idx = add_options.size();
add_options.push_back(AddOption("Texture2DArray", "Textures", "Functions", "VisualShaderNodeTexture2DArray", TTR("Perform the 2D-array texture lookup."), -1, -1, -1, -1, -1));
texture3d_node_option_idx = add_options.size();
add_options.push_back(AddOption("Texture3D", "Textures", "Functions", "VisualShaderNodeTexture3D", TTR("Perform the 3D texture lookup."), -1, -1));
+ add_options.push_back(AddOption("UVPanning", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply panning function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_PANNING, VisualShaderNode::PORT_TYPE_VECTOR));
+ add_options.push_back(AddOption("UVScaling", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply scaling function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_SCALING, VisualShaderNode::PORT_TYPE_VECTOR));
add_options.push_back(AddOption("CubeMapUniform", "Textures", "Variables", "VisualShaderNodeCubemapUniform", TTR("Cubic texture uniform lookup."), -1, -1));
add_options.push_back(AddOption("TextureUniform", "Textures", "Variables", "VisualShaderNodeTextureUniform", TTR("2D texture uniform lookup."), -1, -1));
@@ -4091,6 +4432,7 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("TransformDecompose", "Transform", "Composition", "VisualShaderNodeTransformDecompose", TTR("Decomposes transform to four vectors.")));
add_options.push_back(AddOption("Determinant", "Transform", "Functions", "VisualShaderNodeDeterminant", TTR("Calculates the determinant of a transform."), -1, VisualShaderNode::PORT_TYPE_SCALAR));
+ add_options.push_back(AddOption("GetBillboardMatrix", "Transform", "Functions", "VisualShaderNodeBillboard", TTR("Calculates how the object should face the camera to be applied on Model View Matrix output port for 3D objects."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("Inverse", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the inverse of a transform."), VisualShaderNodeTransformFunc::FUNC_INVERSE, VisualShaderNode::PORT_TYPE_TRANSFORM));
add_options.push_back(AddOption("Transpose", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the transpose of a transform."), VisualShaderNodeTransformFunc::FUNC_TRANSPOSE, VisualShaderNode::PORT_TYPE_TRANSFORM));
@@ -4193,20 +4535,13 @@ VisualShaderEditor::VisualShaderEditor() {
_update_options_menu();
- error_panel = memnew(PanelContainer);
- add_child(error_panel);
- error_label = memnew(Label);
- error_panel->add_child(error_label);
- error_label->set_text("eh");
- error_panel->hide();
-
undo_redo = EditorNode::get_singleton()->get_undo_redo();
Ref<VisualShaderNodePluginDefault> default_plugin;
- default_plugin.instance();
+ default_plugin.instantiate();
add_plugin(default_plugin);
- graph_plugin.instance();
+ graph_plugin.instantiate();
property_editor = memnew(CustomPropertyEditor);
add_child(property_editor);
@@ -4280,7 +4615,7 @@ public:
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("int", "EditorIcons"),
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Vector3", "EditorIcons"),
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("bool", "EditorIcons"),
- EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform", "EditorIcons"),
+ EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform3D", "EditorIcons"),
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons"),
};
@@ -4325,7 +4660,7 @@ public:
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("int", "EditorIcons"),
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("bool", "EditorIcons"),
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Vector3", "EditorIcons"),
- EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform", "EditorIcons"),
+ EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform3D", "EditorIcons"),
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Color", "EditorIcons"),
EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons"),
};
@@ -4516,7 +4851,7 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par
if (Object::cast_to<EditorPropertyResource>(prop)) {
Object::cast_to<EditorPropertyResource>(prop)->set_use_sub_inspector(false);
prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
- } else if (Object::cast_to<EditorPropertyTransform>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) {
+ } else if (Object::cast_to<EditorPropertyTransform3D>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) {
prop->set_custom_minimum_size(Size2(250 * EDSCALE, 0));
} else if (Object::cast_to<EditorPropertyFloat>(prop)) {
prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
@@ -4633,7 +4968,7 @@ void EditorInspectorShaderModePlugin::parse_begin(Object *p_object) {
//do none
}
-bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) {
+bool EditorInspectorShaderModePlugin::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 == "mode" && p_object->is_class("VisualShader") && p_type == Variant::INT) {
EditorPropertyShaderMode *editor = memnew(EditorPropertyShaderMode);
Vector<String> options = p_hint_text.split(",");
@@ -4661,14 +4996,14 @@ void VisualShaderNodePortPreview::_shader_changed() {
String shader_code = shader->generate_preview_shader(type, node, port, default_textures);
Ref<Shader> preview_shader;
- preview_shader.instance();
+ preview_shader.instantiate();
preview_shader->set_code(shader_code);
for (int i = 0; i < default_textures.size(); i++) {
preview_shader->set_default_texture_param(default_textures[i].name, default_textures[i].param);
}
Ref<ShaderMaterial> material;
- material.instance();
+ material.instantiate();
material->set_shader(preview_shader);
//find if a material is also being edited and copy parameters to this one
@@ -4753,7 +5088,7 @@ Ref<Resource> VisualShaderConversionPlugin::convert(const Ref<Resource> &p_resou
ERR_FAIL_COND_V(!vshader.is_valid(), Ref<Resource>());
Ref<Shader> shader;
- shader.instance();
+ shader.instantiate();
String code = vshader->get_code();
shader->set_code(code);
diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h
index 6d57d38cab..2b354db7b3 100644
--- a/editor/plugins/visual_shader_editor_plugin.h
+++ b/editor/plugins/visual_shader_editor_plugin.h
@@ -41,8 +41,8 @@
#include "scene/gui/tree.h"
#include "scene/resources/visual_shader.h"
-class VisualShaderNodePlugin : public Reference {
- GDCLASS(VisualShaderNodePlugin, Reference);
+class VisualShaderNodePlugin : public RefCounted {
+ GDCLASS(VisualShaderNodePlugin, RefCounted);
protected:
static void _bind_methods();
@@ -51,8 +51,8 @@ public:
virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node);
};
-class VisualShaderGraphPlugin : public Reference {
- GDCLASS(VisualShaderGraphPlugin, Reference);
+class VisualShaderGraphPlugin : public RefCounted {
+ GDCLASS(VisualShaderGraphPlugin, RefCounted);
private:
struct InputPort {
@@ -75,7 +75,7 @@ private:
LineEdit *uniform_name = nullptr;
OptionButton *const_op = nullptr;
CodeEdit *expression_edit = nullptr;
- CurveEditor *curve_editor = nullptr;
+ CurveEditor *curve_editors[3] = { nullptr, nullptr, nullptr };
};
Ref<VisualShader> visual_shader;
@@ -83,6 +83,8 @@ private:
List<VisualShader::Connection> connections;
bool dirty = false;
+ Color vector_expanded_color[3];
+
protected:
static void _bind_methods();
@@ -95,7 +97,7 @@ public:
void register_default_input_button(int p_node_id, int p_port_id, Button *p_button);
void register_constant_option_btn(int p_node_id, OptionButton *p_button);
void register_expression_edit(int p_node_id, CodeEdit *p_expression_edit);
- void register_curve_editor(int p_node_id, CurveEditor *p_curve_editor);
+ void register_curve_editor(int p_node_id, int p_index, CurveEditor *p_curve_editor);
void clear_links();
void set_shader_type(VisualShader::Type p_type);
bool is_preview_visible(int p_id) const;
@@ -115,10 +117,12 @@ public:
void update_uniform_refs();
void set_uniform_name(VisualShader::Type p_type, int p_node_id, const String &p_name);
void update_curve(int p_node_id);
+ void update_curve3(int p_node_id);
void update_constant(VisualShader::Type p_type, int p_node_id);
void set_expression(VisualShader::Type p_type, int p_node_id, const String &p_expression);
int get_constant_index(float p_constant) const;
void update_node_size(int p_node_id);
+ void update_theme();
VisualShader::Type get_shader_type() const;
VisualShaderGraphPlugin();
@@ -139,12 +143,11 @@ class VisualShaderEditor : public VBoxContainer {
Button *preview_shader;
OptionButton *edit_type = nullptr;
- OptionButton *edit_type_standart;
+ OptionButton *edit_type_standard;
OptionButton *edit_type_particles;
OptionButton *edit_type_sky;
-
- PanelContainer *error_panel;
- Label *error_label;
+ CheckBox *custom_mode_box;
+ bool custom_mode_enabled = false;
bool pending_update_preview;
bool shader_error;
@@ -152,7 +155,8 @@ class VisualShaderEditor : public VBoxContainer {
VBoxContainer *preview_vbox;
CodeEdit *preview_text;
Ref<CodeHighlighter> syntax_highlighter;
- Label *error_text;
+ PanelContainer *error_panel;
+ Label *error_label;
UndoRedo *undo_redo;
Point2 saved_node_pos;
@@ -188,7 +192,9 @@ class VisualShaderEditor : public VBoxContainer {
enum ParticlesTypeFlags {
TYPE_FLAGS_EMIT = 1,
TYPE_FLAGS_PROCESS = 2,
- TYPE_FLAGS_END = 4
+ TYPE_FLAGS_COLLIDE = 4,
+ TYPE_FLAGS_EMIT_CUSTOM = 8,
+ TYPE_FLAGS_PROCESS_CUSTOM = 16,
};
enum SkyTypeFlags {
@@ -284,21 +290,15 @@ class VisualShaderEditor : public VBoxContainer {
int texture3d_node_option_idx;
int custom_node_option_idx;
int curve_node_option_idx;
+ int curve3_node_option_idx;
List<String> keyword_list;
List<VisualShaderNodeUniformRef> uniform_refs;
void _draw_color_over_button(Object *obj, Color p_color);
- void _add_custom_node(const String &p_path);
- void _add_cubemap_node(const String &p_path);
- void _add_texture2d_node(const String &p_path);
- void _add_texture2d_array_node(const String &p_path);
- void _add_texture3d_node(const String &p_path);
- void _add_curve_node(const String &p_path);
-
void _setup_node(VisualShaderNode *p_node, int p_op_idx);
- VisualShaderNode *_add_node(int p_idx, int p_op_idx = -1);
+ void _add_node(int p_idx, int p_op_idx = -1, String p_resource_path = "", int p_node_idx = -1);
void _update_options_menu();
void _set_mode(int p_which);
@@ -360,7 +360,7 @@ class VisualShaderEditor : public VBoxContainer {
void _comment_title_popup_hide();
void _comment_title_popup_focus_out();
void _comment_title_text_changed(const String &p_new_text);
- void _comment_title_text_entered(const String &p_new_text);
+ void _comment_title_text_submitted(const String &p_new_text);
void _comment_desc_popup_show(const Point2 &p_position, int p_node_id);
void _comment_desc_popup_hide();
@@ -391,6 +391,7 @@ class VisualShaderEditor : public VBoxContainer {
Ref<VisualShaderGraphPlugin> graph_plugin;
void _mode_selected(int p_id);
+ void _custom_mode_toggled(bool p_enabled);
void _input_select_item(Ref<VisualShaderNodeInput> input, String name);
void _uniform_select_item(Ref<VisualShaderNodeUniformRef> p_uniform, String p_name);
@@ -408,6 +409,7 @@ class VisualShaderEditor : public VBoxContainer {
void _remove_output_port(int p_node, int p_port);
void _change_output_port_type(int p_type, int p_node, int p_port);
void _change_output_port_name(const String &p_text, Object *p_line_edit, int p_node, int p_port);
+ void _expand_output_port(int p_node, int p_port, bool p_expand);
void _expression_focus_out(Object *code_edit, int p_node);
@@ -505,7 +507,7 @@ class EditorInspectorShaderModePlugin : public EditorInspectorPlugin {
public:
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
- virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false) override;
+ virtual bool 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 = false) override;
virtual void parse_end() override;
};
diff --git a/editor/plugins/gi_probe_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp
index f309c5da01..162379a49d 100644
--- a/editor/plugins/gi_probe_editor_plugin.cpp
+++ b/editor/plugins/voxel_gi_editor_plugin.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* gi_probe_editor_plugin.cpp */
+/* voxel_gi_editor_plugin.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,51 +28,48 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "gi_probe_editor_plugin.h"
+#include "voxel_gi_editor_plugin.h"
-void GIProbeEditorPlugin::_bake() {
- if (gi_probe) {
- if (gi_probe->get_probe_data().is_null()) {
+void VoxelGIEditorPlugin::_bake() {
+ if (voxel_gi) {
+ if (voxel_gi->get_probe_data().is_null()) {
String path = get_tree()->get_edited_scene_root()->get_filename();
if (path == String()) {
- path = "res://" + gi_probe->get_name() + "_data.res";
+ path = "res://" + voxel_gi->get_name() + "_data.res";
} else {
String ext = path.get_extension();
- path = path.get_basename() + "." + gi_probe->get_name() + "_data.res";
+ path = path.get_basename() + "." + voxel_gi->get_name() + "_data.res";
}
probe_file->set_current_path(path);
probe_file->popup_file_dialog();
return;
}
- gi_probe->bake();
+ voxel_gi->bake();
}
}
-void GIProbeEditorPlugin::edit(Object *p_object) {
- GIProbe *s = Object::cast_to<GIProbe>(p_object);
+void VoxelGIEditorPlugin::edit(Object *p_object) {
+ VoxelGI *s = Object::cast_to<VoxelGI>(p_object);
if (!s) {
return;
}
- gi_probe = s;
+ voxel_gi = s;
}
-bool GIProbeEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("GIProbe");
+bool VoxelGIEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("VoxelGI");
}
-void GIProbeEditorPlugin::_notification(int p_what) {
+void VoxelGIEditorPlugin::_notification(int p_what) {
if (p_what == NOTIFICATION_PROCESS) {
- if (!gi_probe) {
+ if (!voxel_gi) {
return;
}
- const Vector3i size = gi_probe->get_estimated_cell_size();
+ const Vector3i size = voxel_gi->get_estimated_cell_size();
String text = vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z);
- int data_size = 4;
- if (GLOBAL_GET("rendering/quality/gi_probes/anisotropic")) {
- data_size += 4;
- }
+ const int data_size = 4;
const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0);
text += " - " + vformat(TTR("VRAM Size: %s MB"), String::num(size_mb, 2));
@@ -98,7 +95,7 @@ void GIProbeEditorPlugin::_notification(int p_what) {
}
}
-void GIProbeEditorPlugin::make_visible(bool p_visible) {
+void VoxelGIEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
bake_hb->show();
set_process(true);
@@ -108,38 +105,38 @@ void GIProbeEditorPlugin::make_visible(bool p_visible) {
}
}
-EditorProgress *GIProbeEditorPlugin::tmp_progress = nullptr;
+EditorProgress *VoxelGIEditorPlugin::tmp_progress = nullptr;
-void GIProbeEditorPlugin::bake_func_begin(int p_steps) {
+void VoxelGIEditorPlugin::bake_func_begin(int p_steps) {
ERR_FAIL_COND(tmp_progress != nullptr);
tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake GI Probe"), p_steps));
}
-void GIProbeEditorPlugin::bake_func_step(int p_step, const String &p_description) {
+void VoxelGIEditorPlugin::bake_func_step(int p_step, const String &p_description) {
ERR_FAIL_COND(tmp_progress == nullptr);
tmp_progress->step(p_description, p_step, false);
}
-void GIProbeEditorPlugin::bake_func_end() {
+void VoxelGIEditorPlugin::bake_func_end() {
ERR_FAIL_COND(tmp_progress == nullptr);
memdelete(tmp_progress);
tmp_progress = nullptr;
}
-void GIProbeEditorPlugin::_giprobe_save_path_and_bake(const String &p_path) {
+void VoxelGIEditorPlugin::_voxel_gi_save_path_and_bake(const String &p_path) {
probe_file->hide();
- if (gi_probe) {
- gi_probe->bake();
- ERR_FAIL_COND(gi_probe->get_probe_data().is_null());
- ResourceSaver::save(p_path, gi_probe->get_probe_data(), ResourceSaver::FLAG_CHANGE_PATH);
+ if (voxel_gi) {
+ voxel_gi->bake();
+ ERR_FAIL_COND(voxel_gi->get_probe_data().is_null());
+ ResourceSaver::save(p_path, voxel_gi->get_probe_data(), ResourceSaver::FLAG_CHANGE_PATH);
}
}
-void GIProbeEditorPlugin::_bind_methods() {
+void VoxelGIEditorPlugin::_bind_methods() {
}
-GIProbeEditorPlugin::GIProbeEditorPlugin(EditorNode *p_node) {
+VoxelGIEditorPlugin::VoxelGIEditorPlugin(EditorNode *p_node) {
editor = p_node;
bake_hb = memnew(HBoxContainer);
bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -148,7 +145,7 @@ GIProbeEditorPlugin::GIProbeEditorPlugin(EditorNode *p_node) {
bake->set_flat(true);
bake->set_icon(editor->get_gui_base()->get_theme_icon("Bake", "EditorIcons"));
bake->set_text(TTR("Bake GI Probe"));
- bake->connect("pressed", callable_mp(this, &GIProbeEditorPlugin::_bake));
+ bake->connect("pressed", callable_mp(this, &VoxelGIEditorPlugin::_bake));
bake_hb->add_child(bake);
bake_info = memnew(Label);
bake_info->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -156,18 +153,18 @@ GIProbeEditorPlugin::GIProbeEditorPlugin(EditorNode *p_node) {
bake_hb->add_child(bake_info);
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake_hb);
- gi_probe = nullptr;
+ voxel_gi = nullptr;
probe_file = memnew(EditorFileDialog);
probe_file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
probe_file->add_filter("*.res");
- probe_file->connect("file_selected", callable_mp(this, &GIProbeEditorPlugin::_giprobe_save_path_and_bake));
+ probe_file->connect("file_selected", callable_mp(this, &VoxelGIEditorPlugin::_voxel_gi_save_path_and_bake));
get_editor_interface()->get_base_control()->add_child(probe_file);
- probe_file->set_title(TTR("Select path for GIProbe Data File"));
+ probe_file->set_title(TTR("Select path for VoxelGI Data File"));
- GIProbe::bake_begin_function = bake_func_begin;
- GIProbe::bake_step_function = bake_func_step;
- GIProbe::bake_end_function = bake_func_end;
+ VoxelGI::bake_begin_function = bake_func_begin;
+ VoxelGI::bake_step_function = bake_func_step;
+ VoxelGI::bake_end_function = bake_func_end;
}
-GIProbeEditorPlugin::~GIProbeEditorPlugin() {
+VoxelGIEditorPlugin::~VoxelGIEditorPlugin() {
}
diff --git a/editor/plugins/gi_probe_editor_plugin.h b/editor/plugins/voxel_gi_editor_plugin.h
index fdf0623561..4d3cfe90f6 100644
--- a/editor/plugins/gi_probe_editor_plugin.h
+++ b/editor/plugins/voxel_gi_editor_plugin.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* gi_probe_editor_plugin.h */
+/* voxel_gi_editor_plugin.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,18 +28,18 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef GIPROBEEDITORPLUGIN_H
-#define GIPROBEEDITORPLUGIN_H
+#ifndef VOXEL_GIEDITORPLUGIN_H
+#define VOXEL_GIEDITORPLUGIN_H
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
-#include "scene/3d/gi_probe.h"
+#include "scene/3d/voxel_gi.h"
#include "scene/resources/material.h"
-class GIProbeEditorPlugin : public EditorPlugin {
- GDCLASS(GIProbeEditorPlugin, EditorPlugin);
+class VoxelGIEditorPlugin : public EditorPlugin {
+ GDCLASS(VoxelGIEditorPlugin, EditorPlugin);
- GIProbe *gi_probe;
+ VoxelGI *voxel_gi;
HBoxContainer *bake_hb;
Label *bake_info;
@@ -54,21 +54,21 @@ class GIProbeEditorPlugin : public EditorPlugin {
static void bake_func_end();
void _bake();
- void _giprobe_save_path_and_bake(const String &p_path);
+ void _voxel_gi_save_path_and_bake(const String &p_path);
protected:
static void _bind_methods();
void _notification(int p_what);
public:
- virtual String get_name() const override { return "GIProbe"; }
+ virtual String get_name() const override { return "VoxelGI"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
- GIProbeEditorPlugin(EditorNode *p_node);
- ~GIProbeEditorPlugin();
+ VoxelGIEditorPlugin(EditorNode *p_node);
+ ~VoxelGIEditorPlugin();
};
-#endif // GIPROBEEDITORPLUGIN_H
+#endif // VOXEL_GIEDITORPLUGIN_H