diff options
Diffstat (limited to 'editor/plugins')
110 files changed, 21858 insertions, 10598 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 876b67fa77..df01ecd1be 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -150,9 +150,9 @@ void AbstractPolygon2DEditor::_notification(int p_what) { case NOTIFICATION_READY: { disable_polygon_editing(false, String()); - 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_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); button_edit->set_pressed(true); get_tree()->connect("node_removed", callable_mp(this, &AbstractPolygon2DEditor::_node_removed)); @@ -264,9 +264,9 @@ 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(mb->get_position()))); if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) { - if (mb->get_button_index() == BUTTON_LEFT) { + 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; } @@ -326,7 +326,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) return true; } } - } else if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed() && !edited_point.valid()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && !edited_point.valid()) { const PosVertex closest = closest_point(gpoint); if (closest.valid()) { @@ -335,7 +335,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) } } } else if (mode == MODE_DELETE) { - if (mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { const PosVertex closest = closest_point(gpoint); if (closest.valid()) { @@ -346,7 +346,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) } if (mode == MODE_CREATE) { - if (mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { if (_is_line()) { // for lines, we don't have a wip mode, and we can undo each single add point. Vector<Vector2> vertices = _get_polygon(0); @@ -384,7 +384,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) return true; } } - } else if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed() && wip_active) { + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) { _wip_cancel(); } } @@ -395,11 +395,11 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) if (mm.is_valid()) { Vector2 gpoint = mm->get_position(); - if (edited_point.valid() && (wip_active || (mm->get_button_mask() & BUTTON_MASK_LEFT))) { + if (edited_point.valid() && (wip_active || (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT))) { 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; @@ -477,7 +477,7 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform(); // All polygon points are sharp, so use the sharp handle icon - const Ref<Texture2D> handle = get_theme_icon("EditorPathSharpHandle", "EditorIcons"); + const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); const Vertex active_point = get_active_point(); const int n_polygons = _get_polygon_count(); @@ -550,8 +550,8 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, modulate); if (vertex == hover_point) { - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); String num = String::num(vertex.vertex); Size2 num_size = font->get_string_size(num, font_size); p_overlay->draw_string(font, point - num_size * 0.5, num, HALIGN_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); @@ -560,7 +560,7 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl } if (edge_point.valid()) { - Ref<Texture2D> add_handle = get_theme_icon("EditorHandleAdd", "EditorIcons"); + Ref<Texture2D> add_handle = get_theme_icon(SNAME("EditorHandleAdd"), SNAME("EditorIcons")); p_overlay->draw_texture(add_handle, edge_point.pos - add_handle->get_size() * 0.5); } } @@ -585,11 +585,11 @@ void AbstractPolygon2DEditor::edit(Node *p_polygon) { edited_point = PosVertex(); hover_point = Vertex(); selected_point = Vertex(); - - canvas_item_editor->update_viewport(); } else { _set_node(nullptr); } + + canvas_item_editor->update_viewport(); } void AbstractPolygon2DEditor::_bind_methods() { diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index d69913cc46..9e58332e41 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -51,7 +51,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (mb->get_button_index() == BUTTON_LEFT && tool_create->is_pressed()))) { + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && tool_create->is_pressed()))) { menu->clear(); animations_menu->clear(); animations_to_add.clear(); @@ -73,7 +73,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven ap->get_animation_list(&names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - animations_menu->add_icon_item(get_theme_icon("Animation", "EditorIcons"), E->get()); + animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E->get()); animations_to_add.push_back(E->get()); } } @@ -110,7 +110,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { blend_space_draw->update(); // why not // try to see if a point can be selected @@ -132,7 +132,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (dragging_selected) { // move float point = blend_space->get_blend_point_position(selected_point); @@ -161,7 +161,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } // *set* the blend - if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { float blend_pos = mb->get_position().x / blend_space_draw->get_size().x; blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); blend_pos += blend_space->get_min_space(); @@ -184,7 +184,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven _update_edited_point_pos(); } - if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { float blend_pos = mm->get_position().x / blend_space_draw->get_size().x; blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); blend_pos += blend_space->get_min_space(); @@ -196,19 +196,19 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { - Color linecolor = get_theme_color("font_color", "Label"); + Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); Color linecolor_soft = linecolor; linecolor_soft.a *= 0.5; - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); - Ref<Texture2D> icon = get_theme_icon("KeyValue", "EditorIcons"); - Ref<Texture2D> icon_selected = get_theme_icon("KeySelected", "EditorIcons"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Ref<Texture2D> icon = get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")); + Ref<Texture2D> icon_selected = get_theme_icon(SNAME("KeySelected"), SNAME("EditorIcons")); Size2 s = blend_space_draw->get_size(); if (blend_space_draw->has_focus()) { - Color color = get_theme_color("accent_color", "Editor"); + Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); blend_space_draw->draw_rect(Rect2(Point2(), s), color, false); } @@ -279,7 +279,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { { Color color; if (tool_blend->is_pressed()) { - color = get_theme_color("accent_color", "Editor"); + color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); } else { color = linecolor; color.a *= 0.5; @@ -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]); @@ -529,15 +529,15 @@ void AnimationNodeBlendSpace1DEditor::_open_editor() { void AnimationNodeBlendSpace1DEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - 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")); - panel->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree")); - tool_blend->set_icon(get_theme_icon("EditPivot", "EditorIcons")); - tool_select->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); - tool_create->set_icon(get_theme_icon("EditKey", "EditorIcons")); - tool_erase->set_icon(get_theme_icon("Remove", "EditorIcons")); - snap->set_icon(get_theme_icon("SnapGrid", "EditorIcons")); - open_editor->set_icon(get_theme_icon("Edit", "EditorIcons")); + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); } if (p_what == NOTIFICATION_PROCESS) { @@ -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); @@ -698,7 +698,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { max_value->set_step(0.01); label_value = memnew(LineEdit); - label_value->set_expand_to_text_length(true); + label_value->set_expand_to_text_length_enabled(true); // now add diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 6a57463dbc..bbf88bbd8f 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -79,7 +79,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (mb->get_button_index() == BUTTON_LEFT && tool_create->is_pressed()))) { + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && tool_create->is_pressed()))) { menu->clear(); animations_menu->clear(); animations_to_add.clear(); @@ -97,7 +97,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven List<StringName> names; ap->get_animation_list(&names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - animations_menu->add_icon_item(get_theme_icon("Animation", "EditorIcons"), E->get()); + animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E->get()); animations_to_add.push_back(E->get()); } } @@ -134,7 +134,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { blend_space_draw->update(); //update anyway //try to see if a point can be selected selected_point = -1; @@ -174,7 +174,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { blend_space_draw->update(); //update anyway //try to see if a point can be selected selected_point = -1; @@ -209,7 +209,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (dragging_selected) { //move Vector2 point = blend_space->get_blend_point_position(selected_point); @@ -236,7 +236,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven blend_space_draw->update(); } - if (mb.is_valid() && mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { Vector2 blend_pos = (mb->get_position() / blend_space_draw->get_size()); blend_pos.y = 1.0 - blend_pos.y; blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); @@ -270,7 +270,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven blend_space_draw->update(); } - if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { Vector2 blend_pos = (mm->get_position() / blend_space_draw->get_size()); blend_pos.y = 1.0 - blend_pos.y; blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); @@ -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]); @@ -392,18 +392,18 @@ void AnimationNodeBlendSpace2DEditor::_tool_switch(int p_tool) { } void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { - Color linecolor = get_theme_color("font_color", "Label"); + Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); Color linecolor_soft = linecolor; linecolor_soft.a *= 0.5; - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); - Ref<Texture2D> icon = get_theme_icon("KeyValue", "EditorIcons"); - Ref<Texture2D> icon_selected = get_theme_icon("KeySelected", "EditorIcons"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); + Ref<Texture2D> icon = get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")); + Ref<Texture2D> icon_selected = get_theme_icon(SNAME("KeySelected"), SNAME("EditorIcons")); Size2 s = blend_space_draw->get_size(); if (blend_space_draw->has_focus()) { - Color color = get_theme_color("accent_color", "Editor"); + Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); blend_space_draw->draw_rect(Rect2(Point2(), s), color, false); } blend_space_draw->draw_line(Point2(1, 0), Point2(1, s.height - 1), linecolor); @@ -483,7 +483,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { Color color; if (i == selected_triangle) { - color = get_theme_color("accent_color", "Editor"); + color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); color.a *= 0.5; } else { color = linecolor; @@ -543,7 +543,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { { Color color; if (tool_blend->is_pressed()) { - color = get_theme_color("accent_color", "Editor"); + color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); } else { color = linecolor; color.a *= 0.5; @@ -733,21 +733,21 @@ void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) { void AnimationNodeBlendSpace2DEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - 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")); - panel->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree")); - tool_blend->set_icon(get_theme_icon("EditPivot", "EditorIcons")); - tool_select->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); - tool_create->set_icon(get_theme_icon("EditKey", "EditorIcons")); - tool_triangle->set_icon(get_theme_icon("ToolTriangle", "EditorIcons")); - tool_erase->set_icon(get_theme_icon("Remove", "EditorIcons")); - snap->set_icon(get_theme_icon("SnapGrid", "EditorIcons")); - open_editor->set_icon(get_theme_icon("Edit", "EditorIcons")); - auto_triangles->set_icon(get_theme_icon("AutoTriangle", "EditorIcons")); + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); + tool_triangle->set_icon(get_theme_icon(SNAME("ToolTriangle"), SNAME("EditorIcons"))); + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + auto_triangles->set_icon(get_theme_icon(SNAME("AutoTriangle"), SNAME("EditorIcons"))); interpolation->clear(); - interpolation->add_icon_item(get_theme_icon("TrackContinuous", "EditorIcons"), "", 0); - interpolation->add_icon_item(get_theme_icon("TrackDiscrete", "EditorIcons"), "", 1); - interpolation->add_icon_item(get_theme_icon("TrackCapture", "EditorIcons"), "", 2); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2); } if (p_what == NOTIFICATION_PROCESS) { @@ -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); @@ -942,7 +942,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { left_vbox->add_spacer(); label_y = memnew(LineEdit); left_vbox->add_child(label_y); - label_y->set_expand_to_text_length(true); + label_y->set_expand_to_text_length_enabled(true); left_vbox->add_spacer(); min_y_value = memnew(SpinBox); left_vbox->add_child(min_y_value); @@ -978,7 +978,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { bottom_vbox->add_spacer(); label_x = memnew(LineEdit); bottom_vbox->add_child(label_x); - label_x->set_expand_to_text_length(true); + label_x->set_expand_to_text_length_enabled(true); bottom_vbox->add_spacer(); max_x_value = memnew(SpinBox); bottom_vbox->add_child(max_x_value); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index e7e069e8b6..7930bc1e1c 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -136,10 +136,10 @@ void AnimationNodeBlendTreeEditor::_update_graph() { if (String(E->get()) != "output") { LineEdit *name = memnew(LineEdit); name->set_text(E->get()); - name->set_expand_to_text_length(true); + 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)); + node->set_slot(0, false, 0, Color(), true, 0, get_theme_color(SNAME("font_color"), SNAME("Label"))); + 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); @@ -150,7 +150,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { Label *in_name = memnew(Label); node->add_child(in_name); in_name->set_text(agnode->get_input_name(i)); - node->set_slot(base + i, true, 0, get_theme_color("font_color", "Label"), false, 0, Color()); + node->set_slot(base + i, true, 0, get_theme_color(SNAME("font_color"), SNAME("Label")), false, 0, Color()); } List<PropertyInfo> pinfo; @@ -177,7 +177,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { node->add_child(memnew(HSeparator)); Button *open_in_editor = memnew(Button); open_in_editor->set_text(TTR("Open Editor")); - open_in_editor->set_icon(get_theme_icon("Edit", "EditorIcons")); + open_in_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); node->add_child(open_in_editor); open_in_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_open_in_editor), varray(E->get()), CONNECT_DEFERRED); open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER); @@ -187,7 +187,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { node->add_child(memnew(HSeparator)); Button *edit_filters = memnew(Button); edit_filters->set_text(TTR("Edit Filters")); - edit_filters->set_icon(get_theme_icon("AnimationFilter", "EditorIcons")); + edit_filters->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); node->add_child(edit_filters); edit_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_edit_filters), varray(E->get()), CONNECT_DEFERRED); edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER); @@ -197,7 +197,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { if (anim.is_valid()) { MenuButton *mb = memnew(MenuButton); mb->set_text(anim->get_animation()); - mb->set_icon(get_theme_icon("Animation", "EditorIcons")); + mb->set_icon(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons"))); Array options; node->add_child(memnew(HSeparator)); @@ -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(SNAME("frame"), SNAME("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); @@ -619,7 +617,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano ti->set_text(0, F->get()); ti->set_selectable(0, false); ti->set_editable(0, false); - ti->set_icon(0, get_theme_icon("BoneAttachment3D", "EditorIcons")); + ti->set_icon(0, get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); } else { ti = parenthood[accum]; } @@ -630,7 +628,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); ti->set_text(0, concat); ti->set_checked(0, anode->is_path_filtered(path)); - ti->set_icon(0, get_theme_icon("BoneAttachment3D", "EditorIcons")); + ti->set_icon(0, get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); ti->set_metadata(0, path); } else { @@ -692,8 +690,8 @@ void AnimationNodeBlendTreeEditor::_removed_from_graph() { void AnimationNodeBlendTreeEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - 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")); + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { _update_graph(); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 7c623505b5..cbce5bb3f5 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -99,46 +99,46 @@ void AnimationPlayerEditor::_notification(int p_what) { get_tree()->connect("node_removed", callable_mp(this, &AnimationPlayerEditor::_node_removed)); - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox("panel", "Panel")); + add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox("panel", "Panel")); + add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); } break; case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - autoplay->set_icon(get_theme_icon("AutoPlay", "EditorIcons")); + autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); - play->set_icon(get_theme_icon("PlayStart", "EditorIcons")); - play_from->set_icon(get_theme_icon("Play", "EditorIcons")); - play_bw->set_icon(get_theme_icon("PlayStartBackwards", "EditorIcons")); - play_bw_from->set_icon(get_theme_icon("PlayBackwards", "EditorIcons")); + play->set_icon(get_theme_icon(SNAME("PlayStart"), SNAME("EditorIcons"))); + play_from->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + play_bw->set_icon(get_theme_icon(SNAME("PlayStartBackwards"), SNAME("EditorIcons"))); + play_bw_from->set_icon(get_theme_icon(SNAME("PlayBackwards"), SNAME("EditorIcons"))); - autoplay_icon = get_theme_icon("AutoPlay", "EditorIcons"); - reset_icon = get_theme_icon("Reload", "EditorIcons"); + autoplay_icon = get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons")); + reset_icon = get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")); { - Ref<Image> autoplay_img = autoplay_icon->get_data(); - Ref<Image> reset_img = reset_icon->get_data(); + Ref<Image> autoplay_img = autoplay_icon->get_image(); + 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")); + stop->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); - onion_toggle->set_icon(get_theme_icon("Onion", "EditorIcons")); - onion_skinning->set_icon(get_theme_icon("GuiTabMenuHl", "EditorIcons")); + onion_toggle->set_icon(get_theme_icon(SNAME("Onion"), SNAME("EditorIcons"))); + onion_skinning->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - pin->set_icon(get_theme_icon("Pin", "EditorIcons")); + pin->set_icon(get_theme_icon(SNAME("Pin"), SNAME("EditorIcons"))); - tool_anim->add_theme_style_override("normal", get_theme_stylebox("normal", "Button")); - track_editor->get_edit_menu()->add_theme_style_override("normal", get_theme_stylebox("normal", "Button")); + tool_anim->add_theme_style_override("normal", get_theme_stylebox(SNAME("normal"), SNAME("Button"))); + track_editor->get_edit_menu()->add_theme_style_override("normal", get_theme_stylebox(SNAME("normal"), SNAME("Button"))); -#define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_theme_icon(m_icon, "EditorIcons")) +#define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_theme_icon(SNAME(m_icon), SNAME("EditorIcons"))) ITEM_ICON(TOOL_NEW_ANIM, "New"); ITEM_ICON(TOOL_LOAD_ANIM, "Load"); @@ -373,7 +373,7 @@ void AnimationPlayerEditor::_animation_save_in_path(const Ref<Resource> &p_resou } ((Resource *)p_resource.ptr())->set_path(path); - editor->emit_signal("resource_saved", p_resource); + editor->emit_signal(SNAME("resource_saved"), p_resource); } void AnimationPlayerEditor::_animation_save(const Ref<Resource> &p_resource) { @@ -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) { @@ -1219,11 +1221,13 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) { } 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(); @@ -1235,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(); @@ -1320,11 +1324,11 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() { } // And go to next step afterwards. - call_deferred("_prepare_onion_layers_2"); + call_deferred(SNAME("_prepare_onion_layers_2")); } void AnimationPlayerEditor::_prepare_onion_layers_1_deferred() { - call_deferred("_prepare_onion_layers_1"); + call_deferred(SNAME("_prepare_onion_layers_1")); } void AnimationPlayerEditor::_prepare_onion_layers_2() { @@ -1691,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; @@ -1729,27 +1733,27 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay onion.capture.material = Ref<ShaderMaterial>(memnew(ShaderMaterial)); onion.capture.shader = Ref<Shader>(memnew(Shader)); - onion.capture.shader->set_code(" \ - shader_type canvas_item; \ - \ - uniform vec4 bkg_color; \ - uniform vec4 dir_color; \ - uniform bool differences_only; \ - uniform sampler2D present; \ - \ - float zero_if_equal(vec4 a, vec4 b) { \ - return smoothstep(0.0, 0.005, length(a.rgb - b.rgb) / sqrt(3.0)); \ - } \ - \ - void fragment() { \ - vec4 capture_samp = texture(TEXTURE, UV); \ - vec4 present_samp = texture(present, UV); \ - float bkg_mask = zero_if_equal(capture_samp, bkg_color); \ - float diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color); \ - diff_mask = min(1.0, diff_mask + float(!differences_only)); \ - COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask); \ - } \ - "); + onion.capture.shader->set_code(R"( +shader_type canvas_item; + +uniform vec4 bkg_color; +uniform vec4 dir_color; +uniform bool differences_only; +uniform sampler2D present; + +float zero_if_equal(vec4 a, vec4 b) { + return smoothstep(0.0, 0.005, length(a.rgb - b.rgb) / sqrt(3.0)); +} + +void fragment() { + vec4 capture_samp = texture(TEXTURE, UV); + vec4 present_samp = texture(present, UV); + float bkg_mask = zero_if_equal(capture_samp, bkg_color); + float diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color); + diff_mask = min(1.0, diff_mask + float(!differences_only)); + COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask); +} +)"); RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid()); } 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 c6d2faf849..8abe20c3c9 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -76,7 +76,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv Ref<InputEventMouseButton> mb = p_event; //Add new node - if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == BUTTON_LEFT))) { + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT))) { menu->clear(); animations_menu->clear(); animations_to_add.clear(); @@ -94,7 +94,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv List<StringName> names; ap->get_animation_list(&names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - animations_menu->add_icon_item(get_theme_icon("Animation", "EditorIcons"), E->get()); + animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E->get()); animations_to_add.push_back(E->get()); } } @@ -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() == 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(); @@ -145,7 +145,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv if (node_rects[i].name.has_point(mb->get_position())) { //edit name - Ref<StyleBox> line_sb = get_theme_stylebox("normal", "LineEdit"); + Ref<StyleBox> line_sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); Rect2 edit_rect = node_rects[i].name; edit_rect.position -= line_sb->get_offset(); @@ -163,7 +163,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } if (node_rects[i].edit.has_point(mb->get_position())) { //edit name - call_deferred("_open_editor", node_rects[i].node_name); + call_deferred(SNAME("_open_editor"), node_rects[i].node_name); return; } @@ -216,7 +216,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } //end moving node - if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) { + if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { if (dragging_selected) { Ref<AnimationNode> an = state_machine->get_node(selected_node); updating = true; @@ -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() == 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; @@ -250,14 +250,14 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } //end connecting nodes - if (mb.is_valid() && connecting && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) { + if (mb.is_valid() && connecting && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { if (connecting_to_node != StringName()) { if (state_machine->has_transition(connecting_from, connecting_to_node)) { EditorNode::get_singleton()->show_warning(TTR("Transition exists!")); } else { Ref<AnimationNodeStateMachineTransition> tr; - tr.instance(); + tr.instantiate(); tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected())); updating = true; @@ -284,7 +284,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv Ref<InputEventMouseMotion> mm = p_event; //pan window - if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) { + if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) { h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x); v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y); } @@ -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]); @@ -487,9 +487,9 @@ void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) { } void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance) { - Color linecolor = get_theme_color("font_color", "Label"); + Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); Color icon_color(1, 1, 1); - Color accent = get_theme_color("accent_color", "Editor"); + Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); if (!p_enabled) { linecolor.a *= 0.2; @@ -498,12 +498,12 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co } Ref<Texture2D> icons[6] = { - get_theme_icon("TransitionImmediateBig", "EditorIcons"), - get_theme_icon("TransitionSyncBig", "EditorIcons"), - get_theme_icon("TransitionEndBig", "EditorIcons"), - get_theme_icon("TransitionImmediateAutoBig", "EditorIcons"), - get_theme_icon("TransitionSyncAutoBig", "EditorIcons"), - get_theme_icon("TransitionEndAutoBig", "EditorIcons") + get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TransitionSyncBig"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TransitionEndBig"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TransitionImmediateAutoBig"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TransitionSyncAutoBig"), SNAME("EditorIcons")), + get_theme_icon(SNAME("TransitionEndAutoBig"), SNAME("EditorIcons")) }; if (p_selected) { @@ -555,19 +555,19 @@ void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Ve void AnimationNodeStateMachineEditor::_state_machine_draw() { Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); - Ref<StyleBox> style = get_theme_stylebox("state_machine_frame", "GraphNode"); - Ref<StyleBox> style_selected = get_theme_stylebox("state_machine_selectedframe", "GraphNode"); - - Ref<Font> font = get_theme_font("title_font", "GraphNode"); - int font_size = get_theme_font_size("title_font_size", "GraphNode"); - Color font_color = get_theme_color("title_color", "GraphNode"); - Ref<Texture2D> play = get_theme_icon("Play", "EditorIcons"); - Ref<Texture2D> auto_play = get_theme_icon("AutoPlay", "EditorIcons"); - Ref<Texture2D> edit = get_theme_icon("Edit", "EditorIcons"); - Color accent = get_theme_color("accent_color", "Editor"); - Color linecolor = get_theme_color("font_color", "Label"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("state_machine_frame"), SNAME("GraphNode")); + Ref<StyleBox> style_selected = get_theme_stylebox(SNAME("state_machine_selectedframe"), SNAME("GraphNode")); + + Ref<Font> font = get_theme_font(SNAME("title_font"), SNAME("GraphNode")); + int font_size = get_theme_font_size(SNAME("title_font_size"), SNAME("GraphNode")); + Color font_color = get_theme_color(SNAME("title_color"), SNAME("GraphNode")); + Ref<Texture2D> play = get_theme_icon(SNAME("Play"), SNAME("EditorIcons")); + Ref<Texture2D> auto_play = get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons")); + Ref<Texture2D> edit = get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")); + Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); linecolor.a *= 0.3; - Ref<StyleBox> playing_overlay = get_theme_stylebox("position", "GraphNode"); + Ref<StyleBox> playing_overlay = get_theme_stylebox(SNAME("position"), SNAME("GraphNode")); bool playing = false; StringName current; @@ -667,7 +667,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()), true, false, false, false); } - Ref<Texture2D> tr_reference_icon = get_theme_icon("TransitionImmediateBig", "EditorIcons"); + Ref<Texture2D> tr_reference_icon = get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons")); float tr_bidi_offset = int(tr_reference_icon->get_height() * 0.8); //draw transition lines @@ -857,7 +857,7 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw() { float pos = CLAMP(play_pos, 0, len); float c = pos / len; - Color fg = get_theme_color("font_color", "Label"); + Color fg = get_theme_color(SNAME("font_color"), SNAME("Label")); Color bg = fg; bg.a *= 0.3; @@ -882,26 +882,26 @@ void AnimationNodeStateMachineEditor::_update_graph() { void AnimationNodeStateMachineEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { - 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")); - panel->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree")); + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - tool_select->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); - tool_create->set_icon(get_theme_icon("ToolAddNode", "EditorIcons")); - tool_connect->set_icon(get_theme_icon("ToolConnect", "EditorIcons")); + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("ToolAddNode"), SNAME("EditorIcons"))); + tool_connect->set_icon(get_theme_icon(SNAME("ToolConnect"), SNAME("EditorIcons"))); transition_mode->clear(); - transition_mode->add_icon_item(get_theme_icon("TransitionImmediate", "EditorIcons"), TTR("Immediate")); - transition_mode->add_icon_item(get_theme_icon("TransitionSync", "EditorIcons"), TTR("Sync")); - transition_mode->add_icon_item(get_theme_icon("TransitionEnd", "EditorIcons"), TTR("At End")); + transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionImmediate"), SNAME("EditorIcons")), TTR("Immediate")); + transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionSync"), SNAME("EditorIcons")), TTR("Sync")); + transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionEnd"), SNAME("EditorIcons")), TTR("At End")); - tool_erase->set_icon(get_theme_icon("Remove", "EditorIcons")); - tool_autoplay->set_icon(get_theme_icon("AutoPlay", "EditorIcons")); - tool_end->set_icon(get_theme_icon("AutoEnd", "EditorIcons")); + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + tool_autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); + tool_end->set_icon(get_theme_icon(SNAME("AutoEnd"), SNAME("EditorIcons"))); play_mode->clear(); - play_mode->add_icon_item(get_theme_icon("PlayTravel", "EditorIcons"), TTR("Travel")); - play_mode->add_icon_item(get_theme_icon("Play", "EditorIcons"), TTR("Immediate")); + play_mode->add_icon_item(get_theme_icon(SNAME("PlayTravel"), SNAME("EditorIcons")), TTR("Travel")); + play_mode->add_icon_item(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")), TTR("Immediate")); } if (p_what == NOTIFICATION_PROCESS) { @@ -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..9c28e023dd 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -76,10 +76,10 @@ void AnimationTreeEditor::_update_path() { } Ref<ButtonGroup> group; - group.instance(); + group.instantiate(); Button *b = memnew(Button); - b->set_text("Root"); + b->set_text(TTR("Root")); b->set_toggle_mode(true); b->set_button_group(group); b->set_pressed(true); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 4d9c5625a3..bbc8107cf6 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -58,7 +58,7 @@ void EditorAssetLibraryItem::set_image(int p_type, int p_index, const Ref<Textur void EditorAssetLibraryItem::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - icon->set_normal_texture(get_theme_icon("ProjectIconLoading", "EditorIcons")); + icon->set_normal_texture(get_theme_icon(SNAME("ProjectIconLoading"), SNAME("EditorIcons"))); category->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); author->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); price->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); @@ -66,15 +66,15 @@ void EditorAssetLibraryItem::_notification(int p_what) { } void EditorAssetLibraryItem::_asset_clicked() { - emit_signal("asset_selected", asset_id); + emit_signal(SNAME("asset_selected"), asset_id); } void EditorAssetLibraryItem::_category_clicked() { - emit_signal("category_selected", category_id); + emit_signal(SNAME("category_selected"), category_id); } void EditorAssetLibraryItem::_author_clicked() { - emit_signal("author_selected", author_id); + emit_signal(SNAME("author_selected"), author_id); } void EditorAssetLibraryItem::_bind_methods() { @@ -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); @@ -144,8 +144,8 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const for (int i = 0; i < preview_images.size(); i++) { if (preview_images[i].id == p_index) { if (preview_images[i].is_video) { - Ref<Image> overlay = previews->get_theme_icon("PlayOverlay", "EditorIcons")->get_data(); - Ref<Image> thumbnail = p_image->get_data(); + Ref<Image> overlay = previews->get_theme_icon(SNAME("PlayOverlay"), SNAME("EditorIcons"))->get_image(); + Ref<Image> thumbnail = p_image->get_image(); thumbnail = thumbnail->duplicate(); Point2 overlay_pos = Point2((thumbnail->get_width() - overlay->get_width()) / 2, (thumbnail->get_height() - overlay->get_height()) / 2); @@ -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); @@ -185,7 +185,7 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const void EditorAssetLibraryItemDescription::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - previews_bg->add_theme_style_override("panel", previews->get_theme_stylebox("normal", "TextEdit")); + previews_bg->add_theme_style_override("panel", previews->get_theme_stylebox(SNAME("normal"), SNAME("TextEdit"))); } break; } } @@ -240,13 +240,12 @@ 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_icon(previews->get_theme_icon(SNAME("ThumbnailWait"), SNAME("EditorIcons"))); preview.button->set_toggle_mode(true); preview.button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click), varray(p_id)); preview_hb->add_child(preview.button); if (!p_video) { - preview.image = previews->get_theme_icon("ThumbnailWait", "EditorIcons"); + preview.image = previews->get_theme_icon(SNAME("ThumbnailWait"), SNAME("EditorIcons")); } preview_images.push_back(preview); if (preview_images.size() == 1 && !p_video) { @@ -370,6 +369,9 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int progress->set_modulate(Color(0, 0, 0, 0)); set_process(false); + + // Automatically prompt for installation once the download is completed. + _install(); } void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asset_id, const Ref<Texture2D> &p_preview, const String &p_download_url, const String &p_sha256_hash) { @@ -377,7 +379,7 @@ void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asse icon->set_texture(p_preview); asset_id = p_asset_id; if (!p_preview.is_valid()) { - icon->set_texture(get_theme_icon("FileBrokenBigThumb", "EditorIcons")); + icon->set_texture(get_theme_icon(SNAME("FileBrokenBigThumb"), SNAME("EditorIcons"))); } host = p_download_url; sha256 = p_sha256_hash; @@ -388,8 +390,8 @@ void EditorAssetLibraryItemDownload::_notification(int p_what) { switch (p_what) { // FIXME: The editor crashes if 'NOTICATION_THEME_CHANGED' is used. case NOTIFICATION_ENTER_TREE: { - add_theme_style_override("panel", get_theme_stylebox("panel", "TabContainer")); - dismiss->set_normal_texture(get_theme_icon("Close", "EditorIcons")); + add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); + dismiss->set_normal_texture(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); } break; case NOTIFICATION_PROCESS: { // Make the progress bar visible again when retrying the download. @@ -453,10 +455,11 @@ void EditorAssetLibraryItemDownload::_install() { String file = download->get_download_file(); if (external_install) { - emit_signal("install_asset", file, title->get_text()); + emit_signal(SNAME("install_asset"), file, title->get_text()); return; } + asset_installer->set_asset_name(title->get_text()); asset_installer->open(file, 1); } @@ -465,7 +468,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) { @@ -550,15 +553,22 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { void EditorAssetLibrary::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { - error_tr->set_texture(get_theme_icon("Error", "EditorIcons")); - filter->set_right_icon(get_theme_icon("Search", "EditorIcons")); + error_tr->set_texture(get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); + filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); filter->set_clear_button_enabled(true); error_label->raise(); } break; case NOTIFICATION_VISIBILITY_CHANGED: { - if (is_visible() && initial_loading) { - _repository_changed(0); // Update when shown for the first time. + if (is_visible()) { + // Focus the search box automatically when switching to the Templates tab (in the Project Manager) + // or switching to the AssetLib tab (in the editor). + // The Project Manager's project filter box is automatically focused in the project manager code. + filter->grab_focus(); + + if (initial_loading) { + _repository_changed(0); // Update when shown for the first time. + } } } break; case NOTIFICATION_PROCESS: { @@ -578,16 +588,36 @@ void EditorAssetLibrary::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { - library_scroll_bg->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree")); - downloads_scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree")); - error_tr->set_texture(get_theme_icon("Error", "EditorIcons")); - filter->set_right_icon(get_theme_icon("Search", "EditorIcons")); + library_scroll_bg->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + downloads_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_tr->set_texture(get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); + filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); filter->set_clear_button_enabled(true); } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + _update_repository_options(); + } break; + } +} + +void EditorAssetLibrary::_update_repository_options() { + Dictionary default_urls; + default_urls["godotengine.org"] = "https://godotengine.org/asset-library/api"; + default_urls["localhost"] = "http://127.0.0.1/asset-library/api"; + Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls", default_urls, true); + repository->clear(); + Array keys = available_urls.keys(); + for (int i = 0; i < available_urls.size(); i++) { + String key = keys[i]; + repository->add_item(key); + repository->set_item_metadata(i, available_urls[key]); } } void EditorAssetLibrary::_unhandled_key_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + const Ref<InputEventKey> key = p_event; if (key.is_valid() && key->is_pressed()) { @@ -676,7 +706,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); @@ -735,7 +765,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); @@ -743,7 +773,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB } if (!image_set && final) { - obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_theme_icon("FileBrokenBigThumb", "EditorIcons")); + obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_theme_icon(SNAME("FileBrokenBigThumb"), SNAME("EditorIcons"))); } } } @@ -755,7 +785,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; @@ -786,7 +816,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons WARN_PRINT("Error getting image file from URL: " + image_queue[p_queue_id].image_url); Object *obj = ObjectDB::get_instance(image_queue[p_queue_id].target); if (obj) { - obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_theme_icon("FileBrokenBigThumb", "EditorIcons")); + obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_theme_icon(SNAME("FileBrokenBigThumb"), SNAME("EditorIcons"))); } } @@ -803,7 +833,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")) { @@ -969,14 +999,16 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int for (int i = from; i < to; i++) { if (i == p_page) { Button *current = memnew(Button); - current->set_text(itos(i + 1)); + // Keep the extended padding for the currently active page (see below). + current->set_text(vformat(" %d ", i + 1)); current->set_disabled(true); current->set_focus_mode(Control::FOCUS_NONE); hbc->add_child(current); } else { Button *current = memnew(Button); - current->set_text(itos(i + 1)); + // Add padding to make page number buttons easier to click. + current->set_text(vformat(" %d ", i + 1)); current->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(i)); hbc->add_child(current); @@ -1070,11 +1102,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; @@ -1270,6 +1300,7 @@ void EditorAssetLibrary::_asset_file_selected(const String &p_file) { } asset_installer = memnew(EditorAssetInstaller); + asset_installer->set_asset_name(p_file.get_basename()); add_child(asset_installer); asset_installer->open(p_file); } @@ -1284,7 +1315,7 @@ void EditorAssetLibrary::_manage_plugins() { } void EditorAssetLibrary::_install_external_asset(String p_zip_path, String p_title) { - emit_signal("install_asset", p_zip_path, p_title); + emit_signal(SNAME("install_asset"), p_zip_path, p_title); } void EditorAssetLibrary::disable_community_support() { @@ -1312,6 +1343,11 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_main->add_theme_constant_override("separation", 10 * EDSCALE); filter = memnew(LineEdit); + if (templates_only) { + filter->set_placeholder(TTR("Search templates, projects, and demos")); + } else { + filter->set_placeholder(TTR("Search assets (excluding templates, projects, and demos)")); + } search_hb->add_child(filter); filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); filter->connect("text_changed", callable_mp(this, &EditorAssetLibrary::_search_text_changed)); @@ -1371,18 +1407,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { search_hb2->add_child(memnew(Label(TTR("Site:") + " "))); repository = memnew(OptionButton); - { - Dictionary default_urls; - default_urls["godotengine.org"] = "https://godotengine.org/asset-library/api"; - default_urls["localhost"] = "http://127.0.0.1/asset-library/api"; - Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls", default_urls, true); - Array keys = available_urls.keys(); - for (int i = 0; i < available_urls.size(); i++) { - String key = keys[i]; - repository->add_item(key); - repository->set_item_metadata(i, available_urls[key]); - } - } + _update_repository_options(); repository->connect("item_selected", callable_mp(this, &EditorAssetLibrary::_repository_changed)); @@ -1394,6 +1419,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { support = memnew(MenuButton); search_hb2->add_child(support); support->set_text(TTR("Support")); + support->get_popup()->set_hide_on_checkable_item_selection(false); support->get_popup()->add_check_item(TTR("Official"), SUPPORT_OFFICIAL); support->get_popup()->add_check_item(TTR("Community"), SUPPORT_COMMUNITY); support->get_popup()->add_check_item(TTR("Testing"), SUPPORT_TESTING); @@ -1414,7 +1440,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); @@ -1464,7 +1490,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { error_hb = memnew(HBoxContainer); library_main->add_child(error_hb); error_label = memnew(Label); - error_label->add_theme_color_override("color", get_theme_color("error_color", "Editor")); + error_label->add_theme_color_override("color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_hb->add_child(error_label); error_tr = memnew(TextureRect); error_tr->set_v_size_flags(Control::SIZE_SHRINK_CENTER); diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index 0509145673..c6ca1ecd4f 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -176,6 +176,7 @@ class EditorAssetLibrary : public PanelContainer { void _asset_open(); void _asset_file_selected(const String &p_file); + void _update_repository_options(); PanelContainer *library_scroll_bg; ScrollContainer *library_scroll; @@ -281,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 5963092860..482c08f50a 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" @@ -42,10 +43,10 @@ void AudioStreamEditor::_notification(int p_what) { } if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - _play_button->set_icon(get_theme_icon("MainPlay", "EditorIcons")); - _stop_button->set_icon(get_theme_icon("Stop", "EditorIcons")); - _preview->set_color(get_theme_color("dark_color_2", "Editor")); - set_color(get_theme_color("dark_color_1", "Editor")); + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + _stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); + _preview->set_color(get_theme_color(SNAME("dark_color_2"), SNAME("Editor"))); + set_color(get_theme_color(SNAME("dark_color_1"), SNAME("Editor"))); _indicator->update(); _preview->update(); @@ -85,7 +86,7 @@ void AudioStreamEditor::_draw_preview() { } Vector<Color> color; - color.push_back(get_theme_color("contrast_color_2", "Editor")); + color.push_back(get_theme_color(SNAME("contrast_color_2"), SNAME("Editor"))); RS::get_singleton()->canvas_item_add_multiline(_preview->get_canvas_item(), lines, color); } @@ -105,30 +106,35 @@ void AudioStreamEditor::_audio_changed() { void AudioStreamEditor::_play() { if (_player->is_playing()) { + // '_pausing' variable indicates that we want to pause the audio player, not stop it. See '_on_finished()'. + _pausing = true; _player->stop(); - _play_button->set_icon(get_theme_icon("MainPlay", "EditorIcons")); + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); set_process(false); } else { _player->play(_current); - _play_button->set_icon(get_theme_icon("Pause", "EditorIcons")); + _play_button->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); set_process(true); } } void AudioStreamEditor::_stop() { _player->stop(); - _play_button->set_icon(get_theme_icon("MainPlay", "EditorIcons")); + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); _current = 0; _indicator->update(); set_process(false); } void AudioStreamEditor::_on_finished() { - _play_button->set_icon(get_theme_icon("MainPlay", "EditorIcons")); - if (_current == _player->get_stream()->get_length()) { + _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); + if (!_pausing) { _current = 0; _indicator->update(); + } else { + _pausing = false; } + set_process(false); } void AudioStreamEditor::_draw_indicator() { @@ -139,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(SNAME("accent_color"), SNAME("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(SNAME("TimelineIndicator"), SNAME("EditorIcons")), + Point2(ofs_x - get_theme_icon(SNAME("TimelineIndicator"), SNAME("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); @@ -194,8 +203,6 @@ void AudioStreamEditor::_bind_methods() { AudioStreamEditor::AudioStreamEditor() { set_custom_minimum_size(Size2(1, 100) * EDSCALE); - _current = 0; - _dragging = false; _player = memnew(AudioStreamPlayer); _player->connect("finished", callable_mp(this, &AudioStreamEditor::_on_finished)); @@ -225,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); @@ -235,14 +243,14 @@ AudioStreamEditor::AudioStreamEditor() { _current_label = memnew(Label); _current_label->set_align(Label::ALIGN_RIGHT); _current_label->set_h_size_flags(SIZE_EXPAND_FILL); - _current_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("status_source", "EditorFonts")); - _current_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("status_source_size", "EditorFonts")); + _current_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + _current_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); _current_label->set_modulate(Color(1, 1, 1, 0.5)); hbox->add_child(_current_label); _duration_label = memnew(Label); - _duration_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("status_source", "EditorFonts")); - _duration_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("status_source_size", "EditorFonts")); + _duration_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + _duration_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); hbox->add_child(_duration_label); } diff --git a/editor/plugins/audio_stream_editor_plugin.h b/editor/plugins/audio_stream_editor_plugin.h index aa906a6a05..14e829d025 100644 --- a/editor/plugins/audio_stream_editor_plugin.h +++ b/editor/plugins/audio_stream_editor_plugin.h @@ -41,17 +41,18 @@ class AudioStreamEditor : public ColorRect { GDCLASS(AudioStreamEditor, ColorRect); Ref<AudioStream> stream; - AudioStreamPlayer *_player; - ColorRect *_preview; - Control *_indicator; - Label *_current_label; - Label *_duration_label; + AudioStreamPlayer *_player = nullptr; + ColorRect *_preview = nullptr; + Control *_indicator = nullptr; + Label *_current_label = nullptr; + Label *_duration_label = nullptr; - Button *_play_button; - Button *_stop_button; + Button *_play_button = nullptr; + Button *_stop_button = nullptr; - float _current; - bool _dragging; + float _current = 0; + bool _dragging = false; + bool _pausing = false; void _audio_changed(); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index d92837b68d..d5ef13e426 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 { @@ -472,6 +472,8 @@ float CanvasItemEditor::snap_angle(float p_target, float p_start) const { } void CanvasItemEditor::_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()) { @@ -479,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); @@ -629,9 +631,9 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel Node *node = r_items[i].item; // Make sure the selected node is in the current scene, or editable - while (node && node != get_tree()->get_edited_scene_root() && node->get_owner() != scene && !scene->is_editable_instance(node->get_owner())) { - node = node->get_parent(); - }; + if (node && node != get_tree()->get_edited_scene_root()) { + node = scene->get_deepest_editable_node(node); + } CanvasItem *canvas_item = Object::cast_to<CanvasItem>(node); if (!p_allow_locked) { @@ -664,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; @@ -762,7 +677,7 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); Node *scene = editor->get_edited_scene(); - bool editable = p_node == scene || p_node->get_owner() == scene || scene->is_editable_instance(p_node->get_owner()); + bool editable = p_node == scene || p_node->get_owner() == scene || p_node == scene->get_deepest_editable_node(p_node); bool lock_children = p_node->has_meta("_edit_group_") && p_node->get_meta("_edit_group_"); bool locked = _is_node_locked(p_node); @@ -802,11 +717,15 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append) { bool still_selected = true; - if (p_append) { + if (p_append && !editor_selection->get_selected_node_list().is_empty()) { if (editor_selection->is_selected(item)) { // Already in the selection, remove it from the selected nodes editor_selection->remove_node(item); still_selected = false; + + if (editor_selection->get_selected_node_list().size() == 1) { + editor->push_item(editor_selection->get_selected_node_list()[0]); + } } else { // Add the item to the selection editor_selection->add_node(item); @@ -880,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(); @@ -936,29 +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); canvas_item->_edit_set_state(se->undo_state); - if (restore_bones) { - _restore_canvas_item_ik_chain(canvas_item, &(se->pre_drag_bones_undo_state)); - } } } @@ -982,13 +843,15 @@ void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_ite for (List<CanvasItem *>::Element *E = modified_canvas_items.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state()); - undo_redo->add_undo_method(canvas_item, "_edit_set_state", se->undo_state); - if (commit_bones) { - for (List<Dictionary>::Element *F = se->pre_drag_bones_undo_state.front(); F; F = F->next()) { - canvas_item = Object::cast_to<CanvasItem>(canvas_item->get_parent()); - undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state()); - undo_redo->add_undo_method(canvas_item, "_edit_set_state", F->get()); + if (se) { + undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state()); + undo_redo->add_undo_method(canvas_item, "_edit_set_state", se->undo_state); + if (commit_bones) { + for (List<Dictionary>::Element *F = se->pre_drag_bones_undo_state.front(); F; F = F->next()) { + canvas_item = Object::cast_to<CanvasItem>(canvas_item->get_parent()); + undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state()); + undo_redo->add_undo_method(canvas_item, "_edit_set_state", F->get()); + } } } } @@ -1021,6 +884,32 @@ void CanvasItemEditor::_selection_menu_hide() { selection_menu->set_size(Vector2(0, 0)); } +void CanvasItemEditor::_add_node_pressed(int p_result) { + if (p_result == AddNodeOption::ADD_NODE) { + editor->get_scene_tree_dock()->open_add_child_dialog(); + } else if (p_result == AddNodeOption::ADD_INSTANCE) { + editor->get_scene_tree_dock()->open_instance_child_dialog(); + } +} + +void CanvasItemEditor::_node_created(Node *p_node) { + if (node_create_position == Point2()) { + return; + } + + CanvasItem *c = Object::cast_to<CanvasItem>(p_node); + if (c) { + Transform2D xform = c->get_global_transform_with_canvas().affine_inverse() * c->get_transform(); + c->_edit_set_position(xform.xform(node_create_position)); + } + + call_deferred(SNAME("_reset_create_position")); // Defer the call in case more than one node is added. +} + +void CanvasItemEditor::_reset_create_position() { + node_create_position = Point2(); +} + bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> b = p_event; Ref<InputEventMouseMotion> m = p_event; @@ -1065,7 +954,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } // Start dragging a guide - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { // Press button if (b->get_position().x < RULER_WIDTH && b->get_position().y < RULER_WIDTH) { // Drag a new double guide @@ -1124,7 +1013,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } // Release confirms the guide move - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { if (show_guides && EditorNode::get_singleton()->get_edited_scene()) { Transform2D xform = viewport_scrollable->get_transform() * transform; @@ -1233,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() == BUTTON_WHEEL_LEFT || (b->get_shift() && b->get_button_index() == 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(); @@ -1246,7 +1135,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } if (b->is_pressed() && - (b->get_button_index() == BUTTON_WHEEL_RIGHT || (b->get_shift() && b->get_button_index() == 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(); @@ -1254,49 +1143,51 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } } - if (b->is_pressed() && b->get_button_index() == BUTTON_WHEEL_DOWN) { + if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { // Scroll or pan down if (pan_on_scroll) { 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); - if (b->get_factor() != 1.f) { - new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f); + zoom_widget->set_zoom_by_increments(-1, Input::get_singleton()->is_key_pressed(KEY_ALT)); + if (!Math::is_equal_approx(b->get_factor(), 1.0f)) { + // Handle high-precision (analog) scrolling. + 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; } - if (b->is_pressed() && b->get_button_index() == BUTTON_WHEEL_UP) { + if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { // Scroll or pan up if (pan_on_scroll) { 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); - if (b->get_factor() != 1.f) { - new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f); + zoom_widget->set_zoom_by_increments(1, Input::get_singleton()->is_key_pressed(KEY_ALT)); + if (!Math::is_equal_approx(b->get_factor(), 1.0f)) { + // Handle high-precision (analog) scrolling. + 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; } if (!panning) { if (b->is_pressed() && - (b->get_button_index() == BUTTON_MIDDLE || - b->get_button_index() == BUTTON_RIGHT || - (b->get_button_index() == BUTTON_LEFT && tool == TOOL_PAN) || - (b->get_button_index() == BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { + (b->get_button_index() == MOUSE_BUTTON_MIDDLE || + b->get_button_index() == MOUSE_BUTTON_RIGHT || + (b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_PAN) || + (b->get_button_index() == MOUSE_BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { // Pan the viewport panning = true; } } if (panning) { - if (!b->is_pressed() && (pan_on_scroll || (b->get_button_index() != BUTTON_WHEEL_DOWN && b->get_button_index() != BUTTON_WHEEL_UP))) { + if (!b->is_pressed() && (pan_on_scroll || (b->get_button_index() != MOUSE_BUTTON_WHEEL_DOWN && b->get_button_index() != MOUSE_BUTTON_WHEEL_UP))) { // Stop panning the viewport (for any mouse button press except zooming) panning = false; } @@ -1305,6 +1196,30 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo Ref<InputEventKey> k = p_event; if (k.is_valid()) { + if (k->is_pressed()) { + if (ED_GET_SHORTCUT("canvas_item_editor/zoom_3.125_percent")->is_shortcut(p_event)) { + _update_zoom((1.0 / 32.0) * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_6.25_percent")->is_shortcut(p_event)) { + _update_zoom((1.0 / 16.0) * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_12.5_percent")->is_shortcut(p_event)) { + _update_zoom((1.0 / 8.0) * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_25_percent")->is_shortcut(p_event)) { + _update_zoom((1.0 / 4.0) * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_50_percent")->is_shortcut(p_event)) { + _update_zoom((1.0 / 2.0) * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_100_percent")->is_shortcut(p_event)) { + _update_zoom(1.0 * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_200_percent")->is_shortcut(p_event)) { + _update_zoom(2.0 * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_400_percent")->is_shortcut(p_event)) { + _update_zoom(4.0 * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_800_percent")->is_shortcut(p_event)) { + _update_zoom(8.0 * MAX(1, EDSCALE)); + } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_1600_percent")->is_shortcut(p_event)) { + _update_zoom(16.0 * MAX(1, EDSCALE)); + } + } + bool is_pan_key = pan_view_shortcut.is_valid() && pan_view_shortcut->is_shortcut(p_event); if (is_pan_key && (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || drag_type != DRAG_NONE)) { @@ -1352,15 +1267,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; } @@ -1382,7 +1298,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { // Drag the pivot (in pivot mode / with V key) if (drag_type == DRAG_NONE) { - if ((b.is_valid() && b->is_pressed() && b->get_button_index() == BUTTON_LEFT && tool == TOOL_EDIT_PIVOT) || + if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_EDIT_PIVOT) || (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == KEY_V)) { List<CanvasItem *> selection = _get_edited_canvas_items(); @@ -1436,7 +1352,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { // Confirm the pivot move if (drag_selection.size() >= 1 && - ((b.is_valid() && !b->is_pressed() && b->get_button_index() == BUTTON_LEFT && tool == TOOL_EDIT_PIVOT) || + ((b.is_valid() && !b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_EDIT_PIVOT) || (k.is_valid() && !k->is_pressed() && k->get_keycode() == KEY_V))) { _commit_canvas_item_state( drag_selection, @@ -1450,7 +1366,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { } // Cancel a drag - if (b.is_valid() && b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); drag_type = DRAG_NONE; viewport->update(); @@ -1460,84 +1376,14 @@ 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; // Start rotation if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && b->is_pressed()) { - if ((b->get_command() && !b->get_alt() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { + 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 @@ -1580,7 +1426,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { } // Confirms the node rotation - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { if (drag_selection.size() != 1) { _commit_canvas_item_state( drag_selection, @@ -1604,7 +1450,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { } // Cancel a drag - if (b.is_valid() && b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); drag_type = DRAG_NONE; viewport->update(); @@ -1618,7 +1464,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() == 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]; @@ -1637,7 +1483,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { // Starts anchor dragging if needed if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { Control *control = Object::cast_to<Control>(selection[0]); @@ -1703,7 +1549,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); @@ -1757,7 +1603,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { } // Confirms new anchor position - if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { + if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { _commit_canvas_item_state( drag_selection, vformat(TTR("Move CanvasItem \"%s\" Anchor"), drag_selection[0]->get_name())); @@ -1766,7 +1612,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { } // Cancel a drag - if (b.is_valid() && b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); drag_type = DRAG_NONE; viewport->update(); @@ -1782,7 +1628,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { // Drag resize handles if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; @@ -1851,8 +1697,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; @@ -1936,7 +1782,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { } // Confirm resize - if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { + if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { const Node2D *node2d = Object::cast_to<Node2D>(drag_selection[0]); if (node2d) { // Extends from Node2D. @@ -1973,7 +1819,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { } // Cancel a drag - if (b.is_valid() && b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; @@ -1991,7 +1837,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() == 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]; @@ -2037,8 +1883,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); @@ -2087,7 +1933,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } // Confirm resize - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT && !b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { if (drag_selection.size() != 1) { _commit_canvas_item_state( drag_selection, @@ -2112,7 +1958,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } // Cancel a drag - if (b.is_valid() && b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection); drag_type = DRAG_NONE; viewport->update(); @@ -2129,8 +1975,8 @@ 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() == BUTTON_LEFT && b->is_pressed()) { - if ((b->get_alt() && !b->get_control()) || tool == TOOL_MOVE) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { + if ((b->is_alt_pressed() && !b->is_ctrl_pressed()) || tool == TOOL_MOVE) { List<CanvasItem *> selection = _get_edited_canvas_items(); drag_selection.clear(); @@ -2171,14 +2017,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()); @@ -2198,7 +2036,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; @@ -2207,30 +2045,19 @@ 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); 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)); - } + canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); index++; } return true; } // Confirm the move (only if it was moved) - if (b.is_valid() && !b->is_pressed() && b->get_button_index() == BUTTON_LEFT) { + if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT) { if (transform.affine_inverse().xform(b->get_position()) != drag_from) { if (drag_selection.size() != 1) { _commit_canvas_item_state( @@ -2263,7 +2090,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } // Cancel a drag - if (b.is_valid() && b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { _restore_canvas_item_state(drag_selection, true); snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; @@ -2286,18 +2113,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) { @@ -2309,12 +2128,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)); } @@ -2345,19 +2164,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); 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)); - } + canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); index++; } } @@ -2401,18 +2210,18 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_NONE) { if (b.is_valid() && - ((b->get_button_index() == BUTTON_RIGHT && b->get_alt() && tool == TOOL_SELECT) || - (b->get_button_index() == BUTTON_LEFT && tool == TOOL_LIST_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_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()) { @@ -2456,14 +2265,22 @@ 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->get_button_index() == BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { + 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(); + node_create_position = transform.affine_inverse().xform((get_local_mouse_position())); + return true; + } + + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { // Single item selection Point2 click = transform.affine_inverse().xform(b->get_position()); @@ -2475,23 +2292,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(); @@ -2503,33 +2314,49 @@ 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) > (8 * MAX(1, EDSCALE)) / zoom; + 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; } } } if (drag_type == DRAG_BOX_SELECTION) { - if (b.is_valid() && !b->is_pressed() && b->get_button_index() == BUTTON_LEFT) { + if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT) { // Confirms box selection Node *scene = editor->get_edited_scene(); if (scene) { @@ -2545,6 +2372,9 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } _find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems); + if (selitems.size() == 1 && editor_selection->get_selected_node_list().is_empty()) { + editor->push_item(selitems[0]); + } for (List<CanvasItem *>::Element *E = selitems.front(); E; E = E->next()) { editor_selection->add_node(E->get()); } @@ -2555,7 +2385,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { return true; } - if (b.is_valid() && b->is_pressed() && b->get_button_index() == BUTTON_RIGHT) { + if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT) { // Cancel box selection drag_type = DRAG_NONE; viewport->update(); @@ -2580,6 +2410,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) { if (tool != TOOL_RULER) { + ruler_tool_active = false; return false; } @@ -2591,7 +2422,7 @@ bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) { ruler_tool_origin = snap_point(viewport->get_local_mouse_position() / zoom + view_offset); } - if (b.is_valid() && b->get_button_index() == BUTTON_LEFT) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT) { if (b->is_pressed()) { ruler_tool_active = true; } else { @@ -2708,7 +2539,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { // Grab focus if (!viewport->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) { - viewport->call_deferred("grab_focus"); + viewport->call_deferred(SNAME("grab_focus")); } } @@ -2796,10 +2627,10 @@ void CanvasItemEditor::_update_cursor() { } void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Side p_side) { - Color color = get_theme_color("font_color", "Editor"); + Color color = get_theme_color(SNAME("font_color"), SNAME("Editor")); color.a = 0.8; - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); Size2 text_size = font->get_string_size(p_string, font_size); switch (p_side) { case SIDE_LEFT: @@ -2835,7 +2666,7 @@ void CanvasItemEditor::_draw_percentage_at_position(float p_value, Point2 p_posi void CanvasItemEditor::_draw_focus() { // Draw the focus around the base viewport if (viewport->has_focus()) { - get_theme_stylebox("Focus", "EditorStyles")->draw(viewport->get_canvas_item(), Rect2(Point2(), viewport->get_size())); + get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles"))->draw(viewport->get_canvas_item(), Rect2(Point2(), viewport->get_size())); } } @@ -2867,22 +2698,23 @@ void CanvasItemEditor::_draw_guides() { } // Dragged guide - Color text_color = get_theme_color("font_color", "Editor"); - text_color.a = 0.5; + Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); + Color outline_color = text_color.inverted(); + const float outline_size = 2; if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE) { String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).x))); - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); Size2 text_size = font->get_string_size(str, font_size); - viewport->draw_string(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HALIGN_LEFT, -1, font_size, text_color); + viewport->draw_string(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HALIGN_LEFT, -1, font_size, text_color, outline_size, outline_color); viewport->draw_line(Point2(dragged_guide_pos.x, 0), Point2(dragged_guide_pos.x, viewport->get_size().y), guide_color, Math::round(EDSCALE)); } if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_H_GUIDE) { String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).y))); - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); Size2 text_size = font->get_string_size(str, font_size); - viewport->draw_string(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HALIGN_LEFT, -1, font_size, text_color); + viewport->draw_string(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HALIGN_LEFT, -1, font_size, text_color, outline_size, outline_color); viewport->draw_line(Point2(0, dragged_guide_pos.y), Point2(viewport->get_size().x, dragged_guide_pos.y), guide_color, Math::round(EDSCALE)); } } @@ -2902,12 +2734,12 @@ void CanvasItemEditor::_draw_smart_snapping() { } void CanvasItemEditor::_draw_rulers() { - Color bg_color = get_theme_color("dark_color_2", "Editor"); - Color graduation_color = get_theme_color("font_color", "Editor").lerp(bg_color, 0.5); - Color font_color = get_theme_color("font_color", "Editor"); + Color bg_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); + Color graduation_color = get_theme_color(SNAME("font_color"), SNAME("Editor")).lerp(bg_color, 0.5); + Color font_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); font_color.a = 0.8; - Ref<Font> font = get_theme_font("rulers", "EditorFonts"); - int font_size = get_theme_font_size("rulers_size", "EditorFonts"); + Ref<Font> font = get_theme_font(SNAME("rulers"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("rulers_size"), SNAME("EditorFonts")); // The rule transform Transform2D ruler_transform = Transform2D(); @@ -3068,7 +2900,7 @@ void CanvasItemEditor::_draw_ruler_tool() { } if (ruler_tool_active) { - Color ruler_primary_color = get_theme_color("accent_color", "Editor"); + Color ruler_primary_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); Color ruler_secondary_color = ruler_primary_color; ruler_secondary_color.a = 0.5; @@ -3085,9 +2917,9 @@ void CanvasItemEditor::_draw_ruler_tool() { viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE)); } - Ref<Font> font = get_theme_font("bold", "EditorFonts"); - int font_size = get_theme_font_size("bold_size", "EditorFonts"); - Color font_color = get_theme_color("font_color", "Editor"); + Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); + Color font_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); Color font_secondary_color = font_color; font_secondary_color.set_v(font_secondary_color.get_v() > 0.5 ? 0.7 : 0.3); Color outline_color = font_color.inverted(); @@ -3188,8 +3020,8 @@ void CanvasItemEditor::_draw_ruler_tool() { } } else { if (grid_snap_active) { - Ref<Texture2D> position_icon = get_theme_icon("EditorPosition", "EditorIcons"); - viewport->draw_texture(get_theme_icon("EditorPosition", "EditorIcons"), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2); + Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); + viewport->draw_texture(get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2); } } } @@ -3407,9 +3239,9 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) { } void CanvasItemEditor::_draw_selection() { - Ref<Texture2D> pivot_icon = get_theme_icon("EditorPivot", "EditorIcons"); - Ref<Texture2D> position_icon = get_theme_icon("EditorPosition", "EditorIcons"); - Ref<Texture2D> previous_position_icon = get_theme_icon("EditorPositionPrevious", "EditorIcons"); + Ref<Texture2D> pivot_icon = get_theme_icon(SNAME("EditorPivot"), SNAME("EditorIcons")); + Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); + Ref<Texture2D> previous_position_icon = get_theme_icon(SNAME("EditorPositionPrevious"), SNAME("EditorIcons")); RID ci = viewport->get_canvas_item(); @@ -3520,7 +3352,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)) { @@ -3535,16 +3367,16 @@ void CanvasItemEditor::_draw_selection() { points.push_back(Vector2(move_factor.x * EDSCALE, -5 * EDSCALE)); points.push_back(Vector2((move_factor.x + 10) * EDSCALE, 0)); - viewport->draw_colored_polygon(points, get_theme_color("axis_x_color", "Editor")); - viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color("axis_x_color", "Editor"), Math::round(EDSCALE)); + viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), SNAME("Editor")), Math::round(EDSCALE)); points.clear(); points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE)); points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE)); points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE)); - viewport->draw_colored_polygon(points, get_theme_color("axis_y_color", "Editor")); - viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color("axis_y_color", "Editor"), Math::round(EDSCALE)); + viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), SNAME("Editor")), Math::round(EDSCALE)); viewport->draw_set_transform_matrix(viewport->get_transform()); } @@ -3574,12 +3406,12 @@ void CanvasItemEditor::_draw_selection() { viewport->draw_set_transform_matrix(simple_xform); Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); - viewport->draw_rect(x_handle_rect, get_theme_color("axis_x_color", "Editor")); - viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color("axis_x_color", "Editor"), Math::round(EDSCALE)); + viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), SNAME("Editor")), Math::round(EDSCALE)); Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE); - viewport->draw_rect(y_handle_rect, get_theme_color("axis_y_color", "Editor")); - viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color("axis_y_color", "Editor"), Math::round(EDSCALE)); + viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), SNAME("Editor")), Math::round(EDSCALE)); viewport->draw_set_transform_matrix(viewport->get_transform()); } @@ -3594,11 +3426,11 @@ void CanvasItemEditor::_draw_selection() { viewport->draw_rect( Rect2(bsfrom, bsto - bsfrom), - get_theme_color("box_selection_fill_color", "Editor")); + get_theme_color(SNAME("box_selection_fill_color"), SNAME("Editor"))); viewport->draw_rect( Rect2(bsfrom, bsto - bsfrom), - get_theme_color("box_selection_stroke_color", "Editor"), + get_theme_color(SNAME("box_selection_stroke_color"), SNAME("Editor")), false, Math::round(EDSCALE)); } @@ -3608,7 +3440,7 @@ void CanvasItemEditor::_draw_selection() { viewport->draw_line( transform.xform(drag_rotation_center), transform.xform(drag_to), - get_theme_color("accent_color", "Editor") * Color(1, 1, 1, 0.6), + get_theme_color(SNAME("accent_color"), SNAME("Editor")) * Color(1, 1, 1, 0.6), Math::round(2 * EDSCALE)); } } @@ -3656,8 +3488,8 @@ void CanvasItemEditor::_draw_straight_line(Point2 p_from, Point2 p_to, Color p_c void CanvasItemEditor::_draw_axis() { if (show_origin) { - _draw_straight_line(Point2(), Point2(1, 0), get_theme_color("axis_x_color", "Editor") * Color(1, 1, 1, 0.75)); - _draw_straight_line(Point2(), Point2(0, 1), get_theme_color("axis_y_color", "Editor") * Color(1, 1, 1, 0.75)); + _draw_straight_line(Point2(), Point2(1, 0), get_theme_color(SNAME("axis_x_color"), SNAME("Editor")) * Color(1, 1, 1, 0.75)); + _draw_straight_line(Point2(), Point2(0, 1), get_theme_color(SNAME("axis_y_color"), SNAME("Editor")) * Color(1, 1, 1, 0.75)); } if (show_viewport) { @@ -3680,65 +3512,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); @@ -3770,7 +3543,7 @@ void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Trans Transform2D xform = transform * canvas_xform * parent_xform; // Draw the node's position - Ref<Texture2D> position_icon = get_theme_icon("EditorPositionUnselected", "EditorIcons"); + Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPositionUnselected"), SNAME("EditorIcons")); Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = viewport->get_transform() * unscaled_transform; viewport->draw_set_transform_matrix(simple_xform); @@ -3786,8 +3559,8 @@ void CanvasItemEditor::_draw_hover() { Ref<Texture2D> node_icon = hovering_results[i].icon; String node_name = hovering_results[i].name; - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); Size2 node_name_size = font->get_string_size(node_name); Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3)); @@ -3840,13 +3613,13 @@ void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p if (canvas_item) { float offset = 0; - Ref<Texture2D> lock = get_theme_icon("LockViewport", "EditorIcons"); + Ref<Texture2D> lock = get_theme_icon(SNAME("LockViewport"), SNAME("EditorIcons")); if (p_node->has_meta("_edit_lock_") && show_edit_locks) { lock->draw(viewport_canvas_item, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); offset += lock->get_size().x; } - Ref<Texture2D> group = get_theme_icon("GroupViewport", "EditorIcons"); + Ref<Texture2D> group = get_theme_icon(SNAME("GroupViewport"), SNAME("EditorIcons")); if (canvas_item->has_meta("_edit_group_") && show_edit_locks) { group->draw(viewport_canvas_item, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); //offset += group->get_size().x; @@ -3854,72 +3627,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 && !scene->is_editable_instance(canvas_item->get_owner()))) { - 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(); @@ -3979,7 +3686,6 @@ void CanvasItemEditor::_draw_viewport() { force_over_plugin_list->forward_canvas_force_draw_over_viewport(viewport); } - _draw_bones(); if (show_rulers) { _draw_rulers(); } @@ -4105,15 +3811,15 @@ 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(); } } } if (p_what == NOTIFICATION_ENTER_TREE) { - select_sb->set_texture(get_theme_icon("EditorRect2D", "EditorIcons")); + select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); for (int i = 0; i < 4; i++) { select_sb->set_margin_size(Side(i), 4); select_sb->set_default_margin(Side(i), 4); @@ -4121,104 +3827,94 @@ 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)); + select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); } 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")); - move_button->set_icon(get_theme_icon("ToolMove", "EditorIcons")); - scale_button->set_icon(get_theme_icon("ToolScale", "EditorIcons")); - rotate_button->set_icon(get_theme_icon("ToolRotate", "EditorIcons")); - smart_snap_button->set_icon(get_theme_icon("Snap", "EditorIcons")); - grid_snap_button->set_icon(get_theme_icon("SnapGrid", "EditorIcons")); - snap_config_menu->set_icon(get_theme_icon("GuiTabMenuHl", "EditorIcons")); - skeleton_menu->set_icon(get_theme_icon("Bone", "EditorIcons")); - override_camera_button->set_icon(get_theme_icon("Camera2D", "EditorIcons")); - pan_button->set_icon(get_theme_icon("ToolPan", "EditorIcons")); - ruler_button->set_icon(get_theme_icon("Ruler", "EditorIcons")); - pivot_button->set_icon(get_theme_icon("EditPivot", "EditorIcons")); - select_handle = get_theme_icon("EditorHandle", "EditorIcons"); - anchor_handle = get_theme_icon("EditorControlAnchor", "EditorIcons"); - lock_button->set_icon(get_theme_icon("Lock", "EditorIcons")); - unlock_button->set_icon(get_theme_icon("Unlock", "EditorIcons")); - group_button->set_icon(get_theme_icon("Group", "EditorIcons")); - ungroup_button->set_icon(get_theme_icon("Ungroup", "EditorIcons")); - key_loc_button->set_icon(get_theme_icon("KeyPosition", "EditorIcons")); - key_rot_button->set_icon(get_theme_icon("KeyRotation", "EditorIcons")); - key_scale_button->set_icon(get_theme_icon("KeyScale", "EditorIcons")); - key_insert_button->set_icon(get_theme_icon("Key", "EditorIcons")); - key_auto_insert_button->set_icon(get_theme_icon("AutoKey", "EditorIcons")); + select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + list_select_button->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); + move_button->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + scale_button->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + rotate_button->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + smart_snap_button->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + grid_snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + snap_config_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + skeleton_menu->set_icon(get_theme_icon(SNAME("Bone"), SNAME("EditorIcons"))); + override_camera_button->set_icon(get_theme_icon(SNAME("Camera2D"), SNAME("EditorIcons"))); + pan_button->set_icon(get_theme_icon(SNAME("ToolPan"), SNAME("EditorIcons"))); + ruler_button->set_icon(get_theme_icon(SNAME("Ruler"), SNAME("EditorIcons"))); + pivot_button->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + select_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + anchor_handle = get_theme_icon(SNAME("EditorControlAnchor"), SNAME("EditorIcons")); + lock_button->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); + unlock_button->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); + group_button->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + ungroup_button->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); + key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); + key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); + key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); + key_auto_insert_button->set_icon(get_theme_icon(SNAME("AutoKey"), SNAME("EditorIcons"))); // Use a different color for the active autokey icon to make them easier // to distinguish from the other key icons at the top. On a light theme, // the icon will be dark, so we need to lighten it before blending it // with the red color. const Color key_auto_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25); 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")); + animation_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("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")); + presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); PopupMenu *p = presets_menu->get_popup(); p->clear(); - p->add_icon_item(get_theme_icon("ControlAlignTopLeft", "EditorIcons"), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); - p->add_icon_item(get_theme_icon("ControlAlignTopRight", "EditorIcons"), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); - p->add_icon_item(get_theme_icon("ControlAlignBottomRight", "EditorIcons"), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); - p->add_icon_item(get_theme_icon("ControlAlignBottomLeft", "EditorIcons"), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); p->add_separator(); - p->add_icon_item(get_theme_icon("ControlAlignLeftCenter", "EditorIcons"), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); - p->add_icon_item(get_theme_icon("ControlAlignTopCenter", "EditorIcons"), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); - p->add_icon_item(get_theme_icon("ControlAlignRightCenter", "EditorIcons"), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); - p->add_icon_item(get_theme_icon("ControlAlignBottomCenter", "EditorIcons"), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); - p->add_icon_item(get_theme_icon("ControlAlignCenter", "EditorIcons"), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); p->add_separator(); - p->add_icon_item(get_theme_icon("ControlAlignLeftWide", "EditorIcons"), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); - p->add_icon_item(get_theme_icon("ControlAlignTopWide", "EditorIcons"), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); - p->add_icon_item(get_theme_icon("ControlAlignRightWide", "EditorIcons"), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); - p->add_icon_item(get_theme_icon("ControlAlignBottomWide", "EditorIcons"), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); - p->add_icon_item(get_theme_icon("ControlVcenterWide", "EditorIcons"), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); - p->add_icon_item(get_theme_icon("ControlHcenterWide", "EditorIcons"), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); p->add_separator(); - p->add_icon_item(get_theme_icon("ControlAlignWide", "EditorIcons"), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); - p->add_icon_item(get_theme_icon("Anchor", "EditorIcons"), TTR("Keep Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); + p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); p->add_separator(); p->add_submenu_item(TTR("Anchors only"), "Anchors"); - p->set_item_icon(21, get_theme_icon("Anchor", "EditorIcons")); + p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); anchors_popup->clear(); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignTopLeft", "EditorIcons"), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignTopRight", "EditorIcons"), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignBottomRight", "EditorIcons"), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignBottomLeft", "EditorIcons"), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignLeftCenter", "EditorIcons"), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignTopCenter", "EditorIcons"), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignRightCenter", "EditorIcons"), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignBottomCenter", "EditorIcons"), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignCenter", "EditorIcons"), TTR("Center"), ANCHORS_PRESET_CENTER); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignLeftWide", "EditorIcons"), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignTopWide", "EditorIcons"), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignRightWide", "EditorIcons"), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignBottomWide", "EditorIcons"), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); - anchors_popup->add_icon_item(get_theme_icon("ControlVcenterWide", "EditorIcons"), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); - anchors_popup->add_icon_item(get_theme_icon("ControlHcenterWide", "EditorIcons"), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon("ControlAlignWide", "EditorIcons"), TTR("Full Rect"), ANCHORS_PRESET_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); - anchor_mode_button->set_icon(get_theme_icon("Anchor", "EditorIcons")); + anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); } if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { @@ -4270,46 +3966,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; @@ -4325,11 +3981,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); @@ -4526,33 +4180,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); @@ -4577,36 +4204,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) { @@ -4665,7 +4268,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); @@ -4697,7 +4300,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); @@ -4713,7 +4316,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); @@ -4740,11 +4343,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.")); } } @@ -4837,10 +4440,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; @@ -5189,107 +4801,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(); + 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()); - } break; - case SKELETON_CLEAR_BONES: { - Map<Node *, Object *> &selection = editor_selection->get_selection(); - - 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()) { - continue; - } - if (!n2d->has_meta("_edit_bone_")) { + Node *n2d_parent = n2d->get_parent(); + if (!n2d_parent) { 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); } } @@ -5332,9 +4882,6 @@ void CanvasItemEditor::_focus_selection(int p_op) { rect = rect.merge(canvas_item_rect); } }; - if (count == 0) { - return; - } if (p_op == VIEW_CENTER_TO_SELECTION) { center = rect.position + rect.size / 2; @@ -5351,8 +4898,8 @@ void CanvasItemEditor::_focus_selection(int p_op) { zoom = scale_x < scale_y ? scale_x : scale_y; zoom *= 0.90; viewport->update(); - _update_zoom_label(); - call_deferred("_popup_callback", VIEW_CENTER_TO_SELECTION); + zoom_widget->set_zoom(zoom); + call_deferred(SNAME("_popup_callback"), VIEW_CENTER_TO_SELECTION); } } } @@ -5361,10 +4908,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(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")); @@ -5395,14 +4943,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; } @@ -5413,7 +4960,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")) { @@ -5543,7 +5090,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")) { @@ -5570,12 +5117,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(); } @@ -5658,8 +5199,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(); @@ -5675,7 +5214,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; @@ -5684,8 +5222,11 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed)); - editor->call_deferred("connect", "play_pressed", Callable(this, "_update_override_camera_button"), make_binds(true)); - editor->call_deferred("connect", "stop_pressed", Callable(this, "_update_override_camera_button"), make_binds(false)); + editor->get_scene_tree_dock()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created)); + editor->get_scene_tree_dock()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); + + editor->call_deferred(SNAME("connect"), "play_pressed", Callable(this, "_update_override_camera_button"), make_binds(true)); + editor->call_deferred(SNAME("connect"), "stop_pressed", Callable(this, "_update_override_camera_button"), make_binds(false)); hb = memnew(HBoxContainer); add_child(hb); @@ -5716,11 +5257,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); @@ -5750,9 +5286,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { warning_child_of_container = memnew(Label); warning_child_of_container->hide(); warning_child_of_container->set_text(TTR("Warning: Children of a container get their position and size determined only by their parent.")); - warning_child_of_container->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color("warning_color", "Editor")); - warning_child_of_container->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts")); - warning_child_of_container->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts")); + warning_child_of_container->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + warning_child_of_container->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); + warning_child_of_container->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); add_control_to_info_overlay(warning_child_of_container); h_scroll = memnew(HScrollBar); @@ -5767,38 +5303,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); @@ -5807,7 +5325,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { select_button->set_pressed(true); select_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/select_mode", TTR("Select Mode"), KEY_Q)); select_button->set_shortcut_context(this); - select_button->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate") + "\n" + TTR("Alt+Drag: Move") + "\n" + TTR("Press 'v' to Change Pivot, 'Shift+v' to Drag Pivot (while moving).") + "\n" + TTR("Alt+RMB: Depth list selection")); + select_button->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate") + "\n" + TTR("Alt+Drag: Move") + "\n" + TTR("Press 'v' to Change Pivot, 'Shift+v' to Drag Pivot (while moving).") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.")); hb->add_child(memnew(VSeparator)); @@ -5845,7 +5363,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->add_child(list_select_button); list_select_button->set_toggle_mode(true); list_select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_LIST_SELECT)); - list_select_button->set_tooltip(TTR("Show a list of all objects at the position clicked\n(same as Alt+RMB in select mode).")); + list_select_button->set_tooltip(TTR("Show list of selectable nodes at position clicked.")); pivot_button = memnew(Button); pivot_button->set_flat(true); @@ -5930,25 +5448,33 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->add_child(lock_button); 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).")); + lock_button->set_tooltip(TTR("Lock selected node, preventing selection and movement.")); + // 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).")); + unlock_button->set_tooltip(TTR("Unlock selected node, allowing selection and movement.")); + // 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)); @@ -5960,13 +5486,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)); @@ -5990,7 +5512,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); @@ -6035,7 +5557,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)); @@ -6044,7 +5565,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)); @@ -6053,14 +5573,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).")); @@ -6105,6 +5623,12 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { selection_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_selection_result_pressed)); selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide)); + add_node_menu = memnew(PopupMenu); + add_child(add_node_menu); + add_node_menu->add_icon_item(editor->get_scene_tree_dock()->get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add Node Here")); + add_node_menu->add_icon_item(editor->get_scene_tree_dock()->get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Instance Scene Here")); + add_node_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_add_node_pressed)); + multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), KEY_KP_MULTIPLY); divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), KEY_KP_DIVIDE); pan_view_shortcut = ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), KEY_SPACE); @@ -6112,10 +5636,25 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true); singleton = this; + // To ensure that scripts can parse the list of shortcuts correctly, we have to define + // those shortcuts one by one. + // Resetting zoom to 100% is a duplicate shortcut of `canvas_item_editor/reset_zoom`, + // but it ensures both 1 and Ctrl + 0 can be used to reset zoom. + ED_SHORTCUT("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), KEY_MASK_SHIFT | KEY_5); + ED_SHORTCUT("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), KEY_MASK_SHIFT | KEY_4); + ED_SHORTCUT("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), KEY_MASK_SHIFT | KEY_3); + ED_SHORTCUT("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), KEY_MASK_SHIFT | KEY_2); + ED_SHORTCUT("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), KEY_MASK_SHIFT | KEY_1); + ED_SHORTCUT("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), KEY_1); + ED_SHORTCUT("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"), KEY_2); + ED_SHORTCUT("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"), KEY_3); + ED_SHORTCUT("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"), KEY_4); + ED_SHORTCUT("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"), KEY_5); + set_process_unhandled_key_input(true); // Update the menus' checkboxes - call_deferred("set_state", get_state()); + call_deferred(SNAME("set_state"), get_state()); } CanvasItemEditor *CanvasItemEditor::singleton = nullptr; @@ -6133,12 +5672,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); } } @@ -6208,7 +5747,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); } @@ -6321,26 +5860,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)); @@ -6351,11 +5890,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; @@ -6438,7 +5977,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()) { @@ -6447,19 +5986,18 @@ 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" || type == "CurveTexture" || type == "GradientTexture" || type == "StreamTexture2D" || - type == "AtlasTexture" || - type == "LargeTexture") { + type == "AtlasTexture") { Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res)); if (!texture.is_valid()) { continue; @@ -6467,10 +6005,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); } @@ -6478,7 +6016,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(); @@ -6552,7 +6090,7 @@ void CanvasItemEditorViewport::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { connect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit)); - label->add_theme_color_override("font_color", get_theme_color("warning_color", "Editor")); + label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); } break; case NOTIFICATION_EXIT_TREE: { disconnect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit)); @@ -6603,7 +6141,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 24149a57b0..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" @@ -79,6 +80,11 @@ public: TOOL_MAX }; + enum AddNodeOption { + ADD_NODE, + ADD_INSTANCE, + }; + private: EditorNode *editor; @@ -181,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 { @@ -203,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, @@ -217,7 +221,6 @@ private: DRAG_KEY_MOVE }; - EditorSelection *editor_selection; bool selection_menu_additive_selection; Tool tool; @@ -228,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; @@ -275,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; @@ -284,6 +282,7 @@ private: bool ruler_tool_active; Point2 ruler_tool_origin; + Point2 node_create_position; MenuOption last_option; @@ -376,10 +375,12 @@ private: Button *key_auto_insert_button; PopupMenu *selection_menu; + PopupMenu *add_node_menu; Control *top_ruler; Control *left_ruler; + Point2 drag_start_origin; DragType drag_type; Point2 drag_from; Point2 drag_to; @@ -408,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); @@ -419,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); @@ -436,10 +434,11 @@ private: void _snap_changed(); void _selection_result_pressed(int); void _selection_menu_hide(); + void _add_node_pressed(int p_result); + void _node_created(Node *p_node); + 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); @@ -469,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(); @@ -496,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( @@ -526,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); @@ -543,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: @@ -638,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 0c18975258..5d5f78e0dc 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" @@ -42,8 +42,8 @@ void CollisionPolygon3DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { - button_create->set_icon(get_theme_icon("Edit", "EditorIcons")); - button_edit->set_icon(get_theme_icon("MovePoint", "EditorIcons")); + button_create->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + button_edit->set_icon(get_theme_icon(SNAME("MovePoint"), SNAME("EditorIcons"))); button_edit->set_pressed(true); get_tree()->connect("node_removed", callable_mp(this, &CollisionPolygon3DEditor::_node_removed)); @@ -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); @@ -142,7 +142,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con switch (mode) { case MODE_CREATE: { - if (mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { if (!wip_active) { wip.clear(); wip.push_back(cpoint); @@ -166,16 +166,16 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con return true; } } - } else if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed() && wip_active) { + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) { _wip_close(); } } break; case MODE_EDIT: { - if (mb->get_button_index() == BUTTON_LEFT) { + 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); @@ -267,7 +267,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con } } } - if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed() && edited_point == -1) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && edited_point == -1) { int closest_idx = -1; Vector2 closest_pos; real_t closest_dist = 1e10; @@ -301,7 +301,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - if (edited_point != -1 && (wip_active || mm->get_button_mask() & BUTTON_MASK_LEFT)) { + if (edited_point != -1 && (wip_active || mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT)) { Vector2 gpoint = mm->get_position(); Vector3 ray_from = p_camera->project_ray_origin(gpoint); @@ -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); @@ -531,15 +531,15 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); - Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon("Editor3DHandle", "EditorIcons"); + Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons")); handle_material->set_point_size(handle->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle); 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/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index 112c545fd9..2c2adc2672 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -338,7 +338,7 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e if (mb.is_valid()) { Vector2 gpoint = mb->get_position(); - if (mb->get_button_index() == BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { for (int i = 0; i < handles.size(); i++) { if (xform.xform(handles[i]).distance_to(gpoint) < 8) { @@ -458,7 +458,7 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); - Ref<Texture2D> h = get_theme_icon("EditorHandle", "EditorIcons"); + Ref<Texture2D> h = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); Vector2 size = h->get_size() * 0.5; handles.clear(); diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp index 3403aeceba..c4deb296fb 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 + "'."); @@ -224,7 +224,7 @@ void CPUParticles2DEditorPlugin::_generate_emission_mask() { void CPUParticles2DEditorPlugin::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { menu->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(epoints->get_theme_icon("CPUParticles2D", "EditorIcons")); + menu->set_icon(epoints->get_theme_icon(SNAME("CPUParticles2D"), SNAME("EditorIcons"))); file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected)); } } diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp index f41ccfa86b..fc52cd0f99 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp @@ -41,7 +41,7 @@ void CPUParticles3DEditor::_node_removed(Node *p_node) { void CPUParticles3DEditor::_notification(int p_notification) { if (p_notification == NOTIFICATION_ENTER_TREE) { - options->set_icon(get_theme_icon("CPUParticles3D", "EditorIcons")); + options->set_icon(get_theme_icon(SNAME("CPUParticles3D"), SNAME("EditorIcons"))); } } diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index bff5cb8d2a..07ff0eb346 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -115,22 +115,24 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) { } switch (mb.get_button_index()) { - case BUTTON_RIGHT: + case MOUSE_BUTTON_RIGHT: _context_click_pos = mpos; open_context_menu(get_global_transform().xform(mpos)); break; - case BUTTON_MIDDLE: + case MOUSE_BUTTON_MIDDLE: remove_point(_hover_point); break; - case BUTTON_LEFT: + case MOUSE_BUTTON_LEFT: _dragging = true; break; + default: + break; } } - if (!mb.is_pressed() && _dragging && mb.get_button_index() == BUTTON_LEFT) { + if (!mb.is_pressed() && _dragging && mb.get_button_index() == MOUSE_BUTTON_LEFT) { _dragging = false; if (_has_undo_data) { UndoRedo &ur = *EditorNode::get_singleton()->get_undo_redo(); @@ -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; } @@ -391,7 +393,8 @@ int CurveEditor::get_point_at(Vector2 pos) const { } const Curve &curve = **_curve_ref; - const float r = _hover_radius * _hover_radius; + const float true_hover_radius = Math::round(_hover_radius * EDSCALE); + const float r = true_hover_radius * true_hover_radius; for (int i = 0; i < curve.get_point_count(); ++i) { Vector2 p = get_view_pos(curve.get_point_position(i)); @@ -517,8 +520,8 @@ void CurveEditor::set_hover_point_index(int index) { } void CurveEditor::update_view_transform() { - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); const real_t margin = font->get_height(font_size) + 2 * EDSCALE; @@ -556,7 +559,7 @@ Vector2 CurveEditor::get_tangent_view_pos(int i, TangentIndex tangent) const { Vector2 point_pos = get_view_pos(_curve_ref->get_point_position(i)); Vector2 control_pos = get_view_pos(_curve_ref->get_point_position(i) + dir); - return point_pos + _tangents_length * (control_pos - point_pos).normalized(); + return point_pos + Math::round(_tangents_length * EDSCALE) * (control_pos - point_pos).normalized(); } Vector2 CurveEditor::get_view_pos(Vector2 world_pos) const { @@ -633,7 +636,7 @@ void CurveEditor::_draw() { // Background Vector2 view_size = get_rect().size; - draw_style_box(get_theme_stylebox("bg", "Tree"), Rect2(Point2(), view_size)); + draw_style_box(get_theme_stylebox(SNAME("bg"), SNAME("Tree")), Rect2(Point2(), view_size)); // Grid @@ -642,8 +645,8 @@ void CurveEditor::_draw() { Vector2 min_edge = get_world_pos(Vector2(0, view_size.y)); Vector2 max_edge = get_world_pos(Vector2(view_size.x, 0)); - const Color grid_color0 = get_theme_color("mono_color", "Editor") * Color(1, 1, 1, 0.15); - const Color grid_color1 = get_theme_color("mono_color", "Editor") * Color(1, 1, 1, 0.07); + const Color grid_color0 = get_theme_color(SNAME("mono_color"), SNAME("Editor")) * Color(1, 1, 1, 0.15); + const Color grid_color1 = get_theme_color(SNAME("mono_color"), SNAME("Editor")) * Color(1, 1, 1, 0.07); draw_line(Vector2(min_edge.x, curve.get_min_value()), Vector2(max_edge.x, curve.get_min_value()), grid_color0); draw_line(Vector2(max_edge.x, curve.get_max_value()), Vector2(min_edge.x, curve.get_max_value()), grid_color0); draw_line(Vector2(0, min_edge.y), Vector2(0, max_edge.y), grid_color0); @@ -663,10 +666,10 @@ void CurveEditor::_draw() { draw_set_transform_matrix(Transform2D()); - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); float font_height = font->get_height(font_size); - Color text_color = get_theme_color("font_color", "Editor"); + Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); { // X axis @@ -693,7 +696,7 @@ void CurveEditor::_draw() { // Draw tangents for current point if (_selected_point >= 0) { - const Color tangent_color = get_theme_color("accent_color", "Editor"); + const Color tangent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); int i = _selected_point; Vector2 pos = curve.get_point_position(i); @@ -701,13 +704,13 @@ void CurveEditor::_draw() { if (i != 0) { Vector2 control_pos = get_tangent_view_pos(i, TANGENT_LEFT); draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE)); - draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(2), tangent_color); + draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color); } if (i != curve.get_point_count() - 1) { Vector2 control_pos = get_tangent_view_pos(i, TANGENT_RIGHT); draw_line(get_view_pos(pos), control_pos, tangent_color, Math::round(EDSCALE)); - draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(2), tangent_color); + draw_rect(Rect2(control_pos, Vector2(1, 1)).grow(Math::round(2 * EDSCALE)), tangent_color); } } @@ -715,8 +718,8 @@ void CurveEditor::_draw() { draw_set_transform_matrix(_world_to_view); - const Color line_color = get_theme_color("font_color", "Editor"); - const Color edge_line_color = get_theme_color("highlight_color", "Editor"); + const Color line_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); + const Color edge_line_color = get_theme_color(SNAME("highlight_color"), SNAME("Editor")); CanvasItemPlotCurve plot_func(*this, line_color, edge_line_color); plot_curve_accurate(curve, 4.f / view_size.x, plot_func); @@ -725,12 +728,12 @@ void CurveEditor::_draw() { draw_set_transform_matrix(Transform2D()); - const Color point_color = get_theme_color("font_color", "Editor"); - const Color selected_point_color = get_theme_color("accent_color", "Editor"); + const Color point_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); + const Color selected_point_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); for (int i = 0; i < curve.get_point_count(); ++i) { Vector2 pos = curve.get_point_position(i); - draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(3), i == _selected_point ? selected_point_color : point_color); + draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(3 * EDSCALE)), i == _selected_point ? selected_point_color : point_color); // TODO Circles are prettier. Needs a fix! Or a texture //draw_circle(pos, 2, point_color); } @@ -740,7 +743,7 @@ void CurveEditor::_draw() { if (_hover_point != -1) { const Color hover_color = line_color; Vector2 pos = curve.get_point_position(_hover_point); - draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(_hover_radius), hover_color, false, Math::round(EDSCALE)); + draw_rect(Rect2(get_view_pos(pos), Vector2(1, 1)).grow(Math::round(_hover_radius * EDSCALE)), hover_color, false, Math::round(EDSCALE)); } // Help text @@ -776,7 +779,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 +801,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_debugger_plugin.cpp b/editor/plugins/editor_debugger_plugin.cpp index 85114d88ae..89073b7189 100644 --- a/editor/plugins/editor_debugger_plugin.cpp +++ b/editor/plugins/editor_debugger_plugin.cpp @@ -34,18 +34,18 @@ void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug) { if (p_really_did) { - emit_signal("breaked", p_can_debug); + emit_signal(SNAME("breaked"), p_can_debug); } else { - emit_signal("continued"); + emit_signal(SNAME("continued")); } } void EditorDebuggerPlugin::_started() { - emit_signal("started"); + emit_signal(SNAME("started")); } void EditorDebuggerPlugin::_stopped() { - emit_signal("stopped"); + emit_signal(SNAME("stopped")); } void EditorDebuggerPlugin::_bind_methods() { diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index eb3c06fba1..a233d66d82 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -81,25 +81,22 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const { Ref<Texture2D> EditorTexturePreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { Ref<Image> img; Ref<AtlasTexture> atex = p_from; - Ref<LargeTexture> ltex = p_from; if (atex.is_valid()) { Ref<Texture2D> tex = atex->get_atlas(); if (!tex.is_valid()) { return Ref<Texture2D>(); } - Ref<Image> atlas = tex->get_data(); + Ref<Image> atlas = tex->get_image(); if (!atlas.is_valid()) { return Ref<Texture2D>(); } img = atlas->get_rect(atex->get_region()); - } else if (ltex.is_valid()) { - img = ltex->to_image(); } else { Ref<Texture2D> tex = p_from; if (tex.is_valid()) { - img = tex->get_data(); + img = tex->get_image(); if (img.is_valid()) { img = img->duplicate(); } @@ -177,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; @@ -222,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()) { @@ -268,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); @@ -281,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)); @@ -362,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)); @@ -375,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); @@ -490,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); @@ -525,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)); @@ -562,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) { @@ -574,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); @@ -665,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; @@ -697,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); @@ -757,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(); @@ -809,7 +832,7 @@ struct FSample { }; static FSample _samples[] = { - { "hani", U"漢語" }, + { "hani", U"漢字" }, { "armn", U"Աբ" }, { "copt", U"Αα" }, { "cyrl", U"Аб" }, @@ -858,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..22c9cc9ab1 100644 --- a/editor/plugins/font_editor_plugin.cpp +++ b/editor/plugins/font_editor_plugin.cpp @@ -34,7 +34,7 @@ void FontDataPreview::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { - Color text_color = get_theme_color("font_color", "Label"); + Color text_color = get_theme_color(SNAME("font_color"), SNAME("Label")); Color line_color = text_color; line_color.a *= 0.6; Vector2 pos = (get_size() - line->get_size()) / 2; @@ -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(); } /*************************************************************************/ @@ -127,7 +127,7 @@ FontDataPreview::FontDataPreview() { void FontDataEditor::_notification(int p_what) { if (p_what == NOTIFICATION_SORT_CHILDREN) { int split_width = get_name_split_ratio() * get_size().width; - button->set_size(Size2(get_theme_icon("Add", "EditorIcons")->get_width(), get_size().height)); + button->set_size(Size2(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))->get_width(), get_size().height)); if (is_layout_rtl()) { if (le != nullptr) { fit_child_in_rect(le, Rect2(Vector2(split_width, 0), Size2(split_width, get_size().height))); @@ -145,7 +145,7 @@ void FontDataEditor::_notification(int p_what) { } if (p_what == NOTIFICATION_DRAW) { int split_width = get_name_split_ratio() * get_size().width; - Color dark_color = get_theme_color("dark_color_2", "Editor"); + Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); if (is_layout_rtl()) { draw_rect(Rect2(Vector2(0, 0), Size2(split_width, get_size().height)), dark_color); } else { @@ -154,9 +154,9 @@ void FontDataEditor::_notification(int p_what) { } if (p_what == NOTIFICATION_THEME_CHANGED) { if (le != nullptr) { - button->set_icon(get_theme_icon("Add", "EditorIcons")); + button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); } else { - button->set_icon(get_theme_icon("Remove", "EditorIcons")); + button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); } queue_sort(); } @@ -187,12 +187,12 @@ void FontDataEditor::init_lang_add() { le->set_editable(true); add_child(le); - button->set_icon(get_theme_icon("Add", "EditorIcons")); + button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); button->connect("pressed", callable_mp(this, &FontDataEditor::add_lang)); } void FontDataEditor::init_lang_edit() { - button->set_icon(get_theme_icon("Remove", "EditorIcons")); + button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); button->connect("pressed", callable_mp(this, &FontDataEditor::remove_lang)); chk->connect("toggled", callable_mp(this, &FontDataEditor::toggle_lang)); } @@ -204,12 +204,12 @@ void FontDataEditor::init_script_add() { le->set_editable(true); add_child(le); - button->set_icon(get_theme_icon("Add", "EditorIcons")); + button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); button->connect("pressed", callable_mp(this, &FontDataEditor::add_script)); } void FontDataEditor::init_script_edit() { - button->set_icon(get_theme_icon("Remove", "EditorIcons")); + button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); button->connect("pressed", callable_mp(this, &FontDataEditor::remove_script)); chk->connect("toggled", callable_mp(this, &FontDataEditor::toggle_script)); } @@ -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..cc5793a1f9 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); @@ -329,7 +329,7 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { void GPUParticles2DEditorPlugin::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { menu->get_popup()->connect("id_pressed", callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(menu->get_theme_icon("GPUParticles2D", "EditorIcons")); + menu->set_icon(menu->get_theme_icon(SNAME("GPUParticles2D"), SNAME("EditorIcons"))); file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected)); } } diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 433a5ae51c..8576082597 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(); @@ -230,7 +230,7 @@ void GPUParticles3DEditor::_node_removed(Node *p_node) { void GPUParticles3DEditor::_notification(int p_notification) { if (p_notification == NOTIFICATION_ENTER_TREE) { - options->set_icon(options->get_popup()->get_theme_icon("GPUParticles3D", "EditorIcons")); + options->set_icon(options->get_popup()->get_theme_icon(SNAME("GPUParticles3D"), SNAME("EditorIcons"))); get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed)); } } @@ -346,7 +346,7 @@ void GPUParticles3DEditor::_generate_emission_points() { { uint8_t *iw = point_img.ptrw(); - zeromem(iw, w * h * 3 * sizeof(float)); + memset(iw, 0, w * h * 3 * sizeof(float)); const Vector3 *r = points.ptr(); float *wf = (float *)iw; for (int i = 0; i < point_count; i++) { @@ -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()); @@ -374,7 +374,7 @@ void GPUParticles3DEditor::_generate_emission_points() { { uint8_t *iw = point_img2.ptrw(); - zeromem(iw, w * h * 3 * sizeof(float)); + memset(iw, 0, w * h * 3 * sizeof(float)); const Vector3 *r = normals.ptr(); float *wf = (float *)iw; for (int i = 0; i < point_count; i++) { @@ -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..a4436525fb 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -83,13 +83,13 @@ void GPUParticlesCollisionSDFEditorPlugin::_notification(int p_what) { Color color; if (size_mb <= 16.0 + CMP_EPSILON) { // Fast. - color = bake_info->get_theme_color("success_color", "Editor"); + color = bake_info->get_theme_color(SNAME("success_color"), SNAME("Editor")); } else if (size_mb <= 64.0 + CMP_EPSILON) { // Medium. - color = bake_info->get_theme_color("warning_color", "Editor"); + color = bake_info->get_theme_color(SNAME("warning_color"), SNAME("Editor")); } else { // Slow. - color = bake_info->get_theme_color("error_color", "Editor"); + color = bake_info->get_theme_color(SNAME("error_color"), SNAME("Editor")); } bake_info->add_theme_color_override("font_color", color); @@ -131,13 +131,13 @@ void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String if (col_sdf) { Ref<Image> bake_img = col_sdf->bake(); if (bake_img.is_null()) { - EditorNode::get_singleton()->show_warning("Bake Error."); + EditorNode::get_singleton()->show_warning(TTR("Bake Error.")); return; } Ref<ConfigFile> config; - config.instance(); + config.instantiate(); if (FileAccess::exists(p_path + ".import")) { config->load(p_path + ".import"); } @@ -174,7 +174,7 @@ GPUParticlesCollisionSDFEditorPlugin::GPUParticlesCollisionSDFEditorPlugin(Edito bake_hb->hide(); bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon("Bake", "EditorIcons")); + bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake SDF")); bake->connect("pressed", callable_mp(this, &GPUParticlesCollisionSDFEditorPlugin::_bake)); bake_hb->add_child(bake); 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..d3d2de92f5 --- /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(TTR("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/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp index 1ea6630622..3207a989bd 100644 --- a/editor/plugins/item_list_editor_plugin.cpp +++ b/editor/plugins/item_list_editor_plugin.cpp @@ -243,8 +243,8 @@ void ItemListEditor::_node_removed(Node *p_node) { void ItemListEditor::_notification(int p_notification) { if (p_notification == NOTIFICATION_ENTER_TREE || p_notification == NOTIFICATION_THEME_CHANGED) { - add_button->set_icon(get_theme_icon("Add", "EditorIcons")); - del_button->set_icon(get_theme_icon("Remove", "EditorIcons")); + add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + del_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); } else if (p_notification == NOTIFICATION_READY) { get_tree()->connect("node_removed", callable_mp(this, &ItemListEditor::_node_removed)); } diff --git a/editor/plugins/baked_lightmap_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 470b61bf40..b4a70cd31d 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,22 +104,22 @@ 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); - bake->set_icon(editor->get_gui_base()->get_theme_icon("Bake", "EditorIcons")); + bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake Lightmaps")); bake->hide(); bake->connect("pressed", Callable(this, "_bake")); @@ -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..7105d351b0 100644 --- a/editor/plugins/material_editor_plugin.cpp +++ b/editor/plugins/material_editor_plugin.cpp @@ -42,22 +42,22 @@ void MaterialEditor::_notification(int p_what) { if (first_enter) { //it's in propertyeditor so.. could be moved around - light_1_switch->set_normal_texture(get_theme_icon("MaterialPreviewLight1", "EditorIcons")); - light_1_switch->set_pressed_texture(get_theme_icon("MaterialPreviewLight1Off", "EditorIcons")); - light_2_switch->set_normal_texture(get_theme_icon("MaterialPreviewLight2", "EditorIcons")); - light_2_switch->set_pressed_texture(get_theme_icon("MaterialPreviewLight2Off", "EditorIcons")); + light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); + light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); + light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); + light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); - sphere_switch->set_normal_texture(get_theme_icon("MaterialPreviewSphereOff", "EditorIcons")); - sphere_switch->set_pressed_texture(get_theme_icon("MaterialPreviewSphere", "EditorIcons")); - box_switch->set_normal_texture(get_theme_icon("MaterialPreviewCubeOff", "EditorIcons")); - box_switch->set_pressed_texture(get_theme_icon("MaterialPreviewCube", "EditorIcons")); + sphere_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewSphereOff"), SNAME("EditorIcons"))); + sphere_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewSphere"), SNAME("EditorIcons"))); + box_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewCubeOff"), SNAME("EditorIcons"))); + box_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewCube"), SNAME("EditorIcons"))); first_enter = false; } } if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon("Checkerboard", "EditorIcons"); + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); Size2 size = get_size(); draw_texture_rect(checkerboard, Rect2(Point2(), size), true); @@ -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 1e4553a967..39ab3215ff 100644 --- a/editor/plugins/mesh_editor_plugin.cpp +++ b/editor/plugins/mesh_editor_plugin.cpp @@ -33,8 +33,10 @@ #include "editor/editor_scale.h" void MeshEditor::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { rot_x -= mm->get_relative().y * 0.01; rot_y -= mm->get_relative().x * 0.01; if (rot_x < -Math_PI / 2) { @@ -53,17 +55,17 @@ void MeshEditor::_notification(int p_what) { if (first_enter) { //it's in propertyeditor so. could be moved around - light_1_switch->set_normal_texture(get_theme_icon("MaterialPreviewLight1", "EditorIcons")); - light_1_switch->set_pressed_texture(get_theme_icon("MaterialPreviewLight1Off", "EditorIcons")); - light_2_switch->set_normal_texture(get_theme_icon("MaterialPreviewLight2", "EditorIcons")); - light_2_switch->set_pressed_texture(get_theme_icon("MaterialPreviewLight2Off", "EditorIcons")); + light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); + light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); + light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); + light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); first_enter = false; } } } 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); @@ -83,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; @@ -108,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); @@ -180,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..91415f4883 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.")); @@ -432,7 +441,7 @@ MeshInstance3DEditor::MeshInstance3DEditor() { Node3DEditor::get_singleton()->add_control_to_menu_panel(options); options->set_text(TTR("Mesh")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("MeshInstance3D", "EditorIcons")); + options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MeshInstance3D"), SNAME("EditorIcons"))); options->get_popup()->add_item(TTR("Create Trimesh Static Body"), MENU_OPTION_CREATE_STATIC_TRIMESH_BODY); options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a StaticBody3D and assigns a polygon-based collision shape to it automatically.\nThis is the most accurate (but slowest) option for collision detection.")); @@ -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 f8932cd534..15b6a0f3a0 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -93,7 +93,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, mesh = mesh->duplicate(); for (int j = 0; j < mesh->get_surface_count(); ++j) { - Ref<Material> mat = mi->get_surface_material(j); + Ref<Material> mat = mi->get_surface_override_material(j); if (mat.is_valid()) { mesh->surface_set_material(j, mat); @@ -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 + "'."); @@ -254,7 +254,7 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { Node3DEditor::get_singleton()->add_control_to_menu_panel(menu); menu->set_position(Point2(1, 1)); menu->set_text(TTR("Mesh Library")); - menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("MeshLibrary", "EditorIcons")); + menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MeshLibrary"), SNAME("EditorIcons"))); menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM); menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM); menu->get_popup()->add_separator(); diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp index 19c6dcf402..5514bccabb 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; @@ -268,7 +268,7 @@ MultiMeshEditor::MultiMeshEditor() { Node3DEditor::get_singleton()->add_control_to_menu_panel(options); options->set_text("MultiMesh"); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("MultiMeshInstance3D", "EditorIcons")); + options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MultiMeshInstance3D"), SNAME("EditorIcons"))); options->get_popup()->add_item(TTR("Populate Surface")); options->get_popup()->connect("id_pressed", callable_mp(this, &MultiMeshEditor::_menu_option)); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 610ef0c601..7290a39463 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" @@ -48,6 +49,7 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" #include "scene/3d/visual_instance_3d.h" +#include "scene/gui/center_container.h" #include "scene/gui/subviewport_container.h" #include "scene/resources/packed_scene.h" #include "scene/resources/surface_tool.h" @@ -84,15 +86,15 @@ 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")); - axis_colors.push_back(get_theme_color("axis_y_color", "Editor")); - axis_colors.push_back(get_theme_color("axis_z_color", "Editor")); + axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); update(); if (!is_connected("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited))) { @@ -141,7 +143,7 @@ void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) { if (front) { String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z"); draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c); - draw_char(get_theme_font("rotation_control", "EditorFonts"), p_axis.screen_point + Vector2i(-4, 5) * EDSCALE, axis_name, "", get_theme_font_size("rotation_control_size", "EditorFonts"), Color(0.3, 0.3, 0.3)); + draw_char(get_theme_font(SNAME("rotation_control"), SNAME("EditorFonts")), p_axis.screen_point + Vector2i(-4, 5) * EDSCALE, axis_name, "", get_theme_font_size(SNAME("rotation_control_size"), SNAME("EditorFonts")), Color(0.3, 0.3, 0.3)); } else { draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS * (0.55 + (0.2 * (1.0 + p_axis.z_axis))), c); } @@ -184,8 +186,10 @@ void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) { } void ViewportRotationControl::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + const Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { Vector2 pos = mb->get_position(); if (mb->is_pressed()) { if (pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) { @@ -306,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); @@ -341,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) { @@ -358,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); @@ -407,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(); } @@ -463,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) { @@ -535,8 +548,8 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b if (dist < closest_dist) { item = Object::cast_to<Node>(spat); - while (item->get_owner() && item->get_owner() != edited_scene && !edited_scene->is_editable_instance(item->get_owner())) { - item = item->get_owner(); + if (item != edited_scene) { + item = edited_scene->get_deepest_editable_node(item); } closest = item->get_instance_id(); @@ -558,7 +571,7 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b return closest; } -void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select) { +void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select, bool p_include_locked_nodes) { Vector3 ray = _get_ray(p_pos); Vector3 pos = _get_ray_pos(p_pos); @@ -601,6 +614,10 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_inclu continue; } + if (!p_include_locked_nodes && _is_node_locked(spat)) { + continue; + } + if (editor_selection->is_selected(spat)) { r_includes_current = true; } @@ -628,7 +645,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); @@ -697,8 +714,8 @@ void Node3DEditorViewport::_select_region() { } Node *item = Object::cast_to<Node>(sp); - while (item->get_owner() && item->get_owner() != edited_scene && !edited_scene->is_editable_instance(item->get_owner())) { - item = item->get_owner(); + if (item != edited_scene) { + item = edited_scene->get_deepest_editable_node(item); } // Replace the node by the group if grouped @@ -791,22 +808,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; @@ -826,7 +843,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) { @@ -1021,27 +1038,27 @@ 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(), spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); Node *scene = editor->get_edited_scene(); for (int i = 0; i < selection_results.size(); i++) { Node3D *item = selection_results[i].item; - if (item != scene && item->get_owner() != scene && !scene->is_editable_instance(item->get_owner())) { + if (item != scene && item->get_owner() != scene && item != scene->get_deepest_editable_node(item)) { //invalid result selection_results.remove(i); i--; } } - 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(); selection_results.clear(); if (clicked.is_valid()) { - _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() != Node3DEditor::TOOL_MODE_LIST_SELECT); + _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); clicked = ObjectID(); } @@ -1118,27 +1135,25 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { - emit_signal("clicked", this); + emit_signal(SNAME("clicked"), this); float zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor(); switch (b->get_button_index()) { - case BUTTON_WHEEL_UP: { + case MOUSE_BUTTON_WHEEL_UP: { if (is_freelook_active()) { scale_freelook_speed(zoom_factor); } else { scale_cursor_distance(1.0 / zoom_factor); } } break; - - case BUTTON_WHEEL_DOWN: { + case MOUSE_BUTTON_WHEEL_DOWN: { if (is_freelook_active()) { scale_freelook_speed(1.0 / zoom_factor); } else { scale_cursor_distance(zoom_factor); } } break; - - case BUTTON_RIGHT: { + case MOUSE_BUTTON_RIGHT: { NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); if (b->is_pressed() && _edit.gizmo.is_valid()) { @@ -1148,7 +1163,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; } @@ -1199,7 +1214,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } break; - case BUTTON_MIDDLE: { + case MOUSE_BUTTON_MIDDLE: { if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) { switch (_edit.plane) { case TRANSFORM_VIEW: { @@ -1230,10 +1245,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } } break; - case BUTTON_LEFT: { + 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; } @@ -1243,6 +1258,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; @@ -1261,7 +1277,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; @@ -1278,7 +1294,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_control()) || 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 @@ -1313,11 +1329,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) { @@ -1395,6 +1411,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } break; + default: + break; } } @@ -1439,17 +1457,18 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle); set_message(n + ": " + String(v)); - } else if (m->get_button_mask() & BUTTON_MASK_LEFT) { - if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) { + } else if (m->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + 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()) { + const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE; + if (clicked.is_valid() && movement_threshold_passed) { if (!clicked_includes_current) { _select_clicked(clicked_wants_append, true); // Processing was deferred. @@ -1473,7 +1492,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector3 ray_pos = _get_ray_pos(m->get_position()); Vector3 ray = _get_ray(m->get_position()); - float snap = EDITOR_GET("interface/inspector/default_float_step"); + double snap = EDITOR_GET("interface/inspector/default_float_step"); int snap_step_decimals = Math::range_step_decimals(snap); switch (_edit.mode) { @@ -1578,10 +1597,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) { @@ -1607,7 +1626,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)); @@ -1700,8 +1719,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()) { @@ -1767,7 +1786,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector3 y_axis = (click - _edit.center).normalized(); Vector3 x_axis = plane.normal.cross(y_axis).normalized(); - float angle = Math::atan2(x_axis.dot(intersection - _edit.center), y_axis.dot(intersection - _edit.center)); + double angle = Math::atan2(x_axis.dot(intersection - _edit.center), y_axis.dot(intersection - _edit.center)); if (_edit.snap || spatial_editor->is_snap_enabled()) { snap = spatial_editor->get_rotate_snap(); @@ -1796,10 +1815,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; @@ -1810,9 +1829,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; @@ -1829,9 +1848,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } } - - } else if ((m->get_button_mask() & BUTTON_MASK_RIGHT) || freelook_active) { - if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) { + } else if ((m->get_button_mask() & MOUSE_BUTTON_MASK_RIGHT) || freelook_active) { + if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) { nav_mode = NAVIGATION_ZOOM; } else if (freelook_active) { nav_mode = NAVIGATION_LOOK; @@ -1839,10 +1857,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { nav_mode = NAVIGATION_PAN; } - } else if (m->get_button_mask() & BUTTON_MASK_MIDDLE) { + } else if (m->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) { + const int mod = _get_key_modifier(m); if (nav_scheme == NAVIGATION_GODOT) { - const int mod = _get_key_modifier(m); - if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) { nav_mode = NAVIGATION_PAN; } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) { @@ -1851,13 +1868,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { // Always allow Alt as a modifier to better support graphic tablets. nav_mode = NAVIGATION_ORBIT; } - } else if (nav_scheme == NAVIGATION_MAYA) { - if (m->get_alt()) { + if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) { nav_mode = NAVIGATION_PAN; } } - } else if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_3_button_mouse")) { // Handle trackpad (no external mouse) use case const int mod = _get_key_modifier(m); @@ -1927,7 +1942,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; } } @@ -1965,6 +1980,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; @@ -2023,7 +2045,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { continue; } - spatial_editor->emit_signal("transform_key_request", sp, "", sp->get_transform()); + spatial_editor->emit_signal(SNAME("transform_key_request"), sp, "", sp->get_transform()); } set_message(TTR("Animation Key Inserted.")); @@ -2039,7 +2061,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (k->get_keycode() == KEY_SPACE) { if (!k->is_pressed()) { - emit_signal("toggle_maximize_view", this); + emit_signal(SNAME("toggle_maximize_view"), this); } } } @@ -2056,11 +2078,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); @@ -2081,7 +2103,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; } @@ -2148,7 +2170,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; @@ -2161,7 +2183,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; @@ -2218,6 +2240,12 @@ void Node3DEditorViewport::scale_cursor_distance(real_t scale) { cursor.distance = CLAMP(cursor.distance * scale, min_distance, max_distance); } + if (cursor.distance == max_distance || cursor.distance == min_distance) { + zoom_failed_attempts_count++; + } else { + zoom_failed_attempts_count = 0; + } + zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S; surface->update(); } @@ -2365,6 +2393,9 @@ void Node3DEditorViewport::_project_settings_changed() { viewport->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode)); const bool use_debanding = GLOBAL_GET("rendering/anti_aliasing/quality/use_debanding"); viewport->set_use_debanding(use_debanding); + + const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"); + viewport->set_use_occlusion_culling(use_occlusion_culling); } void Node3DEditorViewport::_notification(int p_what) { @@ -2384,12 +2415,12 @@ void Node3DEditorViewport::_notification(int p_what) { } else { set_freelook_active(false); } - call_deferred("update_transform_gizmo_view"); + call_deferred(SNAME("update_transform_gizmo_view")); rotation_control->set_visible(EditorSettings::get_singleton()->get("editors/3d/navigation/show_viewport_rotation_gizmo")); } if (p_what == NOTIFICATION_RESIZED) { - call_deferred("update_transform_gizmo_view"); + call_deferred(SNAME("update_transform_gizmo_view")); } if (p_what == NOTIFICATION_PROCESS) { @@ -2399,6 +2430,7 @@ void Node3DEditorViewport::_notification(int p_what) { zoom_indicator_delay -= delta; if (zoom_indicator_delay <= 0) { surface->update(); + zoom_limit_label->hide(); } } @@ -2406,7 +2438,7 @@ void Node3DEditorViewport::_notification(int p_what) { Node *scene_root = editor->get_scene_tree_dock()->get_editor_data()->get_edited_scene_root(); if (previewing_cinema && scene_root != nullptr) { - Camera3D *cam = scene_root->get_viewport()->get_camera(); + Camera3D *cam = scene_root->get_viewport()->get_camera_3d(); if (cam != nullptr && cam != previewing) { //then switch the viewport's camera to the scene's viewport camera if (previewing != nullptr) { @@ -2437,7 +2469,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); @@ -2492,25 +2524,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); } @@ -2538,6 +2566,8 @@ void Node3DEditorViewport::_notification(int p_what) { cpu_time += cpu_time_history[i]; } cpu_time /= FRAME_TIME_HISTORY; + // Prevent unrealistically low values. + cpu_time = MAX(0.01, cpu_time); gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid()); gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY; @@ -2546,16 +2576,19 @@ void Node3DEditorViewport::_notification(int p_what) { gpu_time += gpu_time_history[i]; } gpu_time /= FRAME_TIME_HISTORY; + // Prevent division by zero for the FPS counter (and unrealistically low values). + // This limits the reported FPS to 100000. + gpu_time = MAX(0.01, gpu_time); // Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red). // Middle point is at 15 ms. - cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), String::num(cpu_time, 1))); + cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(1))); cpu_time_label->add_theme_color_override( "font_color", frame_time_gradient->get_color_at_offset( Math::range_lerp(cpu_time, 0, 30, 0, 1))); - gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), String::num(gpu_time, 1))); + gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(1))); // Middle point is at 15 ms. gpu_time_label->add_theme_color_override( "font_color", @@ -2600,31 +2633,31 @@ void Node3DEditorViewport::_notification(int p_what) { } if (p_what == NOTIFICATION_THEME_CHANGED) { - view_menu->set_icon(get_theme_icon("GuiTabMenuHl", "EditorIcons")); - preview_camera->set_icon(get_theme_icon("Camera3D", "EditorIcons")); + view_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + preview_camera->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); - view_menu->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - view_menu->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - view_menu->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - view_menu->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - view_menu->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); + view_menu->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - preview_camera->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - preview_camera->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - preview_camera->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - preview_camera->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); + preview_camera->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - frame_time_gradient->set_color(0, get_theme_color("success_color", "Editor")); - frame_time_gradient->set_color(1, get_theme_color("warning_color", "Editor")); - frame_time_gradient->set_color(2, get_theme_color("error_color", "Editor")); + frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color"), SNAME("Editor"))); + frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), SNAME("Editor"))); - info_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - cpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - gpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - fps_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - cinema_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); - locked_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("Information3dViewport", "EditorStyles")); + info_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + cpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + gpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + fps_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + cinema_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + locked_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); } } @@ -2665,7 +2698,7 @@ void Node3DEditorViewport::_draw() { if (surface->has_focus()) { Size2 size = surface->get_size(); Rect2 r = Rect2(Point2(), size); - get_theme_stylebox("Focus", "EditorStyles")->draw(surface->get_canvas_item(), r); + get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles"))->draw(surface->get_canvas_item(), r); } if (cursor.region_select) { @@ -2673,11 +2706,11 @@ void Node3DEditorViewport::_draw() { surface->draw_rect( selection_rect, - get_theme_color("box_selection_fill_color", "Editor")); + get_theme_color(SNAME("box_selection_fill_color"), SNAME("Editor"))); surface->draw_rect( selection_rect, - get_theme_color("box_selection_stroke_color", "Editor"), + get_theme_color(SNAME("box_selection_stroke_color"), SNAME("Editor")), false, Math::round(EDSCALE)); } @@ -2685,8 +2718,8 @@ void Node3DEditorViewport::_draw() { RID ci = surface->get_canvas_item(); if (message_time > 0) { - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); Point2 msgpos = Point2(5, get_size().y - 20); font->draw_string(ci, msgpos + Point2(1, 1), message, HALIGN_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); font->draw_string(ci, msgpos + Point2(-1, -1), message, HALIGN_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); @@ -2699,16 +2732,16 @@ void Node3DEditorViewport::_draw() { Color handle_color; switch (_edit.plane) { case TRANSFORM_X_AXIS: - handle_color = get_theme_color("axis_x_color", "Editor"); + handle_color = get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); break; case TRANSFORM_Y_AXIS: - handle_color = get_theme_color("axis_y_color", "Editor"); + handle_color = get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); break; case TRANSFORM_Z_AXIS: - handle_color = get_theme_color("axis_z_color", "Editor"); + handle_color = get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); break; default: - handle_color = get_theme_color("accent_color", "Editor"); + handle_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); break; } handle_color.a = 1.0; @@ -2765,14 +2798,15 @@ void Node3DEditorViewport::_draw() { draw_indicator_bar( *surface, 1.0 - logscale_t, - get_theme_icon("ViewportSpeed", "EditorIcons"), - get_theme_font("font", "Label"), - get_theme_font_size("font_size", "Label"), + get_theme_icon(SNAME("ViewportSpeed"), SNAME("EditorIcons")), + get_theme_font(SNAME("font"), SNAME("Label")), + get_theme_font_size(SNAME("font_size"), SNAME("Label")), vformat("%s u/s", String::num(freelook_speed).pad_decimals(precision))); } } else { // Show zoom + zoom_limit_label->set_visible(zoom_failed_attempts_count > 15); real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN); real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX); @@ -2786,9 +2820,9 @@ void Node3DEditorViewport::_draw() { draw_indicator_bar( *surface, logscale_t, - get_theme_icon("ViewportZoom", "EditorIcons"), - get_theme_font("font", "Label"), - get_theme_font_size("font_size", "Label"), + get_theme_icon(SNAME("ViewportZoom"), SNAME("EditorIcons")), + get_theme_font(SNAME("font"), SNAME("Label")), + get_theme_font_size(SNAME("font_size"), SNAME("Label")), vformat("%s u", String::num(cursor.distance).pad_decimals(precision))); } } @@ -2865,7 +2899,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(); @@ -2882,7 +2916,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()); @@ -2902,7 +2936,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(); @@ -2942,7 +2976,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), false); orthogonal = false; auto_orthogonal = false; - call_deferred("update_transform_gizmo_view"); + call_deferred(SNAME("update_transform_gizmo_view")); _update_name(); } break; @@ -2951,7 +2985,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), true); orthogonal = true; auto_orthogonal = false; - call_deferred("update_transform_gizmo_view"); + call_deferred(SNAME("update_transform_gizmo_view")); _update_name(); } break; @@ -3047,9 +3081,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: @@ -3061,7 +3095,8 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS: case VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS: case VIEW_DISPLAY_DEBUG_CLUSTER_DECALS: - case VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES: { + case VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES: + case VIEW_DISPLAY_DEBUG_OCCLUDERS: { static const int display_options[] = { VIEW_DISPLAY_NORMAL, VIEW_DISPLAY_WIREFRAME, @@ -3072,9 +3107,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, @@ -3087,6 +3122,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, + VIEW_DISPLAY_DEBUG_OCCLUDERS, VIEW_MAX }; static const Viewport::DebugDraw debug_draw_modes[] = { @@ -3099,9 +3135,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, @@ -3114,6 +3150,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { Viewport::DEBUG_DRAW_CLUSTER_SPOT_LIGHTS, Viewport::DEBUG_DRAW_CLUSTER_DECALS, Viewport::DEBUG_DRAW_CLUSTER_REFLECTION_PROBES, + Viewport::DEBUG_DRAW_OCCLUDERS, }; int idx = 0; @@ -3163,6 +3200,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_set_visible(move_gizmo_instance[i], false); RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); move_plane_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid()); @@ -3170,6 +3208,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false); RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); rotate_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid()); @@ -3177,6 +3216,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false); RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); scale_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid()); @@ -3184,6 +3224,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false); RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); scale_plane_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid()); @@ -3191,6 +3232,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false); RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); } // Rotation white outline @@ -3200,6 +3242,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false); RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[3], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[3], layer); + RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); } void Node3DEditorViewport::_finish_gizmo_instances() { @@ -3227,14 +3270,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(); } } @@ -3269,7 +3310,7 @@ void Node3DEditorViewport::_selection_result_pressed(int p_result) { clicked = selection_results[p_result].item->get_instance_id(); if (clicked.is_valid()) { - _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() != Node3DEditor::TOOL_MODE_LIST_SELECT); + _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); clicked = ObjectID(); } } @@ -3293,11 +3334,11 @@ 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) { + if (xform.origin.is_equal_approx(camera_xform.origin)) { for (int i = 0; i < 3; i++) { RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false); RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false); @@ -3481,7 +3522,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(); @@ -3529,8 +3569,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"))); @@ -3550,10 +3590,6 @@ void Node3DEditorViewport::reset() { } void Node3DEditorViewport::focus_selection() { - if (!get_selected_count()) { - return; - } - Vector3 center; int count = 0; @@ -3650,9 +3686,9 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_exclude_top_level_transform) { AABB bounds; - const MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_parent); - if (mesh_instance) { - bounds = mesh_instance->get_aabb(); + const VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(p_parent); + if (visual_instance) { + bounds = visual_instance->get_aabb(); } for (int i = 0; i < p_parent->get_child_count(); i++) { @@ -3693,7 +3729,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); } @@ -3738,51 +3774,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(); @@ -3791,7 +3827,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; @@ -3834,7 +3870,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; @@ -3856,11 +3892,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()) { @@ -3869,24 +3905,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) { @@ -4018,9 +4054,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); @@ -4037,6 +4073,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito display_submenu->add_radio_check_item(TTR("Spot Light Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS); display_submenu->add_radio_check_item(TTR("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS); display_submenu->add_radio_check_item(TTR("Reflection Probe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES); + display_submenu->add_radio_check_item(TTR("Occlusion Culling Buffer"), VIEW_DISPLAY_DEBUG_OCCLUDERS); display_submenu->set_name("display_advanced"); view_menu->get_popup()->add_submenu_item(TTR("Display Advanced..."), "display_advanced", VIEW_DISPLAY_ADVANCED); @@ -4135,6 +4172,15 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito locked_label->set_text(TTR("View Rotation Locked")); locked_label->hide(); + zoom_limit_label = memnew(Label); + zoom_limit_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT); + zoom_limit_label->set_offset(Side::SIDE_TOP, -28 * EDSCALE); + zoom_limit_label->set_text(TTR("To zoom further, change the camera's clipping planes (View -> Settings...)")); + zoom_limit_label->set_name("ZoomLimitMessageLabel"); + zoom_limit_label->add_theme_color_override("font_color", Color(1, 1, 1, 1)); + zoom_limit_label->hide(); + surface->add_child(zoom_limit_label); + frame_time_gradient = memnew(Gradient); // The color is set when the theme changes. frame_time_gradient->add_point(0.5, Color()); @@ -4197,14 +4243,16 @@ Node3DEditorViewport::~Node3DEditorViewport() { ////////////////////////////////////////////////////////////// void Node3DEditorViewportContainer::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { Vector2 size = get_size(); - int h_sep = get_theme_constant("separation", "HSplitContainer"); - int v_sep = get_theme_constant("separation", "VSplitContainer"); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); int mid_w = size.width * ratio_h; int mid_h = size.height * ratio_v; @@ -4249,8 +4297,8 @@ void Node3DEditorViewportContainer::_gui_input(const Ref<InputEvent> &p_event) { if (view == VIEW_USE_3_VIEWPORTS || view == VIEW_USE_3_VIEWPORTS_ALT || view == VIEW_USE_4_VIEWPORTS) { Vector2 size = get_size(); - int h_sep = get_theme_constant("separation", "HSplitContainer"); - int v_sep = get_theme_constant("separation", "VSplitContainer"); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); int mid_w = size.width * ratio_h; int mid_h = size.height * ratio_v; @@ -4289,18 +4337,18 @@ void Node3DEditorViewportContainer::_notification(int p_what) { } if (p_what == NOTIFICATION_DRAW && mouseover) { - Ref<Texture2D> h_grabber = get_theme_icon("grabber", "HSplitContainer"); - Ref<Texture2D> v_grabber = get_theme_icon("grabber", "VSplitContainer"); + Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer")); + Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer")); - Ref<Texture2D> hdiag_grabber = get_theme_icon("GuiViewportHdiagsplitter", "EditorIcons"); - Ref<Texture2D> vdiag_grabber = get_theme_icon("GuiViewportVdiagsplitter", "EditorIcons"); - Ref<Texture2D> vh_grabber = get_theme_icon("GuiViewportVhsplitter", "EditorIcons"); + Ref<Texture2D> hdiag_grabber = get_theme_icon(SNAME("GuiViewportHdiagsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> vdiag_grabber = get_theme_icon(SNAME("GuiViewportVdiagsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> vh_grabber = get_theme_icon(SNAME("GuiViewportVhsplitter"), SNAME("EditorIcons")); Vector2 size = get_size(); - int h_sep = get_theme_constant("separation", "HSplitContainer"); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); - int v_sep = get_theme_constant("separation", "VSplitContainer"); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); int mid_w = size.width * ratio_h; int mid_h = size.height * ratio_v; @@ -4386,9 +4434,9 @@ void Node3DEditorViewportContainer::_notification(int p_what) { } return; } - int h_sep = get_theme_constant("separation", "HSplitContainer"); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); - int v_sep = get_theme_constant("separation", "VSplitContainer"); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); int mid_w = size.width * ratio_h; int mid_h = size.height * ratio_v; @@ -4544,7 +4592,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; @@ -4582,7 +4630,12 @@ void _update_all_gizmos(Node *p_node) { void Node3DEditor::update_all_gizmos(Node *p_node) { if (!p_node) { - p_node = SceneTree::get_singleton()->get_root(); + if (SceneTree::get_singleton()) { + p_node = SceneTree::get_singleton()->get_root(); + } else { + // No scene tree, so nothing to update. + return; + } } _update_all_gizmos(p_node); } @@ -4603,6 +4656,7 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { si->sbox_instance, RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2( selection_box_xray->get_rid(), sp->get_world_3d()->get_scenario()); @@ -4610,6 +4664,7 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { si->sbox_instance_xray, RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::MISC_TOOL_LAYER); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); return si; } @@ -4712,11 +4767,33 @@ Dictionary Node3DEditor::get_state() const { continue; } int state = gizmos_menu->get_item_state(gizmos_menu->get_item_index(i)); - String name = gizmo_plugins_by_name[i]->get_name(); + String name = gizmo_plugins_by_name[i]->get_gizmo_name(); gizmos_status[name] = state; } d["gizmos_status"] = gizmos_status; + { + Dictionary pd; + + pd["sun_rotation"] = sun_rotation; + + pd["environ_sky_color"] = environ_sky_color->get_pick_color(); + pd["environ_ground_color"] = environ_ground_color->get_pick_color(); + pd["environ_energy"] = environ_energy->get_value(); + pd["environ_glow_enabled"] = environ_glow_button->is_pressed(); + pd["environ_tonemap_enabled"] = environ_tonemap_button->is_pressed(); + pd["environ_ao_enabled"] = environ_ao_button->is_pressed(); + pd["environ_gi_enabled"] = environ_gi_button->is_pressed(); + pd["sun_max_distance"] = sun_max_distance->get_value(); + + pd["sun_color"] = sun_color->get_pick_color(); + pd["sun_energy"] = sun_energy->get_value(); + + pd["sun_disabled"] = sun_button->is_pressed(); + pd["environ_disabled"] = environ_button->is_pressed(); + + d["preview_sun_env"] = pd; + } return d; } @@ -4816,7 +4893,7 @@ void Node3DEditor::set_state(const Dictionary &p_state) { } int state = EditorNode3DGizmoPlugin::VISIBLE; for (int i = 0; i < keys.size(); i++) { - if (gizmo_plugins_by_name.write[j]->get_name() == String(keys[i])) { + if (gizmo_plugins_by_name.write[j]->get_gizmo_name() == String(keys[i])) { state = gizmos_status[keys[i]]; break; } @@ -4826,6 +4903,38 @@ void Node3DEditor::set_state(const Dictionary &p_state) { } _update_gizmos_menu(); } + + if (d.has("preview_sun_env")) { + sun_environ_updating = true; + Dictionary pd = d["preview_sun_env"]; + sun_rotation = pd["sun_rotation"]; + + environ_sky_color->set_pick_color(pd["environ_sky_color"]); + environ_ground_color->set_pick_color(pd["environ_ground_color"]); + environ_energy->set_value(pd["environ_energy"]); + environ_glow_button->set_pressed(pd["environ_glow_enabled"]); + environ_tonemap_button->set_pressed(pd["environ_tonemap_enabled"]); + environ_ao_button->set_pressed(pd["environ_ao_enabled"]); + environ_gi_button->set_pressed(pd["environ_gi_enabled"]); + sun_max_distance->set_value(pd["sun_max_distance"]); + + sun_color->set_pick_color(pd["sun_color"]); + sun_energy->set_value(pd["sun_energy"]); + + sun_button->set_pressed(pd["sun_disabled"]); + environ_button->set_pressed(pd["environ_disabled"]); + + sun_environ_updating = false; + + _preview_settings_changed(); + _update_preview_environment(); + } else { + _load_default_preview_settings(); + sun_button->set_pressed(false); + environ_button->set_pressed(false); + _preview_settings_changed(); + _update_preview_environment(); + } } void Node3DEditor::edit(Node3D *p_spatial) { @@ -4864,7 +4973,7 @@ void Node3DEditor::_snap_update() { } void Node3DEditor::_xform_dialog_action() { - Transform t; + Transform3D t; //translation Vector3 scale; Vector3 rotate; @@ -4897,7 +5006,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 { @@ -4945,13 +5054,13 @@ void Node3DEditor::_menu_gizmo_toggled(int p_option) { const int state = gizmos_menu->get_item_state(idx); switch (state) { case EditorNode3DGizmoPlugin::VISIBLE: - gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_theme_icon("visibility_visible")); + gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_theme_icon(SNAME("visibility_visible"))); break; case EditorNode3DGizmoPlugin::ON_TOP: - gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_theme_icon("visibility_xray")); + gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_theme_icon(SNAME("visibility_xray"))); break; case EditorNode3DGizmoPlugin::HIDDEN: - gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_theme_icon("visibility_hidden")); + gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_theme_icon(SNAME("visibility_hidden"))); break; } @@ -4965,11 +5074,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.")); } } @@ -5223,7 +5332,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); @@ -5238,13 +5347,13 @@ void Node3DEditor::_init_indicators() { Color origin_color; switch (i) { case 0: - origin_color = get_theme_color("axis_x_color", "Editor"); + origin_color = get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); break; case 1: - origin_color = get_theme_color("axis_y_color", "Editor"); + origin_color = get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); break; case 2: - origin_color = get_theme_color("axis_z_color", "Editor"); + origin_color = get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); break; default: origin_color = Color(); @@ -5272,38 +5381,40 @@ void Node3DEditor::_init_indicators() { } Ref<Shader> grid_shader = memnew(Shader); - grid_shader->set_code( - "\n" - "shader_type spatial; \n" - "render_mode unshaded; \n" - "uniform bool orthogonal; \n" - "uniform float grid_size; \n" - "\n" - "void vertex() { \n" - " // From FLAG_SRGB_VERTEX_COLOR \n" - " if (!OUTPUT_IS_SRGB) { \n" - " COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045))); \n" - " } \n" - "} \n" - "\n" - "void fragment() { \n" - " ALBEDO = COLOR.rgb; \n" - " vec3 dir = orthogonal ? -vec3(0, 0, 1) : VIEW; \n" - " float angle_fade = abs(dot(dir, NORMAL)); \n" - " angle_fade = smoothstep(0.05, 0.2, angle_fade); \n" - " \n" - " vec3 world_pos = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz; \n" - " vec3 world_normal = (CAMERA_MATRIX * vec4(NORMAL, 0.0)).xyz; \n" - " vec3 camera_world_pos = CAMERA_MATRIX[3].xyz; \n" - " vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal); \n" - " float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size); \n" - " dist_fade = smoothstep(0.02, 0.3, dist_fade); \n" - " \n" - " ALPHA = COLOR.a * dist_fade * angle_fade; \n" - "}"); + grid_shader->set_code(R"( +shader_type spatial; + +render_mode unshaded; + +uniform bool orthogonal; +uniform float grid_size; + +void vertex() { + // From FLAG_SRGB_VERTEX_COLOR. + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045))); + } +} + +void fragment() { + ALBEDO = COLOR.rgb; + vec3 dir = orthogonal ? -vec3(0, 0, 1) : VIEW; + float angle_fade = abs(dot(dir, NORMAL)); + angle_fade = smoothstep(0.05, 0.2, angle_fade); + + vec3 world_pos = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz; + vec3 world_normal = (CAMERA_MATRIX * vec4(NORMAL, 0.0)).xyz; + vec3 camera_world_pos = CAMERA_MATRIX[3].xyz; + vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal); + float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size); + dist_fade = smoothstep(0.02, 0.3, dist_fade); + + ALPHA = COLOR.a * dist_fade * angle_fade; +} +)"); for (int i = 0; i < 3; i++) { - grid_mat[i].instance(); + grid_mat[i].instantiate(); grid_mat[i]->set_shader(grid_shader); } @@ -5327,6 +5438,7 @@ void Node3DEditor::_init_indicators() { origin_instance = RenderingServer::get_singleton()->instance_create2(origin, get_tree()->get_root()->get_world_3d()->get_scenario()); RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); + RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF); } @@ -5338,13 +5450,13 @@ void Node3DEditor::_init_indicators() { Color col; switch (i) { case 0: - col = get_theme_color("axis_x_color", "Editor"); + col = get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); break; case 1: - col = get_theme_color("axis_y_color", "Editor"); + col = get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); break; case 2: - col = get_theme_color("axis_z_color", "Editor"); + col = get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); break; default: col = Color(); @@ -5511,33 +5623,35 @@ void Node3DEditor::_init_indicators() { Ref<Shader> rotate_shader = memnew(Shader); - rotate_shader->set_code( - "\n" - "shader_type spatial; \n" - "render_mode unshaded, depth_test_disabled; \n" - "uniform vec4 albedo; \n" - "\n" - "mat3 orthonormalize(mat3 m) { \n" - " vec3 x = normalize(m[0]); \n" - " vec3 y = normalize(m[1] - x * dot(x, m[1])); \n" - " vec3 z = m[2] - x * dot(x, m[2]); \n" - " z = normalize(z - y * (dot(y,m[2]))); \n" - " return mat3(x,y,z); \n" - "} \n" - "\n" - "void vertex() { \n" - " mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX)); \n" - " vec3 n = mv * VERTEX; \n" - " float orientation = dot(vec3(0,0,-1),n); \n" - " if (orientation <= 0.005) { \n" - " VERTEX += NORMAL*0.02; \n" - " } \n" - "} \n" - "\n" - "void fragment() { \n" - " ALBEDO = albedo.rgb; \n" - " ALPHA = albedo.a; \n" - "}"); + rotate_shader->set_code(R"( +shader_type spatial; + +render_mode unshaded, depth_test_disabled; + +uniform vec4 albedo; + +mat3 orthonormalize(mat3 m) { + vec3 x = normalize(m[0]); + vec3 y = normalize(m[1] - x * dot(x, m[1])); + vec3 z = m[2] - x * dot(x, m[2]); + z = normalize(z - y * (dot(y,m[2]))); + return mat3(x,y,z); +} + +void vertex() { + mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX)); + vec3 n = mv * VERTEX; + float orientation = dot(vec3(0, 0, -1), n); + if (orientation <= 0.005) { + VERTEX += NORMAL * 0.02; + } +} + +void fragment() { + ALBEDO = albedo.rgb; + ALPHA = albedo.a; +} +)"); Ref<ShaderMaterial> rotate_mat = memnew(ShaderMaterial); rotate_mat->set_render_priority(Material::RENDER_PRIORITY_MAX); @@ -5557,34 +5671,36 @@ void Node3DEditor::_init_indicators() { Ref<ShaderMaterial> border_mat = rotate_mat->duplicate(); Ref<Shader> border_shader = memnew(Shader); - border_shader->set_code( - "\n" - "shader_type spatial; \n" - "render_mode unshaded, depth_test_disabled; \n" - "uniform vec4 albedo; \n" - "\n" - "mat3 orthonormalize(mat3 m) { \n" - " vec3 x = normalize(m[0]); \n" - " vec3 y = normalize(m[1] - x * dot(x, m[1])); \n" - " vec3 z = m[2] - x * dot(x, m[2]); \n" - " z = normalize(z - y * (dot(y,m[2]))); \n" - " return mat3(x,y,z); \n" - "} \n" - "\n" - "void vertex() { \n" - " mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX)); \n" - " mv = inverse(mv); \n" - " VERTEX += NORMAL*0.008; \n" - " vec3 camera_dir_local = mv * vec3(0,0,1); \n" - " vec3 camera_up_local = mv * vec3(0,1,0); \n" - " mat3 rotation_matrix = mat3(cross(camera_dir_local, camera_up_local), camera_up_local, camera_dir_local); \n" - " VERTEX = rotation_matrix * VERTEX; \n" - "} \n" - "\n" - "void fragment() { \n" - " ALBEDO = albedo.rgb; \n" - " ALPHA = albedo.a; \n" - "}"); + border_shader->set_code(R"( +shader_type spatial; + +render_mode unshaded, depth_test_disabled; + +uniform vec4 albedo; + +mat3 orthonormalize(mat3 m) { + vec3 x = normalize(m[0]); + vec3 y = normalize(m[1] - x * dot(x, m[1])); + vec3 z = m[2] - x * dot(x, m[2]); + z = normalize(z - y * (dot(y,m[2]))); + return mat3(x,y,z); +} + +void vertex() { + mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX)); + mv = inverse(mv); + VERTEX += NORMAL*0.008; + vec3 camera_dir_local = mv * vec3(0,0,1); + vec3 camera_up_local = mv * vec3(0,1,0); + mat3 rotation_matrix = mat3(cross(camera_dir_local, camera_up_local), camera_up_local, camera_dir_local); + VERTEX = rotation_matrix * VERTEX; +} + +void fragment() { + ALBEDO = albedo.rgb; + ALPHA = albedo.a; +} +)"); border_mat->set_shader(border_shader); border_mat->set_shader_param("albedo", Color(0.75, 0.75, 0.75, col.a / 3.0)); @@ -5695,7 +5811,7 @@ void Node3DEditor::_update_gizmos_menu() { if (!gizmo_plugins_by_name[i]->can_be_hidden()) { continue; } - String plugin_name = gizmo_plugins_by_name[i]->get_name(); + String plugin_name = gizmo_plugins_by_name[i]->get_gizmo_name(); const int plugin_state = gizmo_plugins_by_name[i]->get_state(); gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i); const int idx = gizmos_menu->get_item_index(i); @@ -5704,13 +5820,13 @@ void Node3DEditor::_update_gizmos_menu() { TTR("Click to toggle between visibility states.\n\nOpen eye: Gizmo is visible.\nClosed eye: Gizmo is hidden.\nHalf-open eye: Gizmo is also visible through opaque surfaces (\"x-ray\").")); switch (plugin_state) { case EditorNode3DGizmoPlugin::VISIBLE: - gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon("visibility_visible")); + gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon(SNAME("visibility_visible"))); break; case EditorNode3DGizmoPlugin::ON_TOP: - gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon("visibility_xray")); + gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon(SNAME("visibility_xray"))); break; case EditorNode3DGizmoPlugin::HIDDEN: - gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon("visibility_hidden")); + gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon(SNAME("visibility_hidden"))); break; } } @@ -5725,13 +5841,13 @@ void Node3DEditor::_update_gizmos_menu_theme() { const int idx = gizmos_menu->get_item_index(i); switch (plugin_state) { case EditorNode3DGizmoPlugin::VISIBLE: - gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon("visibility_visible")); + gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon(SNAME("visibility_visible"))); break; case EditorNode3DGizmoPlugin::ON_TOP: - gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon("visibility_xray")); + gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon(SNAME("visibility_xray"))); break; case EditorNode3DGizmoPlugin::HIDDEN: - gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon("visibility_hidden")); + gizmos_menu->set_item_icon(idx, gizmos_menu->get_theme_icon(SNAME("visibility_hidden"))); break; } } @@ -5742,7 +5858,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. } @@ -5766,7 +5882,7 @@ void Node3DEditor::_init_grid() { // Offsets division_level for bigger or smaller grids. // Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids. real_t division_level_bias = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_bias"); - // Default largest grid size is 100m, 10^2 (default value is 2). + // Default largest grid size is 8^2 when primary_grid_steps is 8 (64m apart, so primary grid lines are 512m apart). int division_level_max = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_max"); // Default smallest grid size is 1cm, 10^-2 (default value is -2). int division_level_min = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_min"); @@ -5888,6 +6004,7 @@ void Node3DEditor::_init_grid() { RenderingServer::get_singleton()->instance_set_visible(grid_instance[c], grid_visible[a]); RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); + RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); } } @@ -6074,7 +6191,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { } if (snapped_to_floor) { - undo_redo->create_action(TTR("Snap Nodes To Floor")); + undo_redo->create_action(TTR("Snap Nodes to Floor")); // Perform snapping if at least one node can be snapped for (int i = 0; i < keys.size(); i++) { @@ -6087,7 +6204,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; @@ -6105,74 +6222,154 @@ void Node3DEditor::snap_selected_nodes_to_floor() { } void Node3DEditor::_unhandled_key_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (!is_visible_in_tree()) { 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() { + Vector2 pos = sun_environ_settings->get_screen_position() + sun_environ_settings->get_size(); + sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0)); + sun_environ_popup->popup(); +} + +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) { + // 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(TTR("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(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) { + // 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); + + WorldEnvironment *new_env = memnew(WorldEnvironment); + new_env->set_environment(preview_environment->get_environment()->duplicate(true)); + + undo_redo->create_action(TTR("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); + undo_redo->commit_action(); } void Node3DEditor::_notification(int p_what) { if (p_what == NOTIFICATION_READY) { - tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon("ToolMove", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon("ToolRotate", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon("ToolScale", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon("ListSelect", "EditorIcons")); - tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon("Lock", "EditorIcons")); - tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon("Unlock", "EditorIcons")); - tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon("Group", "EditorIcons")); - tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon("Ungroup", "EditorIcons")); - - tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon("Object", "EditorIcons")); - tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon("Snap", "EditorIcons")); - tool_option_button[Node3DEditor::TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon("Camera3D", "EditorIcons")); - - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon("Panels1", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon("Panels2", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_theme_icon("Panels2Alt", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_theme_icon("Panels3", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon("Panels3Alt", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon("Panels4", "EditorIcons")); + tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + + tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); + tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + tool_option_button[Node3DEditor::TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); + + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon(SNAME("Panels1"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon(SNAME("Panels2"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels2Alt"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_theme_icon(SNAME("Panels3"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels3Alt"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon(SNAME("Panels4"), SNAME("EditorIcons"))); _menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT); _refresh_menu_icons(); get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed)); + get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added)); EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons)); editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons)); editor->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(false)); editor->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(true)); + + sun_button->set_icon(get_theme_icon(SNAME("DirectionalLight3D"), SNAME("EditorIcons"))); + environ_button->set_icon(get_theme_icon(SNAME("WorldEnvironment"), SNAME("EditorIcons"))); + sun_environ_settings->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + + _update_preview_environment(); + sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); + environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); + + sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size()); + environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size()); } else if (p_what == NOTIFICATION_ENTER_TREE) { _register_all_gizmos(); _update_gizmos_menu(); _init_indicators(); } else if (p_what == NOTIFICATION_THEME_CHANGED) { _update_gizmos_menu_theme(); + sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); + environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); } else if (p_what == NOTIFICATION_EXIT_TREE) { _finish_indicators(); } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon("ToolMove", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon("ToolRotate", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon("ToolScale", "EditorIcons")); - tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon("ListSelect", "EditorIcons")); - tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon("Lock", "EditorIcons")); - tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon("Unlock", "EditorIcons")); - tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon("Group", "EditorIcons")); - tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon("Ungroup", "EditorIcons")); - - tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon("Object", "EditorIcons")); - tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon("Snap", "EditorIcons")); - - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon("Panels1", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon("Panels2", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_theme_icon("Panels2Alt", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_theme_icon("Panels3", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon("Panels3Alt", "EditorIcons")); - view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon("Panels4", "EditorIcons")); + tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + + tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); + tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon(SNAME("Panels1"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon(SNAME("Panels2"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels2Alt"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_theme_icon(SNAME("Panels3"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels3Alt"), SNAME("EditorIcons"))); + view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon(SNAME("Panels4"), SNAME("EditorIcons"))); // Update grid color by rebuilding grid. _finish_grid(); @@ -6214,7 +6411,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) { @@ -6287,7 +6484,37 @@ void Node3DEditor::_toggle_maximize_view(Object *p_viewport) { } } +void Node3DEditor::_node_added(Node *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) { + _update_preview_environment(); + } + } else if (Object::cast_to<DirectionalLight3D>(p_node)) { + directional_light_count++; + if (directional_light_count == 1) { + _update_preview_environment(); + } + } + } +} + void Node3DEditor::_node_removed(Node *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) { + _update_preview_environment(); + } + } else if (Object::cast_to<DirectionalLight3D>(p_node)) { + directional_light_count--; + if (directional_light_count == 0) { + _update_preview_environment(); + } + } + } + if (p_node == selected) { selected = nullptr; } @@ -6298,6 +6525,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<Light3DGizmoPlugin>(memnew(Light3DGizmoPlugin))); add_gizmo_plugin(Ref<AudioStreamPlayer3DGizmoPlugin>(memnew(AudioStreamPlayer3DGizmoPlugin))); add_gizmo_plugin(Ref<MeshInstance3DGizmoPlugin>(memnew(MeshInstance3DGizmoPlugin))); + add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin))); add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin))); add_gizmo_plugin(Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin))); @@ -6305,15 +6533,16 @@ 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))); add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin))); add_gizmo_plugin(Ref<NavigationRegion3DGizmoPlugin>(memnew(NavigationRegion3DGizmoPlugin))); @@ -6357,6 +6586,146 @@ void Node3DEditor::clear() { view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_GRID), true); } +void Node3DEditor::_sun_direction_draw() { + sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1)); + 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() { + if (sun_environ_updating) { + return; + } + + { // preview sun + 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()); + preview_sun->set_param(Light3D::PARAM_SHADOW_MAX_DISTANCE, sun_max_distance->get_value()); + preview_sun->set_color(sun_color->get_pick_color()); + } + + { //preview env + sky_material->set_sky_energy(environ_energy->get_value()); + Color hz_color = environ_sky_color->get_pick_color().lerp(environ_ground_color->get_pick_color(), 0.5).lerp(Color(1, 1, 1), 0.5); + sky_material->set_sky_top_color(environ_sky_color->get_pick_color()); + sky_material->set_sky_horizon_color(hz_color); + sky_material->set_ground_bottom_color(environ_ground_color->get_pick_color()); + sky_material->set_ground_horizon_color(hz_color); + + environment->set_ssao_enabled(environ_ao_button->is_pressed()); + environment->set_glow_enabled(environ_glow_button->is_pressed()); + environment->set_sdfgi_enabled(environ_gi_button->is_pressed()); + 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; + + // 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)); + environ_energy->set_value(1.0); + environ_glow_button->set_pressed(true); + environ_tonemap_button->set_pressed(true); + environ_ao_button->set_pressed(false); + environ_gi_button->set_pressed(false); + sun_max_distance->set_value(250); + + sun_color->set_pick_color(Color(1, 1, 1)); + sun_energy->set_value(1.0); + + sun_environ_updating = false; +} + +void Node3DEditor::_update_preview_environment() { + bool disable_light = directional_light_count > 0 || sun_button->is_pressed(); + + sun_button->set_disabled(directional_light_count > 0); + + if (disable_light) { + if (preview_sun->get_parent()) { + preview_sun->get_parent()->remove_child(preview_sun); + sun_state->show(); + sun_vb->hide(); + } + + if (directional_light_count > 0) { + sun_state->set_text(TTR("Scene contains\nDirectionalLight3D.\nPreview disabled.")); + } else { + sun_state->set_text(TTR("Preview disabled.")); + } + + } else { + if (!preview_sun->get_parent()) { + add_child(preview_sun); + sun_state->hide(); + sun_vb->show(); + } + } + + 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); + + if (disable_env) { + if (preview_environment->get_parent()) { + preview_environment->get_parent()->remove_child(preview_environment); + environ_state->show(); + environ_vb->hide(); + } + if (world_env_count > 0) { + environ_state->set_text(TTR("Scene contains\nWorldEnvironment.\nPreview disabled.")); + } else { + environ_state->set_text(TTR("Preview disabled.")); + } + + } else { + if (!preview_environment->get_parent()) { + add_child(preview_environment); + environ_state->hide(); + environ_vb->show(); + } + } +} + +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) { + 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; @@ -6384,6 +6753,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); @@ -6393,7 +6769,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { tool_button[TOOL_MODE_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTR("Select Mode"), KEY_Q)); tool_button[TOOL_MODE_SELECT]->set_shortcut_context(this); - tool_button[TOOL_MODE_SELECT]->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate\nAlt+Drag: Move\nAlt+RMB: Depth list selection")); + tool_button[TOOL_MODE_SELECT]->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate\nAlt+Drag: Move\nAlt+RMB: Show list of all nodes at position clicked, including locked.")); hbc_menu->add_child(memnew(VSeparator)); @@ -6432,21 +6808,25 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { tool_button[TOOL_MODE_LIST_SELECT]->set_flat(true); button_binds.write[0] = MENU_TOOL_LIST_SELECT; tool_button[TOOL_MODE_LIST_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); - tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip(TTR("Show a list of all objects at the position clicked\n(same as Alt+RMB in select mode).")); + tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip(TTR("Show list of selectable nodes at position clicked.")); tool_button[TOOL_LOCK_SELECTED] = memnew(Button); hbc_menu->add_child(tool_button[TOOL_LOCK_SELECTED]); tool_button[TOOL_LOCK_SELECTED]->set_flat(true); 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).")); + tool_button[TOOL_LOCK_SELECTED]->set_tooltip(TTR("Lock selected node, preventing selection and movement.")); + // 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]); tool_button[TOOL_UNLOCK_SELECTED]->set_flat(true); 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).")); + tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip(TTR("Unlock selected node, allowing selection and movement.")); + // 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]); @@ -6454,6 +6834,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]); @@ -6461,6 +6843,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)); @@ -6494,6 +6878,32 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { _update_camera_override_button(false); hbc_menu->add_child(memnew(VSeparator)); + sun_button = memnew(Button); + sun_button->set_tooltip(TTR("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled.")); + sun_button->set_toggle_mode(true); + sun_button->set_flat(true); + sun_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), varray(), CONNECT_DEFERRED); + sun_button->set_disabled(true); + + hbc_menu->add_child(sun_button); + + environ_button = memnew(Button); + environ_button->set_tooltip(TTR("Toggle preview environment.\nIf a WorldEnvironment node is added to the scene, preview environment is disabled.")); + environ_button->set_toggle_mode(true); + environ_button->set_flat(true); + environ_button->connect("pressed", callable_mp(this, &Node3DEditor::_update_preview_environment), varray(), CONNECT_DEFERRED); + environ_button->set_disabled(true); + + hbc_menu->add_child(environ_button); + + sun_environ_settings = memnew(Button); + sun_environ_settings->set_tooltip(TTR("Edit Sun and Environment settings.")); + sun_environ_settings->set_flat(true); + sun_environ_settings->connect("pressed", callable_mp(this, &Node3DEditor::_sun_environ_settings_pressed)); + + hbc_menu->add_child(sun_environ_settings); + + hbc_menu->add_child(memnew(VSeparator)); // Drag and drop support; preview_node = memnew(Node3D); @@ -6553,7 +6963,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); @@ -6709,8 +7119,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); @@ -6723,6 +7131,198 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { EDITOR_DEF("editors/3d/navigation/show_viewport_rotation_gizmo", true); over_gizmo_handle = -1; + { + //sun popup + + sun_environ_popup = memnew(PopupPanel); + add_child(sun_environ_popup); + + HBoxContainer *sun_environ_hb = memnew(HBoxContainer); + + sun_environ_popup->add_child(sun_environ_hb); + + sun_vb = memnew(VBoxContainer); + sun_environ_hb->add_child(sun_vb); + sun_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); + sun_vb->hide(); + + sun_title = memnew(Label); + sun_title->set_theme_type_variation("HeaderSmall"); + sun_vb->add_child(sun_title); + sun_title->set_text(TTR("Preview Sun")); + sun_title->set_align(Label::ALIGN_CENTER); + + CenterContainer *sun_direction_center = memnew(CenterContainer); + sun_direction = memnew(Control); + sun_direction->set_custom_minimum_size(Size2i(128, 128) * EDSCALE); + sun_direction_center->add_child(sun_direction); + sun_vb->add_margin_child(TTR("Sun Direction"), sun_direction_center); + sun_direction->connect("gui_input", callable_mp(this, &Node3DEditor::_sun_direction_input)); + sun_direction->connect("draw", callable_mp(this, &Node3DEditor::_sun_direction_draw)); + sun_direction->set_default_cursor_shape(CURSOR_MOVE); + + sun_direction_shader.instantiate(); + sun_direction_shader->set_code(R"( +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_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); + sun_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + + sun_energy = memnew(EditorSpinSlider); + sun_vb->add_margin_child(TTR("Sun Energy"), sun_energy); + sun_energy->connect("value_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + sun_energy->set_max(64.0); + + sun_max_distance = memnew(EditorSpinSlider); + sun_vb->add_margin_child(TTR("Shadow Max Distance"), sun_max_distance); + sun_max_distance->connect("value_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + sun_max_distance->set_min(1); + sun_max_distance->set_max(4096); + + sun_add_to_scene = memnew(Button); + sun_add_to_scene->set_text(TTR("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); + + sun_state = memnew(Label); + sun_environ_hb->add_child(sun_state); + sun_state->set_align(Label::ALIGN_CENTER); + sun_state->set_valign(Label::VALIGN_CENTER); + sun_state->set_h_size_flags(SIZE_EXPAND_FILL); + + VSeparator *sc = memnew(VSeparator); + sc->set_custom_minimum_size(Size2(50 * EDSCALE, 0)); + sc->set_v_size_flags(SIZE_EXPAND_FILL); + sun_environ_hb->add_child(sc); + + environ_vb = memnew(VBoxContainer); + sun_environ_hb->add_child(environ_vb); + environ_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); + environ_vb->hide(); + + environ_title = memnew(Label); + environ_title->set_theme_type_variation("HeaderSmall"); + + environ_vb->add_child(environ_title); + environ_title->set_text(TTR("Preview Environment")); + environ_title->set_align(Label::ALIGN_CENTER); + + environ_sky_color = memnew(ColorPickerButton); + environ_sky_color->set_edit_alpha(false); + environ_sky_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + environ_vb->add_margin_child(TTR("Sky Color"), environ_sky_color); + environ_ground_color = memnew(ColorPickerButton); + environ_ground_color->connect("color_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + environ_ground_color->set_edit_alpha(false); + environ_vb->add_margin_child(TTR("Ground Color"), environ_ground_color); + environ_energy = memnew(EditorSpinSlider); + environ_energy->connect("value_changed", callable_mp(this, &Node3DEditor::_preview_settings_changed).unbind(1)); + environ_energy->set_max(8.0); + environ_vb->add_margin_child(TTR("Sky Energy"), environ_energy); + HBoxContainer *fx_vb = memnew(HBoxContainer); + fx_vb->set_h_size_flags(SIZE_EXPAND_FILL); + + environ_ao_button = memnew(Button); + environ_ao_button->set_text(TTR("AO")); + environ_ao_button->set_toggle_mode(true); + environ_ao_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + fx_vb->add_child(environ_ao_button); + environ_glow_button = memnew(Button); + environ_glow_button->set_text(TTR("Glow")); + environ_glow_button->set_toggle_mode(true); + environ_glow_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + fx_vb->add_child(environ_glow_button); + environ_tonemap_button = memnew(Button); + environ_tonemap_button->set_text(TTR("Tonemap")); + environ_tonemap_button->set_toggle_mode(true); + environ_tonemap_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + fx_vb->add_child(environ_tonemap_button); + environ_gi_button = memnew(Button); + environ_gi_button->set_text(TTR("GI")); + environ_gi_button->set_toggle_mode(true); + environ_gi_button->connect("pressed", callable_mp(this, &Node3DEditor::_preview_settings_changed), varray(), CONNECT_DEFERRED); + fx_vb->add_child(environ_gi_button); + environ_vb->add_margin_child(TTR("Post Process"), fx_vb); + + environ_add_to_scene = memnew(Button); + environ_add_to_scene->set_text(TTR("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); + + environ_state = memnew(Label); + sun_environ_hb->add_child(environ_state); + environ_state->set_align(Label::ALIGN_CENTER); + environ_state->set_valign(Label::VALIGN_CENTER); + environ_state->set_h_size_flags(SIZE_EXPAND_FILL); + + preview_sun = memnew(DirectionalLight3D); + preview_sun->set_shadow(true); + preview_sun->set_shadow_mode(DirectionalLight3D::SHADOW_PARALLEL_4_SPLITS); + preview_environment = memnew(WorldEnvironment); + environment.instantiate(); + preview_environment->set_environment(environment); + Ref<Sky> sky; + sky.instantiate(); + sky_material.instantiate(); + sky->set_material(sky_material); + environment->set_sky(sky); + environment->set_background(Environment::BG_SKY); + + _load_default_preview_settings(); + _preview_settings_changed(); + } } Node3DEditor::~Node3DEditor() { @@ -6756,10 +7356,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); @@ -6802,18 +7398,10 @@ 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()) { - return p_a->get_name() < p_b->get_name(); + return p_a->get_gizmo_name() < p_b->get_gizmo_name(); } return p_a->get_priority() > p_b->get_priority(); } @@ -6821,7 +7409,7 @@ struct _GizmoPluginPriorityComparator { struct _GizmoPluginNameComparator { bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const { - return p_a->get_name() < p_b->get_name(); + return p_a->get_gizmo_name() < p_b->get_gizmo_name(); } }; @@ -6858,17 +7446,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; @@ -6878,6 +7466,7 @@ void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1); + material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); if (p_use_vertex_color) { material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); @@ -6899,17 +7488,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; @@ -6943,7 +7532,7 @@ void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true); - Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon("Editor3DHandle", "EditorIcons"); + Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons")); handle_material->set_point_size(handle_t->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t); handle_material->set_albedo(Color(1, 1, 1)); @@ -6987,15 +7576,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; } @@ -7022,8 +7611,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))); @@ -7032,97 +7621,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_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 bf478f850e..cc65cb53ca 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -34,10 +34,12 @@ #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" #include "scene/gui/panel_container.h" +#include "scene/resources/environment.h" +#include "scene/resources/sky_material.h" class Camera3D; class Node3DEditor; @@ -49,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; } @@ -96,6 +98,7 @@ protected: public: void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1)); + void add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1)); void add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard = false, const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>(), const Ref<Material> &p_material = Ref<Material>()); void add_collision_segments(const Vector<Vector3> &p_lines); void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh); @@ -202,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, @@ -217,6 +220,7 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, + VIEW_DISPLAY_DEBUG_OCCLUDERS, VIEW_LOCK_ROTATION, VIEW_CINEMATIC_PREVIEW, @@ -291,6 +295,7 @@ private: Label *info_label; Label *cinema_label; Label *locked_label; + Label *zoom_limit_label; VBoxContainer *top_right_vbox; ViewportRotationControl *rotation_control; @@ -312,11 +317,11 @@ private: void _select_clicked(bool p_append, bool p_single, bool p_allow_locked = false); void _select(Node *p_node, bool p_append, bool p_single); ObjectID _select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle = nullptr, bool p_alt_select = false); - void _find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select = false); + void _find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select = false, bool p_include_locked_nodes = false); 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; @@ -374,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; @@ -414,6 +420,7 @@ private: void scale_freelook_speed(real_t scale); real_t zoom_indicator_delay; + int zoom_failed_attempts_count = 0; RID move_gizmo_instance[3], move_plane_gizmo_instance[3], rotate_gizmo_instance[4], scale_gizmo_instance[3], scale_plane_gizmo_instance[3]; @@ -425,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(); @@ -487,7 +494,7 @@ public: AcceptDialog *p_accept); SubViewport *get_viewport_node() { return viewport; } - Camera3D *get_camera() { return camera; } // return the default camera object. + Camera3D *get_camera_3d() { return camera; } // return the default camera object. Node3DEditorViewport(Node3DEditor *p_spatial_editor, EditorNode *p_editor, int p_index); ~Node3DEditorViewport(); @@ -498,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; @@ -593,8 +600,6 @@ private: ToolMode tool_mode; - RenderingServer::ScenarioDebugMode scenario_debug; - RID origin; RID origin_instance; bool origin_enabled; @@ -634,7 +639,7 @@ private: struct Gizmo { bool visible = false; float scale = 0; - Transform transform; + Transform3D transform; } gizmo; enum MenuOption { @@ -732,6 +737,7 @@ private: static Node3DEditor *singleton; + void _node_added(Node *p_node); void _node_removed(Node *p_node); Vector<Ref<EditorNode3DGizmoPlugin>> gizmo_plugins_by_priority; Vector<Ref<EditorNode3DGizmoPlugin>> gizmo_plugins_by_name; @@ -744,6 +750,64 @@ private: void _refresh_menu_icons(); + // Preview Sun and Environment + + uint32_t world_env_count = 0; + uint32_t directional_light_count = 0; + + Button *sun_button; + Label *sun_state; + Label *sun_title; + 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; + Button *sun_add_to_scene; + + void _sun_direction_draw(); + void _sun_direction_input(const Ref<InputEvent> &p_event); + void _sun_direction_angle_set(); + + Vector2 sun_rotation; + + Ref<Shader> sun_direction_shader; + Ref<ShaderMaterial> sun_direction_material; + + Button *environ_button; + Label *environ_state; + Label *environ_title; + VBoxContainer *environ_vb; + ColorPickerButton *environ_sky_color; + ColorPickerButton *environ_ground_color; + EditorSpinSlider *environ_energy; + Button *environ_ao_button; + Button *environ_glow_button; + Button *environ_tonemap_button; + Button *environ_gi_button; + Button *environ_add_to_scene; + + Button *sun_environ_settings; + + DirectionalLight3D *preview_sun; + WorldEnvironment *preview_environment; + Ref<Environment> environment; + Ref<ProceduralSkyMaterial> sky_material; + + bool sun_environ_updating = false; + + void _load_default_preview_settings(); + void _update_preview_environment(); + + void _preview_settings_changed(); + void _sun_environ_settings_pressed(); + + 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); //void _gui_input(InputEvent p_event); @@ -753,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; @@ -761,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; } @@ -826,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 new file mode 100644 index 0000000000..ab88b9f00d --- /dev/null +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -0,0 +1,117 @@ +/*************************************************************************/ +/* occluder_instance_3d_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 "occluder_instance_3d_editor_plugin.h" + +void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { + if (occluder_instance) { + OccluderInstance3D::BakeError err; + if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == occluder_instance) { + err = occluder_instance->bake(occluder_instance, p_file); + } else { + err = occluder_instance->bake(occluder_instance->get_parent(), p_file); + } + + switch (err) { + case OccluderInstance3D::BAKE_ERROR_NO_SAVE_PATH: { + String scene_path = occluder_instance->get_filename(); + if (scene_path == String()) { + scene_path = occluder_instance->get_owner()->get_filename(); + } + if (scene_path == String()) { + EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for the occluder.\nSave your scene and try again.")); + break; + } + scene_path = scene_path.get_basename() + ".occ"; + + file_dialog->set_current_path(scene_path); + file_dialog->popup_file_dialog(); + + } break; + case OccluderInstance3D::BAKE_ERROR_NO_MESHES: { + 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: { + } + } + } +} + +void OccluderInstance3DEditorPlugin::_bake() { + _bake_select_file(""); +} + +void OccluderInstance3DEditorPlugin::edit(Object *p_object) { + OccluderInstance3D *s = Object::cast_to<OccluderInstance3D>(p_object); + if (!s) { + return; + } + + occluder_instance = s; +} + +bool OccluderInstance3DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("OccluderInstance3D"); +} + +void OccluderInstance3DEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + bake->show(); + } else { + bake->hide(); + } +} + +void OccluderInstance3DEditorPlugin::_bind_methods() { + ClassDB::bind_method("_bake", &OccluderInstance3DEditorPlugin::_bake); +} + +OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin(EditorNode *p_node) { + editor = p_node; + bake = memnew(Button); + bake->set_flat(true); + bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_text(TTR("Bake Occluders")); + bake->hide(); + bake->connect("pressed", Callable(this, "_bake")); + add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake); + occluder_instance = nullptr; + + file_dialog = memnew(EditorFileDialog); + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_dialog->add_filter("*.occ ; Occluder3D"); + file_dialog->set_title(TTR("Select occluder bake file:")); + file_dialog->connect("file_selected", callable_mp(this, &OccluderInstance3DEditorPlugin::_bake_select_file)); + bake->add_child(file_dialog); +} + +OccluderInstance3DEditorPlugin::~OccluderInstance3DEditorPlugin() { +} diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.h b/editor/plugins/occluder_instance_3d_editor_plugin.h new file mode 100644 index 0000000000..161b17811c --- /dev/null +++ b/editor/plugins/occluder_instance_3d_editor_plugin.h @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* occluder_instance_3d_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 OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H +#define OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "scene/3d/occluder_instance_3d.h" +#include "scene/resources/material.h" + +class OccluderInstance3DEditorPlugin : public EditorPlugin { + GDCLASS(OccluderInstance3DEditorPlugin, EditorPlugin); + + OccluderInstance3D *occluder_instance; + + Button *bake; + EditorNode *editor; + + EditorFileDialog *file_dialog; + + void _bake_select_file(const String &p_file); + void _bake(); + +protected: + static void _bind_methods(); + +public: + virtual String get_name() const override { return "OccluderInstance3D"; } + 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; + + OccluderInstance3DEditorPlugin(EditorNode *p_node); + ~OccluderInstance3DEditorPlugin(); +}; + +#endif diff --git a/editor/plugins/ot_features_plugin.cpp b/editor/plugins/ot_features_plugin.cpp index ebfdf2c7cd..fd42bce06e 100644 --- a/editor/plugins/ot_features_plugin.cpp +++ b/editor/plugins/ot_features_plugin.cpp @@ -49,10 +49,10 @@ void OpenTypeFeaturesEditor::update_property() { void OpenTypeFeaturesEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - Color base = get_theme_color("accent_color", "Editor"); + Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - button->set_icon(get_theme_icon("Remove", "EditorIcons")); - button->set_size(get_theme_icon("Remove", "EditorIcons")->get_size()); + button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + button->set_size(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))->get_size()); spin->set_custom_label_color(true, base); } } @@ -106,7 +106,7 @@ void OpenTypeFeaturesAdd::update_property() { bool have_ss = false; bool have_cv = false; bool have_cu = false; - Dictionary features = Object::cast_to<Control>(get_edited_object())->get_theme_font("font")->get_feature_list(); + Dictionary features = Object::cast_to<Control>(get_edited_object())->get_theme_font(SNAME("font"))->get_feature_list(); for (const Variant *ftr = features.next(nullptr); ftr != nullptr; ftr = features.next(ftr)) { String ftr_name = TS->tag_to_name(*ftr); if (ftr_name.begins_with("stylistic_set_")) { @@ -142,8 +142,8 @@ void OpenTypeFeaturesAdd::_features_menu() { void OpenTypeFeaturesAdd::_notification(int p_what) { if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { set_label(""); - button->set_icon(get_theme_icon("Add", "EditorIcons")); - button->set_size(get_theme_icon("Add", "EditorIcons")->get_size()); + button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + button->set_size(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))->get_size()); } } @@ -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 908235f89f..8866e8c53e 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" @@ -88,8 +88,8 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { real_t dist_to_p_in = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_in(i))); // Check for point movement start (for point + in/out controls). - if (mb->get_button_index() == BUTTON_LEFT) { - if (mode == MODE_EDIT && !mb->get_shift() && dist_to_p < grab_threshold) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + 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; @@ -118,7 +118,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point deletion. - if ((mb->get_button_index() == BUTTON_RIGHT && mode == MODE_EDIT) || (mb->get_button_index() == BUTTON_LEFT && mode == MODE_DELETE)) { + if ((mb->get_button_index() == MOUSE_BUTTON_RIGHT && mode == MODE_EDIT) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && mode == MODE_DELETE)) { if (dist_to_p < grab_threshold) { undo_redo->create_action(TTR("Remove Point from Curve")); undo_redo->add_do_method(curve.ptr(), "remove_point", i); @@ -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() == 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")); @@ -170,7 +170,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for segment split. - if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && mode == MODE_EDIT && on_edge) { + if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mode == MODE_EDIT && on_edge) { Vector2 gpoint2 = mb->get_position(); Ref<Curve2D> curve = node->get_curve(); @@ -207,7 +207,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point movement completion. - if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && action != ACTION_NONE) { + if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && action != ACTION_NONE) { Ref<Curve2D> curve = node->get_curve(); Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from); @@ -364,12 +364,12 @@ void Path2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); - const Ref<Texture2D> path_sharp_handle = get_theme_icon("EditorPathSharpHandle", "EditorIcons"); - const Ref<Texture2D> path_smooth_handle = get_theme_icon("EditorPathSmoothHandle", "EditorIcons"); + const Ref<Texture2D> path_sharp_handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); + const Ref<Texture2D> path_smooth_handle = get_theme_icon(SNAME("EditorPathSmoothHandle"), SNAME("EditorIcons")); // Both handle icons must be of the same size const Size2 handle_size = path_sharp_handle->get_size(); - const Ref<Texture2D> curve_handle = get_theme_icon("EditorCurveHandle", "EditorIcons"); + const Ref<Texture2D> curve_handle = get_theme_icon(SNAME("EditorCurveHandle"), SNAME("EditorIcons")); const Size2 curve_handle_size = curve_handle->get_size(); Ref<Curve2D> curve = node->get_curve(); @@ -411,7 +411,7 @@ void Path2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { } if (on_edge) { - Ref<Texture2D> add_handle = get_theme_icon("EditorHandleAdd", "EditorIcons"); + Ref<Texture2D> add_handle = get_theme_icon(SNAME("EditorHandleAdd"), SNAME("EditorIcons")); p_overlay->draw_texture(add_handle, edge_point - add_handle->get_size() * 0.5); } } @@ -481,7 +481,7 @@ void Path2DEditor::_mode_selected(int p_mode) { Vector2 begin = node->get_curve()->get_point_position(0); Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1); - if (begin.distance_to(end) < CMP_EPSILON) { + if (begin.is_equal_approx(end)) { return; } @@ -534,7 +534,7 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { base_hb->add_child(sep); curve_edit = memnew(Button); curve_edit->set_flat(true); - curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveEdit", "EditorIcons")); + curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); curve_edit->set_toggle_mode(true); curve_edit->set_focus_mode(Control::FOCUS_NONE); curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string(KEY_MASK_CMD) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point")); @@ -542,7 +542,7 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { base_hb->add_child(curve_edit); curve_edit_curve = memnew(Button); curve_edit_curve->set_flat(true); - curve_edit_curve->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCurve", "EditorIcons")); + curve_edit_curve->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCurve"), SNAME("EditorIcons"))); curve_edit_curve->set_toggle_mode(true); curve_edit_curve->set_focus_mode(Control::FOCUS_NONE); curve_edit_curve->set_tooltip(TTR("Select Control Points (Shift+Drag)")); @@ -550,7 +550,7 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { base_hb->add_child(curve_edit_curve); curve_create = memnew(Button); curve_create->set_flat(true); - curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCreate", "EditorIcons")); + curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); curve_create->set_toggle_mode(true); curve_create->set_focus_mode(Control::FOCUS_NONE); curve_create->set_tooltip(TTR("Add Point (in empty space)")); @@ -558,7 +558,7 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { base_hb->add_child(curve_create); curve_del = memnew(Button); curve_del->set_flat(true); - curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveDelete", "EditorIcons")); + curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); curve_del->set_toggle_mode(true); curve_del->set_focus_mode(Control::FOCUS_NONE); curve_del->set_tooltip(TTR("Delete Point")); @@ -566,7 +566,7 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { base_hb->add_child(curve_del); curve_close = memnew(Button); curve_close->set_flat(true); - curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveClose", "EditorIcons")); + curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip(TTR("Close Curve")); curve_close->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(ACTION_CLOSE)); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 3783af8fc6..0a95986fe2 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() == 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; @@ -411,7 +411,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref //add new at pos } - } else if (mb->is_pressed() && ((mb->get_button_index() == BUTTON_LEFT && curve_del->is_pressed()) || (mb->get_button_index() == BUTTON_RIGHT && curve_edit->is_pressed()))) { + } else if (mb->is_pressed() && ((mb->get_button_index() == MOUSE_BUTTON_LEFT && curve_del->is_pressed()) || (mb->get_button_index() == MOUSE_BUTTON_RIGHT && curve_edit->is_pressed()))) { for (int i = 0; i < c->get_point_count(); i++) { real_t dist_to_p = p_camera->unproject_position(gt.xform(c->get_point_position(i))).distance_to(mbpos); real_t dist_to_p_out = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_out(i))).distance_to(mbpos); @@ -453,14 +453,14 @@ void Path3DEditorPlugin::edit(Object *p_object) { path = Object::cast_to<Path3D>(p_object); if (path) { if (path->get_curve().is_valid()) { - path->get_curve()->emit_signal("changed"); + path->get_curve()->emit_signal(SNAME("changed")); } } } else { Path3D *pre = path; path = nullptr; if (pre) { - pre->get_curve()->emit_signal("changed"); + pre->get_curve()->emit_signal(SNAME("changed")); } } //collision_polygon_editor->edit(Object::cast_to<Node>(p_object)); @@ -490,7 +490,7 @@ void Path3DEditorPlugin::make_visible(bool p_visible) { Path3D *pre = path; path = nullptr; if (pre && pre->get_curve().is_valid()) { - pre->get_curve()->emit_signal("changed"); + pre->get_curve()->emit_signal(SNAME("changed")); } } } @@ -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); @@ -562,7 +562,7 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { Node3DEditor::get_singleton()->add_control_to_menu_panel(sep); curve_edit = memnew(Button); curve_edit->set_flat(true); - curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveEdit", "EditorIcons")); + curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); curve_edit->set_toggle_mode(true); curve_edit->hide(); curve_edit->set_focus_mode(Control::FOCUS_NONE); @@ -570,7 +570,7 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_edit); curve_create = memnew(Button); curve_create->set_flat(true); - curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCreate", "EditorIcons")); + curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); curve_create->set_toggle_mode(true); curve_create->hide(); curve_create->set_focus_mode(Control::FOCUS_NONE); @@ -578,7 +578,7 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_create); curve_del = memnew(Button); curve_del->set_flat(true); - curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveDelete", "EditorIcons")); + curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); curve_del->set_toggle_mode(true); curve_del->hide(); curve_del->set_focus_mode(Control::FOCUS_NONE); @@ -586,7 +586,7 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_del); curve_close = memnew(Button); curve_close->set_flat(true); - curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveClose", "EditorIcons")); + curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); curve_close->hide(); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip(TTR("Close Curve")); @@ -644,6 +644,6 @@ Path3DGizmoPlugin::Path3DGizmoPlugin() { Color path_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/path", Color(0.5, 0.5, 1.0, 0.8)); create_material("path_material", path_color); create_material("path_thin_material", Color(0.5, 0.5, 0.5)); - create_handle_material("handles", false, Node3DEditor::get_singleton()->get_theme_icon("EditorPathSmoothHandle", "EditorIcons")); - create_handle_material("sec_handles", false, Node3DEditor::get_singleton()->get_theme_icon("EditorCurveHandle", "EditorIcons")); + create_handle_material("handles", false, Node3DEditor::get_singleton()->get_theme_icon(SNAME("EditorPathSmoothHandle"), SNAME("EditorIcons"))); + create_handle_material("sec_handles", false, Node3DEditor::get_singleton()->get_theme_icon(SNAME("EditorCurveHandle"), SNAME("EditorIcons"))); } diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index 4b52512933..f92f50f826 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -60,7 +60,7 @@ PhysicalBone3DEditor::PhysicalBone3DEditor(EditorNode *p_editor) : spatial_editor_hb->add_child(button_transform_joint); button_transform_joint->set_text(TTR("Move Joint")); - button_transform_joint->set_icon(Node3DEditor::get_singleton()->get_theme_icon("PhysicalBone3D", "EditorIcons")); + button_transform_joint->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("PhysicalBone3D"), SNAME("EditorIcons"))); button_transform_joint->set_toggle_mode(true); button_transform_joint->connect("toggled", callable_mp(this, &PhysicalBone3DEditor::_on_toggle_button_transform_joint)); diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 3d7b01c149..b420372eff 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" @@ -64,27 +64,27 @@ void Polygon2DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { - uv_edit_draw->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree")); - bone_scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree")); + uv_edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + bone_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); } break; case NOTIFICATION_READY: { - button_uv->set_icon(get_theme_icon("Uv", "EditorIcons")); - - uv_button[UV_MODE_CREATE]->set_icon(get_theme_icon("Edit", "EditorIcons")); - uv_button[UV_MODE_CREATE_INTERNAL]->set_icon(get_theme_icon("EditInternal", "EditorIcons")); - uv_button[UV_MODE_REMOVE_INTERNAL]->set_icon(get_theme_icon("RemoveInternal", "EditorIcons")); - uv_button[UV_MODE_EDIT_POINT]->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); - uv_button[UV_MODE_MOVE]->set_icon(get_theme_icon("ToolMove", "EditorIcons")); - uv_button[UV_MODE_ROTATE]->set_icon(get_theme_icon("ToolRotate", "EditorIcons")); - uv_button[UV_MODE_SCALE]->set_icon(get_theme_icon("ToolScale", "EditorIcons")); - uv_button[UV_MODE_ADD_POLYGON]->set_icon(get_theme_icon("Edit", "EditorIcons")); - uv_button[UV_MODE_REMOVE_POLYGON]->set_icon(get_theme_icon("Close", "EditorIcons")); - uv_button[UV_MODE_PAINT_WEIGHT]->set_icon(get_theme_icon("Bucket", "EditorIcons")); - uv_button[UV_MODE_CLEAR_WEIGHT]->set_icon(get_theme_icon("Clear", "EditorIcons")); - - b_snap_grid->set_icon(get_theme_icon("Grid", "EditorIcons")); - b_snap_enable->set_icon(get_theme_icon("SnapGrid", "EditorIcons")); - uv_icon_zoom->set_texture(get_theme_icon("Zoom", "EditorIcons")); + button_uv->set_icon(get_theme_icon(SNAME("Uv"), SNAME("EditorIcons"))); + + uv_button[UV_MODE_CREATE]->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + uv_button[UV_MODE_CREATE_INTERNAL]->set_icon(get_theme_icon(SNAME("EditInternal"), SNAME("EditorIcons"))); + uv_button[UV_MODE_REMOVE_INTERNAL]->set_icon(get_theme_icon(SNAME("RemoveInternal"), SNAME("EditorIcons"))); + uv_button[UV_MODE_EDIT_POINT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + uv_button[UV_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + uv_button[UV_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + uv_button[UV_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + uv_button[UV_MODE_ADD_POLYGON]->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + uv_button[UV_MODE_REMOVE_POLYGON]->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); + uv_button[UV_MODE_PAINT_WEIGHT]->set_icon(get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons"))); + uv_button[UV_MODE_CLEAR_WEIGHT]->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); + + b_snap_grid->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); + b_snap_enable->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + uv_icon_zoom->set_texture(get_theme_icon(SNAME("Zoom"), SNAME("EditorIcons"))); uv_vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); uv_hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); @@ -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); @@ -447,7 +447,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { - if (mb->get_button_index() == BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { uv_drag_from = snap_point(Vector2(mb->get_position().x, mb->get_position().y)); uv_drag = true; @@ -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; } } @@ -759,7 +759,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { bone_painting = false; } } - } else if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { _cancel_editing(); if (bone_painting) { @@ -768,9 +768,9 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_edit_draw->update(); - } else if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * mb->get_factor()))); - } else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { uv_zoom->set_value(uv_zoom->get_value() * (1 - (0.1 * mb->get_factor()))); } } @@ -778,7 +778,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { Ref<InputEventMouseMotion> mm = p_input; if (mm.is_valid()) { - if ((mm->get_button_mask() & BUTTON_MASK_MIDDLE) || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { + if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { Vector2 drag(mm->get_relative().x, mm->get_relative().y); uv_hscroll->set_value(uv_hscroll->get_value() - drag.x); uv_vscroll->set_value(uv_vscroll->get_value() - drag.y); @@ -1015,7 +1015,7 @@ void Polygon2DEditor::_uv_draw() { } // All UV points are sharp, so use the sharp handle icon - Ref<Texture2D> handle = get_theme_icon("EditorPathSharpHandle", "EditorIcons"); + Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); Color poly_line_color = Color(0.9, 0.5, 0.5); if (polygons.size() || polygon_create.size()) { @@ -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..e099c8b5fd 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -40,7 +40,7 @@ void ResourcePreloaderEditor::_gui_input(Ref<InputEvent> p_event) { void ResourcePreloaderEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - load->set_icon(get_theme_icon("Folder", "EditorIcons")); + load->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); } if (p_what == NOTIFICATION_READY) { @@ -208,11 +208,11 @@ void ResourcePreloaderEditor::_update_library() { ti->set_selectable(1, false); if (type == "PackedScene") { - ti->add_button(1, get_theme_icon("InstanceOptions", "EditorIcons"), BUTTON_OPEN_SCENE, false, TTR("Open in Editor")); + ti->add_button(1, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")), BUTTON_OPEN_SCENE, false, TTR("Open in Editor")); } else { - ti->add_button(1, get_theme_icon("Load", "EditorIcons"), BUTTON_EDIT_RESOURCE, false, TTR("Open in Editor")); + ti->add_button(1, get_theme_icon(SNAME("Load"), SNAME("EditorIcons")), BUTTON_EDIT_RESOURCE, false, TTR("Open in Editor")); } - ti->add_button(1, get_theme_icon("Remove", "EditorIcons"), BUTTON_REMOVE, false, TTR("Remove")); + ti->add_button(1, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), BUTTON_REMOVE, false, TTR("Remove")); } //player->add_resource("default",resource); @@ -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..cea76f5233 100644 --- a/editor/plugins/root_motion_editor_plugin.cpp +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -149,7 +149,7 @@ void EditorPropertyRootMotion::_node_assign() { ti->set_text(0, F->get()); ti->set_selectable(0, true); ti->set_editable(0, false); - ti->set_icon(0, get_theme_icon("BoneAttachment3D", "EditorIcons")); + ti->set_icon(0, get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); ti->set_metadata(0, accum); } else { ti = parenthood[accum]; @@ -158,7 +158,7 @@ void EditorPropertyRootMotion::_node_assign() { ti->set_selectable(0, true); ti->set_text(0, concat); - ti->set_icon(0, get_theme_icon("BoneAttachment3D", "EditorIcons")); + ti->set_icon(0, get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); ti->set_metadata(0, path); if (path == current) { ti->select(0); @@ -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()) { @@ -235,7 +234,7 @@ void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) { void EditorPropertyRootMotion::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - Ref<Texture2D> t = get_theme_icon("Clear", "EditorIcons"); + Ref<Texture2D> t = get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")); clear->set_icon(t); } } @@ -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 a6afd45686..1851ffdc34 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() { @@ -348,7 +355,7 @@ void ScriptEditorQuickOpen::_confirmed() { } int line = ti->get_text(0).get_slice(":", 1).to_int(); - emit_signal("goto_line", line - 1); + emit_signal(SNAME("goto_line"), line - 1); hide(); } @@ -361,7 +368,7 @@ void ScriptEditorQuickOpen::_notification(int p_what) { [[fallthrough]]; } case NOTIFICATION_VISIBILITY_CHANGED: { - search_box->set_right_icon(search_options->get_theme_icon("Search", "EditorIcons")); + search_box->set_right_icon(search_options->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); } break; case NOTIFICATION_EXIT_TREE: { disconnect("confirmed", callable_mp(this, &ScriptEditorQuickOpen::_confirmed)); @@ -570,7 +577,7 @@ void ScriptEditor::_go_to_tab(int p_idx) { } if (Object::cast_to<EditorHelp>(c)) { script_name_label->set_text(Object::cast_to<EditorHelp>(c)->get_class()); - script_icon->set_texture(get_theme_icon("Help", "EditorIcons")); + script_icon->set_texture(get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); if (is_visible_in_tree()) { Object::cast_to<EditorHelp>(c)->set_focused(); } @@ -624,7 +631,7 @@ void ScriptEditor::_open_recent_script(int p_idx) { // clear button if (p_idx == recent_scripts->get_item_count() - 1) { EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", Array()); - call_deferred("_update_recent_scripts"); + call_deferred(SNAME("_update_recent_scripts")); return; } @@ -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,10 +760,11 @@ 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() { - _close_tab(tab_container->get_current_tab()); +void ScriptEditor::_close_current_tab(bool p_save) { + _close_tab(tab_container->get_current_tab(), p_save); } void ScriptEditor::_close_discard_current_tab(const String &p_str) { @@ -802,7 +810,7 @@ void ScriptEditor::_close_other_tabs() { } } - _close_current_tab(); + _close_current_tab(false); } } @@ -820,8 +828,9 @@ void ScriptEditor::_close_all_tabs() { } } - _close_current_tab(); + _close_current_tab(false); } + _update_find_replace_bar(); } void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) { @@ -937,7 +946,7 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { _update_script_names(); if (!pending_auto_reload && auto_reload_running_scripts) { - call_deferred("_live_auto_reload_running_scripts"); + call_deferred(SNAME("_live_auto_reload_running_scripts")); pending_auto_reload = true; } } @@ -988,7 +997,7 @@ bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { script_editor->_reload_scripts(); need_reload = false; } else { - disk_changed->call_deferred("popup_centered_ratio", 0.5); + disk_changed->call_deferred(SNAME("popup_centered_ratio"), 0.5); } } @@ -1146,7 +1155,7 @@ void ScriptEditor::_menu_option(int p_option) { if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { if (!EditorNode::get_singleton()->is_scene_open(res_path)) { EditorNode::get_singleton()->load_scene(res_path); - script_editor->call_deferred("_menu_option", p_option); + script_editor->call_deferred(SNAME("_menu_option"), p_option); previous_scripts.push_back(path); //repeat the operation return; } @@ -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) { @@ -1372,7 +1333,7 @@ void ScriptEditor::_menu_option(int p_option) { if (current->is_unsaved()) { _ask_close_current_unsaved_tab(current); } else { - _close_current_tab(); + _close_current_tab(false); } } break; case FILE_COPY_PATH: { @@ -1525,25 +1486,30 @@ void ScriptEditor::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - help_search->set_icon(get_theme_icon("HelpSearch", "EditorIcons")); - site_search->set_icon(get_theme_icon("Instance", "EditorIcons")); + help_search->set_icon(get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons"))); + site_search->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); if (is_layout_rtl()) { - script_forward->set_icon(get_theme_icon("Back", "EditorIcons")); - script_back->set_icon(get_theme_icon("Forward", "EditorIcons")); + script_forward->set_icon(get_theme_icon(SNAME("Back"), SNAME("EditorIcons"))); + script_back->set_icon(get_theme_icon(SNAME("Forward"), SNAME("EditorIcons"))); } else { - script_forward->set_icon(get_theme_icon("Forward", "EditorIcons")); - script_back->set_icon(get_theme_icon("Back", "EditorIcons")); + script_forward->set_icon(get_theme_icon(SNAME("Forward"), SNAME("EditorIcons"))); + script_back->set_icon(get_theme_icon(SNAME("Back"), SNAME("EditorIcons"))); } - members_overview_alphabeta_sort_button->set_icon(get_theme_icon("Sort", "EditorIcons")); + members_overview_alphabeta_sort_button->set_icon(get_theme_icon(SNAME("Sort"), SNAME("EditorIcons"))); - filter_scripts->set_right_icon(get_theme_icon("Search", "EditorIcons")); - filter_methods->set_right_icon(get_theme_icon("Search", "EditorIcons")); + filter_scripts->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + filter_methods->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filename->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("normal", "LineEdit")); + filename->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))); recent_scripts->set_as_minsize(); + + if (is_inside_tree()) { + _update_script_colors(); + _update_script_names(); + } } break; case NOTIFICATION_READY: { @@ -1610,11 +1576,11 @@ void ScriptEditor::edited_scene_changed() { } void ScriptEditor::notify_script_close(const Ref<Script> &p_script) { - emit_signal("script_close", p_script); + emit_signal(SNAME("script_close"), p_script); } void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { - emit_signal("editor_script_changed", p_script); + emit_signal(SNAME("editor_script_changed"), p_script); } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { @@ -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,11 +1794,10 @@ 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"); - Color cold_color = get_theme_color("font_color", "Editor"); + Color hot_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + Color cold_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); for (int i = 0; i < script_list->get_item_count(); i++) { int c = script_list->get_item_metadata(i); @@ -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; } @@ -1984,7 +1946,7 @@ void ScriptEditor::_update_script_names() { EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); if (eh) { String name = eh->get_class(); - Ref<Texture2D> icon = get_theme_icon("Help", "EditorIcons"); + Ref<Texture2D> icon = get_theme_icon(SNAME("Help"), SNAME("EditorIcons")); String tooltip = vformat(TTR("%s Class Reference"), name); _ScriptEditorItemData sd; @@ -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(); } @@ -2525,8 +2554,8 @@ void ScriptEditor::_tree_changed() { } waiting_update_names = true; - call_deferred("_update_script_names"); - call_deferred("_update_script_connections"); + call_deferred(SNAME("_update_script_names")); + call_deferred(SNAME("_update_script_connections")); } void ScriptEditor::_script_split_dragged(float) { @@ -2552,7 +2581,7 @@ Variant ScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { EditorHelp *eh = Object::cast_to<EditorHelp>(cur_node); if (eh) { preview_name = eh->get_class(); - preview_icon = get_theme_icon("Help", "EditorIcons"); + preview_icon = get_theme_icon(SNAME("Help"), SNAME("EditorIcons")); } if (!preview_icon.is_null()) { @@ -2706,6 +2735,8 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } void ScriptEditor::_unhandled_key_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (!is_visible_in_tree() || !p_event->is_pressed() || p_event->is_echo()) { return; } @@ -2737,7 +2768,7 @@ void ScriptEditor::_script_list_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventMouseButton> mb = ev; if (mb.is_valid() && mb->is_pressed()) { switch (mb->get_button_index()) { - case BUTTON_MIDDLE: { + case MOUSE_BUTTON_MIDDLE: { // Right-click selects automatically; middle-click does not. int idx = script_list->get_item_at_position(mb->get_position(), true); if (idx >= 0) { @@ -2747,9 +2778,11 @@ void ScriptEditor::_script_list_gui_input(const Ref<InputEvent> &ev) { } } break; - case BUTTON_RIGHT: { + case MOUSE_BUTTON_RIGHT: { _make_script_list_context_menu(); } break; + default: + break; } } } @@ -3172,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); @@ -3255,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); @@ -3329,7 +3362,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { filename = memnew(Label); filename->set_clip_text(true); filename->set_h_size_flags(SIZE_EXPAND_FILL); - filename->add_theme_style_override("normal", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("normal", "LineEdit")); + filename->add_theme_style_override("normal", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))); buttons_hbox->add_child(filename); members_overview_alphabeta_sort_button = memnew(Button); @@ -3361,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); @@ -3497,7 +3538,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { erase_tab_confirm = memnew(ConfirmationDialog); erase_tab_confirm->get_ok_button()->set_text(TTR("Save")); erase_tab_confirm->add_button(TTR("Discard"), DisplayServer::get_singleton()->get_swap_cancel_ok(), "discard"); - erase_tab_confirm->connect("confirmed", callable_mp(this, &ScriptEditor::_close_current_tab)); + erase_tab_confirm->connect("confirmed", callable_mp(this, &ScriptEditor::_close_current_tab), varray(true)); erase_tab_confirm->connect("custom_action", callable_mp(this, &ScriptEditor::_close_discard_current_tab)); add_child(erase_tab_confirm); @@ -3571,8 +3612,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { ScriptServer::edit_request_func = _open_script_request; - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox("ScriptEditorPanel", "EditorStyles")); - tab_container->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox("ScriptEditor", "EditorStyles")); + add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); + tab_container->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditor"), SNAME("EditorStyles"))); } ScriptEditor::~ScriptEditor() { @@ -3670,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 09ed3854ea..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,8 +328,9 @@ 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(); + void _close_current_tab(bool p_save = true); void _close_discard_current_tab(const String &p_str); void _close_docs_tab(); void _close_other_tabs(); @@ -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 b6df66b8af..1a6dfa273e 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -66,7 +66,7 @@ void ConnectionInfoDialog::popup_connections(String p_method, Vector<Node *> p_n node_item->set_text(1, connection.signal.get_name()); Control *p = Object::cast_to<Control>(get_parent()); - node_item->set_icon(1, p->get_theme_icon("Slot", "EditorIcons")); + node_item->set_icon(1, p->get_theme_icon(SNAME("Slot"), SNAME("EditorIcons"))); node_item->set_selectable(1, false); node_item->set_editable(1, false); @@ -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()) { @@ -149,7 +147,7 @@ void ScriptTextEditor::set_edited_resource(const RES &p_res) { code_editor->get_text_editor()->clear_undo_history(); code_editor->get_text_editor()->tag_saved_version(); - emit_signal("name_changed"); + emit_signal(SNAME("name_changed")); code_editor->update_line_and_column(); } @@ -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(); @@ -497,7 +440,7 @@ void ScriptTextEditor::_validate_script() { String target_path = base == connection.callable.get_object() ? base_path : base_path + "/" + base->get_path_to(Object::cast_to<Node>(connection.callable.get_object())); warnings_panel->push_cell(); - warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor")); + warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); warnings_panel->add_text(vformat(TTR("Missing connected method '%s' for signal '%s' from node '%s' to node '%s'."), connection.callable.get_method(), connection.signal.get_name(), source_path, target_path)); warnings_panel->pop(); // Color. warnings_panel->pop(); // Cell. @@ -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); @@ -521,7 +465,7 @@ void ScriptTextEditor::_validate_script() { warnings_panel->push_cell(); warnings_panel->push_meta(ignore_meta); warnings_panel->push_color( - warnings_panel->get_theme_color("accent_color", "Editor").lerp(warnings_panel->get_theme_color("mono_color", "Editor"), 0.5)); + warnings_panel->get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(warnings_panel->get_theme_color(SNAME("mono_color"), SNAME("Editor")), 0.5)); warnings_panel->add_text(TTR("[Ignore]")); warnings_panel->pop(); // Color. warnings_panel->pop(); // Meta ignore. @@ -529,7 +473,7 @@ void ScriptTextEditor::_validate_script() { warnings_panel->push_cell(); warnings_panel->push_meta(w.start_line - 1); - warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor")); + warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); warnings_panel->add_text(TTR("Line") + " " + itos(w.start_line)); warnings_panel->add_text(" (" + w.string_code + "):"); warnings_panel->pop(); // Color. @@ -542,28 +486,57 @@ 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(SNAME("error_color"), SNAME("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); } } - emit_signal("name_changed"); - emit_signal("edited_script_changed"); + emit_signal(SNAME("name_changed")); + emit_signal(SNAME("edited_script_changed")); } void ScriptTextEditor::_update_bookmark_list() { @@ -601,8 +574,7 @@ void ScriptTextEditor::_bookmark_item_pressed(int p_idx) { if (p_idx < 4) { // Any item before the separator. _edit_option(bookmarks_menu->get_item_id(p_idx)); } else { - code_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); - code_editor->get_text_editor()->call_deferred("center_viewport_to_cursor"); //Need to be deferred, because goto uses call_deferred(). + code_editor->goto_line_centered(bookmarks_menu->get_item_metadata(p_idx)); } } @@ -753,7 +725,7 @@ void ScriptTextEditor::_breakpoint_item_pressed(int p_idx) { _edit_option(breakpoints_menu->get_item_id(p_idx)); } else { code_editor->goto_line(breakpoints_menu->get_item_metadata(p_idx)); - code_editor->get_text_editor()->call_deferred("center_viewport_to_cursor"); //Need to be deferred, because goto uses call_deferred(). + code_editor->get_text_editor()->call_deferred(SNAME("center_viewport_to_cursor")); //Need to be deferred, because goto uses call_deferred(). } } @@ -788,14 +760,14 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c switch (result.type) { case ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION: { if (result.script.is_valid()) { - emit_signal("request_open_script_at_line", result.script, result.location - 1); + emit_signal(SNAME("request_open_script_at_line"), result.script, result.location - 1); } else { - emit_signal("request_save_history"); - _goto_line(result.location - 1); + emit_signal(SNAME("request_save_history")); + goto_line_centered(result.location - 1); } } break; case ScriptLanguage::LookupResult::RESULT_CLASS: { - emit_signal("go_to_help", "class_name:" + result.class_name); + emit_signal(SNAME("go_to_help"), "class_name:" + result.class_name); } break; case ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT: { StringName cname = result.class_name; @@ -810,11 +782,11 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c } } - emit_signal("go_to_help", "class_constant:" + result.class_name + ":" + result.class_member); + emit_signal(SNAME("go_to_help"), "class_constant:" + result.class_name + ":" + result.class_member); } break; case ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY: { - emit_signal("go_to_help", "class_property:" + result.class_name + ":" + result.class_member); + emit_signal(SNAME("go_to_help"), "class_property:" + result.class_name + ":" + result.class_member); } break; case ScriptLanguage::LookupResult::RESULT_CLASS_METHOD: { @@ -829,7 +801,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c } } - emit_signal("go_to_help", "class_method:" + result.class_name + ":" + result.class_member); + emit_signal(SNAME("go_to_help"), "class_method:" + result.class_name + ":" + result.class_member); } break; case ScriptLanguage::LookupResult::RESULT_CLASS_ENUM: { @@ -845,11 +817,11 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c } } - emit_signal("go_to_help", "class_enum:" + result.class_name + ":" + result.class_member); + emit_signal(SNAME("go_to_help"), "class_enum:" + result.class_name + ":" + result.class_member); } break; case ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE: { - emit_signal("go_to_help", "class_global:" + result.class_name + ":" + result.class_member); + emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member); } break; } } else if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { @@ -962,7 +934,7 @@ void ScriptTextEditor::_update_connected_methods() { if (name == connection.callable.get_method()) { line = functions[j].get_slice(":", 1).to_int() - 1; text_edit->set_line_gutter_metadata(line, connection_gutter, connection.callable.get_method()); - text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon("Slot", "EditorIcons")); + text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("Slot"), SNAME("EditorIcons"))); text_edit->set_line_gutter_clickable(line, connection_gutter, true); methods_found.insert(connection.callable.get_method()); break; @@ -1032,27 +1004,27 @@ void ScriptTextEditor::_edit_option(int p_op) { switch (p_op) { case EDIT_UNDO: { tx->undo(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_REDO: { tx->redo(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_CUT: { tx->cut(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_COPY: { tx->copy(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_PASTE: { tx->paste(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_SELECT_ALL: { tx->select_all(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_MOVE_LINE_UP: { code_editor->move_lines_up(); @@ -1066,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; @@ -1074,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: { @@ -1098,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(); @@ -1191,12 +1163,12 @@ void ScriptTextEditor::_edit_option(int p_op) { // Yep, because it doesn't make sense to instance this dialog for every single script open... // So this will be delegated to the ScriptEditor. - emit_signal("search_in_files_requested", selected_text); + emit_signal(SNAME("search_in_files_requested"), selected_text); } break; case REPLACE_IN_FILES: { String selected_text = code_editor->get_text_editor()->get_selection_text(); - emit_signal("replace_in_files_requested", selected_text); + emit_signal(SNAME("replace_in_files_requested"), selected_text); } break; case SEARCH_LOCATE_FUNCTION: { quick_open->popup_dialog(get_functions()); @@ -1290,7 +1262,7 @@ void ScriptTextEditor::_edit_option(int p_op) { text = tx->get_word_under_cursor(); } if (text != "") { - emit_signal("request_help", text); + emit_signal(SNAME("request_help"), text); } } break; case LOOKUP_SYMBOL: { @@ -1365,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); } @@ -1380,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; @@ -1403,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(); } @@ -1464,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); @@ -1517,7 +1504,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { bool create_menu = false; CodeEdit *tx = code_editor->get_text_editor(); - if (mb.is_valid() && mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { local_pos = mb->get_global_position() - tx->get_global_position(); create_menu = true; } else if (k.is_valid() && k->get_keycode() == KEY_MENU) { @@ -1557,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; @@ -1683,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)); @@ -1698,11 +1686,18 @@ void ScriptTextEditor::_enable_code_editor() { editor_box->add_child(warnings_panel); warnings_panel->add_theme_font_override( - "normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts")); + "normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); warnings_panel->add_theme_font_size_override( - "normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts")); + "normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("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(SNAME("main"), SNAME("EditorFonts"))); + errors_panel->add_theme_font_size_override( + "normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("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)); @@ -1722,6 +1717,9 @@ void ScriptTextEditor::_enable_code_editor() { color_picker->set_raw_mode(true); } + int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); + color_picker->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); + quick_open = memnew(ScriptEditorQuickOpen); quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line)); add_child(quick_open); @@ -1762,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); @@ -1821,7 +1819,7 @@ ScriptTextEditor::ScriptTextEditor() { code_editor->get_text_editor()->set_gutter_name(connection_gutter, "connection_gutter"); code_editor->get_text_editor()->set_gutter_draw(connection_gutter, false); code_editor->get_text_editor()->set_gutter_overwritable(connection_gutter, true); - code_editor->get_text_editor()->set_gutter_type(connection_gutter, TextEdit::GUTTER_TPYE_ICON); + code_editor->get_text_editor()->set_gutter_type(connection_gutter, TextEdit::GUTTER_TYPE_ICON); warnings_panel = memnew(RichTextLabel); warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); @@ -1831,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); @@ -1858,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); @@ -1893,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); @@ -1929,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 c8a46715ad..0d65dec87b 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(SNAME("set_h_scroll"), 0); + get_text_editor()->call_deferred(SNAME("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(SNAME("main"), SNAME("EditorFonts"))); + warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); + } } void ShaderTextEditor::_check_shader_mode() { @@ -205,7 +195,7 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptCo ShaderLanguage sl; String calltip; - sl.complete(p_code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type, r_options, calltip); + sl.complete(p_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, r_options, calltip); get_text_editor()->set_code_hint(calltip); } @@ -219,32 +209,85 @@ void ShaderTextEditor::_validate_script() { ShaderLanguage sl; - 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())), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type); + 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) { String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); 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(""); } - emit_signal("script_changed"); + 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(SNAME("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(SNAME("warning_color"), SNAME("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(); @@ -343,7 +380,7 @@ void ShaderEditor::_menu_option(int p_option) { } break; } if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { - shader_editor->get_text_editor()->call_deferred("grab_focus"); + shader_editor->get_text_editor()->call_deferred(SNAME("grab_focus")); } } @@ -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; @@ -399,7 +484,7 @@ void ShaderEditor::_check_for_external_edit() { if (use_autoreload) { _reload_shader_from_disk(); } else { - disk_changed->call_deferred("popup_centered"); + disk_changed->call_deferred(SNAME("popup_centered")); } } } @@ -460,7 +545,7 @@ void ShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventMouseButton> mb = ev; if (mb.is_valid()) { - if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { int col, row; CodeEdit *tx = shader_editor->get_text_editor(); tx->_get_mouse_pos(mb->get_global_position() - tx->get_global_position(), row, col); @@ -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)); @@ -636,7 +728,7 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { help_menu = memnew(MenuButton); help_menu->set_text(TTR("Help")); help_menu->set_switch_on_hover(true); - help_menu->get_popup()->add_icon_item(p_node->get_gui_base()->get_theme_icon("Instance", "EditorIcons"), TTR("Online Docs"), HELP_DOCS); + help_menu->get_popup()->add_icon_item(p_node->get_gui_base()->get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Online Docs"), HELP_DOCS); help_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); add_child(main_container); @@ -645,8 +737,29 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { hbc->add_child(edit_menu); 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); + hbc->add_theme_style_override("panel", p_node->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); + + 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..66d2b36be1 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -66,9 +66,9 @@ void ShaderFileEditor::_version_selected(int p_option) { Ref<Texture2D> icon; if (bytecode->get_stage_compile_error(RD::ShaderStage(i)) != String()) { - icon = get_theme_icon("ImportFail", "EditorIcons"); + icon = get_theme_icon(SNAME("ImportFail"), SNAME("EditorIcons")); } else { - icon = get_theme_icon("ImportCheck", "EditorIcons"); + icon = get_theme_icon(SNAME("ImportCheck"), SNAME("EditorIcons")); } stages[i]->set_icon(icon); @@ -95,7 +95,7 @@ void ShaderFileEditor::_version_selected(int p_option) { String error = bytecode->get_stage_compile_error(stage); - error_text->push_font(get_theme_font("source", "EditorFonts")); + error_text->push_font(get_theme_font(SNAME("source"), SNAME("EditorFonts"))); if (error == String()) { error_text->add_text(TTR("Shader stage compiled without errors.")); @@ -111,7 +111,7 @@ void ShaderFileEditor::_update_options() { stage_hb->hide(); versions->hide(); error_text->clear(); - error_text->push_font(get_theme_font("source", "EditorFonts")); + error_text->push_font(get_theme_font(SNAME("source"), SNAME("EditorFonts"))); error_text->add_text(vformat(TTR("File structure for '%s' contains unrecoverable errors:\n\n"), shader_file->get_path().get_file())); error_text->add_text(shader_file->get_base_error()); return; @@ -154,9 +154,9 @@ void ShaderFileEditor::_update_options() { } if (failed) { - icon = get_theme_icon("ImportFail", "EditorIcons"); + icon = get_theme_icon(SNAME("ImportFail"), SNAME("EditorIcons")); } else { - icon = get_theme_icon("ImportCheck", "EditorIcons"); + icon = get_theme_icon(SNAME("ImportCheck"), SNAME("EditorIcons")); } versions->add_item(title, icon); @@ -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_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp index 44916e1d46..7ef680d7ef 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.cpp +++ b/editor/plugins/skeleton_2d_editor_plugin.cpp @@ -96,7 +96,7 @@ Skeleton2DEditor::Skeleton2DEditor() { CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options); options->set_text(TTR("Skeleton2D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Skeleton2D", "EditorIcons")); + options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton2D"), SNAME("EditorIcons"))); options->get_popup()->add_item(TTR("Make Rest Pose (From Bones)"), MENU_OPTION_MAKE_REST); options->get_popup()->add_separator(); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 121ccfa417..4202d8b611 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -44,7 +44,7 @@ #include "scene/resources/sphere_shape_3d.h" void BoneTransformEditor::create_editors() { - const Color section_color = get_theme_color("prop_subsection", "Editor"); + const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); section = memnew(EditorInspectorSection); section->setup("trf_properties", label, this, section_color, true); @@ -53,7 +53,7 @@ void BoneTransformEditor::create_editors() { key_button = memnew(Button); key_button->set_text(TTR("Key Transform")); key_button->set_visible(keyable); - key_button->set_icon(get_theme_icon("Key", "EditorIcons")); + key_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); key_button->set_flat(true); section->get_vbox()->add_child(key_button); @@ -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); @@ -113,19 +113,19 @@ void BoneTransformEditor::_notification(int p_what) { [[fallthrough]]; } case NOTIFICATION_SORT_CHILDREN: { - const Ref<Font> font = get_theme_font("font", "Tree"); - int font_size = get_theme_font_size("font_size", "Tree"); + const Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); Point2 buffer; - buffer.x += get_theme_constant("inspector_margin", "Editor"); + buffer.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); buffer.y += font->get_height(font_size); - buffer.y += get_theme_constant("vseparation", "Tree"); + buffer.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); const float vector_height = translation_property->get_size().y; const float transform_height = transform_property->get_size().y; const float button_height = key_button->get_size().y; - const float width = get_size().x - get_theme_constant("inspector_margin", "Editor"); + const float width = get_size().x - get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); Vector<Rect2> input_rects; if (keyable && section->get_vbox()->is_visible()) { input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height))); @@ -155,7 +155,7 @@ void BoneTransformEditor::_notification(int p_what) { break; } case NOTIFICATION_DRAW: { - const Color dark_color = get_theme_color("dark_color_2", "Editor"); + const Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); for (int i = 0; i < 5; ++i) { draw_rect(background_rects[i], dark_color); @@ -167,39 +167,42 @@ void BoneTransformEditor::_notification(int p_what) { } void BoneTransformEditor::_value_changed(const double p_value) { - if (updating) + if (updating) { return; + } - Transform tform = compute_transform_from_vector3s(); + Transform3D tform = compute_transform_from_vector3s(); _change_transform(tform); } void BoneTransformEditor::_value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) + 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) { - if (updating) +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())); @@ -222,32 +225,36 @@ void BoneTransformEditor::update_enabled_checkbox() { } void BoneTransformEditor::_update_properties() { - if (updating) + if (updating) { return; + } - if (skeleton == nullptr) + if (skeleton == nullptr) { return; + } updating = true; - Transform tform = skeleton->get(property); + Transform3D tform = skeleton->get(property); _update_transform_properties(tform); } void BoneTransformEditor::_update_custom_pose_properties() { - if (updating) + if (updating) { return; + } - if (skeleton == nullptr) + if (skeleton == nullptr) { return; + } 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)); @@ -287,17 +294,19 @@ void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { } void BoneTransformEditor::_key_button_pressed() { - if (skeleton == nullptr) + if (skeleton == nullptr) { return; + } const BoneId bone_id = property.get_slicec('/', 1).to_int(); const String name = skeleton->get_bone_name(bone_id); - if (name.is_empty()) + if (name.is_empty()) { return; + } // 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); } @@ -371,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); @@ -383,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); @@ -405,8 +414,9 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { TreeItem *selected = joint_tree->get_selected(); - if (!selected) + if (!selected) { return Variant(); + } Ref<Texture> icon = selected->get_icon(0); @@ -431,27 +441,32 @@ Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_fro bool Skeleton3DEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { TreeItem *target = joint_tree->get_item_at_position(p_point); - if (!target) + if (!target) { return false; + } const String path = target->get_metadata(0); - if (!path.begins_with("bones/")) + if (!path.begins_with("bones/")) { return false; + } TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node"]); - if (target == selected) + if (target == selected) { return false; + } const String path2 = target->get_metadata(0); - if (!path2.begins_with("bones/")) + if (!path2.begins_with("bones/")) { return false; + } return true; } void Skeleton3DEditor::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)) + if (!can_drop_data_fw(p_point, p_data, p_from)) { return; + } TreeItem *target = joint_tree->get_item_at_position(p_point); TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node"]); @@ -500,6 +515,8 @@ void Skeleton3DEditor::_joint_tree_selection_changed() { rest_editor->set_target(bone_path + "rest"); custom_pose_editor->set_target(bone_path + "custom_pose"); + _update_properties(); + pose_editor->set_visible(true); rest_editor->set_visible(true); custom_pose_editor->set_visible(true); @@ -510,19 +527,23 @@ void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { } void Skeleton3DEditor::_update_properties() { - if (rest_editor) + if (rest_editor) { rest_editor->_update_properties(); - if (pose_editor) + } + if (pose_editor) { pose_editor->_update_properties(); - if (custom_pose_editor) + } + if (custom_pose_editor) { custom_pose_editor->_update_custom_pose_properties(); + } } void Skeleton3DEditor::update_joint_tree() { joint_tree->clear(); - if (skeleton == nullptr) + if (skeleton == nullptr) { return; + } TreeItem *root = joint_tree->create_item(); @@ -531,7 +552,7 @@ void Skeleton3DEditor::update_joint_tree() { items.insert(-1, root); const Vector<int> &joint_porder = skeleton->get_bone_process_orders(); - Ref<Texture> bone_icon = get_theme_icon("BoneAttachment3D", "EditorIcons"); + Ref<Texture> bone_icon = get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")); for (int i = 0; i < joint_porder.size(); ++i) { const int b_idx = joint_porder[i]; @@ -563,13 +584,13 @@ void Skeleton3DEditor::create_editors() { Node3DEditor::get_singleton()->add_control_to_menu_panel(options); options->set_text(TTR("Skeleton3D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Skeleton3D", "EditorIcons")); + options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option)); - const Color section_color = get_theme_color("prop_subsection", "Editor"); + const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); EditorInspectorSection *bones_section = memnew(EditorInspectorSection); bones_section->setup("bones", "Bones", skeleton, section_color, true); @@ -645,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); } @@ -679,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/skeleton_ik_3d_editor_plugin.cpp b/editor/plugins/skeleton_ik_3d_editor_plugin.cpp index 2da49c1c0b..85632cf481 100644 --- a/editor/plugins/skeleton_ik_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_ik_3d_editor_plugin.cpp @@ -83,7 +83,7 @@ void SkeletonIK3DEditorPlugin::_bind_methods() { SkeletonIK3DEditorPlugin::SkeletonIK3DEditorPlugin(EditorNode *p_node) { editor = p_node; play_btn = memnew(Button); - play_btn->set_icon(editor->get_gui_base()->get_theme_icon("Play", "EditorIcons")); + play_btn->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); play_btn->set_text(TTR("Play IK")); play_btn->set_toggle_mode(true); play_btn->hide(); diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 4949d2b9b7..0f889ce33d 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -171,7 +171,7 @@ void Sprite2DEditor::_update_mesh_data() { return; } - Ref<Image> image = texture->get_data(); + Ref<Image> image = texture->get_image(); ERR_FAIL_COND(image.is_null()); if (image->is_compressed()) { @@ -179,14 +179,14 @@ void Sprite2DEditor::_update_mesh_data() { } Rect2 rect; - if (node->is_region()) { + if (node->is_region_enabled()) { rect = node->get_region_rect(); } else { rect.size = Size2(image->get_width(), image->get_height()); } 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()); @@ -506,7 +506,7 @@ Sprite2DEditor::Sprite2DEditor() { CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options); options->set_text(TTR("Sprite2D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Sprite2D", "EditorIcons")); + options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Sprite2D"), SNAME("EditorIcons"))); options->get_popup()->add_item(TTR("Convert to Mesh2D"), MENU_OPTION_CONVERT_TO_MESH_2D); options->get_popup()->add_item(TTR("Convert to Polygon2D"), MENU_OPTION_CONVERT_TO_POLYGON_2D); @@ -521,7 +521,7 @@ Sprite2DEditor::Sprite2DEditor() { debug_uv_dialog = memnew(ConfirmationDialog); debug_uv_dialog->get_ok_button()->set_text(TTR("Create Mesh2D")); - debug_uv_dialog->set_title("Mesh 2D Preview"); + debug_uv_dialog->set_title(TTR("Mesh 2D Preview")); VBoxContainer *vb = memnew(VBoxContainer); debug_uv_dialog->add_child(vb); ScrollContainer *scroll = memnew(ScrollContainer); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 0547f99079..9bb8fbacbb 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -65,13 +65,11 @@ void SpriteFramesEditor::_sheet_preview_draw() { int x = i * width; split_sheet_preview->draw_line(Point2(x, 0), Point2(x, size.height), Color(1, 1, 1, a)); split_sheet_preview->draw_line(Point2(x + 1, 0), Point2(x + 1, size.height), Color(0, 0, 0, a)); - - for (int j = 1; j < v; j++) { - int y = j * height; - - split_sheet_preview->draw_line(Point2(0, y), Point2(size.width, y), Color(1, 1, 1, a)); - split_sheet_preview->draw_line(Point2(0, y + 1), Point2(size.width, y + 1), Color(0, 0, 0, a)); - } + } + for (int i = 1; i < v; i++) { + int y = i * height; + split_sheet_preview->draw_line(Point2(0, y), Point2(size.width, y), Color(1, 1, 1, a)); + split_sheet_preview->draw_line(Point2(0, y + 1), Point2(size.width, y + 1), Color(0, 0, 0, a)); } if (frames_selected.size() == 0) { @@ -80,7 +78,7 @@ void SpriteFramesEditor::_sheet_preview_draw() { return; } - Color accent = get_theme_color("accent_color", "Editor"); + Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { int idx = E->get(); @@ -103,19 +101,18 @@ void SpriteFramesEditor::_sheet_preview_draw() { } void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { - Ref<InputEventMouseButton> mb = p_event; - - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { - Size2i size = split_sheet_preview->get_size(); - int h = split_sheet_h->get_value(); - int v = split_sheet_v->get_value(); + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + 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 +121,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 +144,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 +186,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() == 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() == 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 +225,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)); @@ -219,7 +255,8 @@ void SpriteFramesEditor::_sheet_zoom_out() { } void SpriteFramesEditor::_sheet_zoom_reset() { - sheet_zoom = 1.f; + // 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.0f, EDSCALE); Size2 texture_size = split_sheet_preview->get_texture()->get_size(); split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); } @@ -251,10 +288,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 @@ -269,27 +306,27 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { void SpriteFramesEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - load->set_icon(get_theme_icon("Load", "EditorIcons")); - load_sheet->set_icon(get_theme_icon("SpriteSheet", "EditorIcons")); - copy->set_icon(get_theme_icon("ActionCopy", "EditorIcons")); - paste->set_icon(get_theme_icon("ActionPaste", "EditorIcons")); - empty->set_icon(get_theme_icon("InsertBefore", "EditorIcons")); - empty2->set_icon(get_theme_icon("InsertAfter", "EditorIcons")); - move_up->set_icon(get_theme_icon("MoveLeft", "EditorIcons")); - 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_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_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); + load->set_icon(get_theme_icon(SNAME("Load"), SNAME("EditorIcons"))); + load_sheet->set_icon(get_theme_icon(SNAME("SpriteSheet"), SNAME("EditorIcons"))); + copy->set_icon(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); + paste->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons"))); + empty->set_icon(get_theme_icon(SNAME("InsertBefore"), SNAME("EditorIcons"))); + empty2->set_icon(get_theme_icon(SNAME("InsertAfter"), SNAME("EditorIcons"))); + move_up->set_icon(get_theme_icon(SNAME("MoveLeft"), SNAME("EditorIcons"))); + move_down->set_icon(get_theme_icon(SNAME("MoveRight"), SNAME("EditorIcons"))); + _delete->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); + zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); + zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); + new_anim->set_icon(get_theme_icon(SNAME("New"), SNAME("EditorIcons"))); + remove_anim->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + split_sheet_zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); + split_sheet_zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); + split_sheet_zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("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(SNAME("bg"), SNAME("Tree"))); } break; case NOTIFICATION_READY: { add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up. @@ -512,7 +549,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); } } @@ -693,11 +730,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() == 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() == 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(); @@ -732,7 +769,7 @@ void SpriteFramesEditor::_zoom_out() { } void SpriteFramesEditor::_zoom_reset() { - thumbnail_zoom = 1.0f; + 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)); } @@ -953,7 +990,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); @@ -963,9 +1000,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() { @@ -1085,11 +1122,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); @@ -1182,16 +1221,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); @@ -1208,12 +1247,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); @@ -1229,13 +1270,14 @@ SpriteFramesEditor::SpriteFramesEditor() { // Config scale. scale_ratio = 1.2f; - thumbnail_default_size = 96; - thumbnail_zoom = 1.0f; - max_thumbnail_zoom = 8.0f; - min_thumbnail_zoom = 0.1f; - sheet_zoom = 1.0f; - max_sheet_zoom = 16.0f; - min_sheet_zoom = 0.01f; + 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.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/sub_viewport_preview_editor_plugin.cpp b/editor/plugins/sub_viewport_preview_editor_plugin.cpp new file mode 100644 index 0000000000..75c47bda2e --- /dev/null +++ b/editor/plugins/sub_viewport_preview_editor_plugin.cpp @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* sub_viewport_preview_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 "sub_viewport_preview_editor_plugin.h" + +bool EditorInspectorPluginSubViewportPreview::can_handle(Object *p_object) { + return Object::cast_to<SubViewport>(p_object) != nullptr; +} + +void EditorInspectorPluginSubViewportPreview::parse_begin(Object *p_object) { + SubViewport *sub_viewport = Object::cast_to<SubViewport>(p_object); + + TexturePreview *sub_viewport_preview = memnew(TexturePreview(sub_viewport->get_texture(), false)); + // Otherwise `sub_viewport_preview`'s `texture_display` doesn't update properly when `sub_viewport`'s size changes. + sub_viewport->connect("size_changed", callable_mp((CanvasItem *)sub_viewport_preview->get_texture_display(), &CanvasItem::update)); + add_custom_control(sub_viewport_preview); +} + +SubViewportPreviewEditorPlugin::SubViewportPreviewEditorPlugin(EditorNode *p_node) { + Ref<EditorInspectorPluginSubViewportPreview> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/sub_viewport_preview_editor_plugin.h b/editor/plugins/sub_viewport_preview_editor_plugin.h new file mode 100644 index 0000000000..03b8b678d1 --- /dev/null +++ b/editor/plugins/sub_viewport_preview_editor_plugin.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* sub_viewport_preview_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 SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H +#define SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/plugins/texture_editor_plugin.h" +#include "scene/main/viewport.h" + +class EditorInspectorPluginSubViewportPreview : public EditorInspectorPluginTexture { + GDCLASS(EditorInspectorPluginSubViewportPreview, EditorInspectorPluginTexture); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class SubViewportPreviewEditorPlugin : public EditorPlugin { + GDCLASS(SubViewportPreviewEditorPlugin, EditorPlugin); + +public: + virtual String get_name() const override { return "SubViewportPreview"; } + + SubViewportPreviewEditorPlugin(EditorNode *p_node); +}; + +#endif // SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index b88f1c91e6..faf287b9bd 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() { @@ -151,7 +100,7 @@ void TextEditor::set_edited_resource(const RES &p_res) { code_editor->get_text_editor()->clear_undo_history(); code_editor->get_text_editor()->tag_saved_version(); - emit_signal("name_changed"); + emit_signal(SNAME("name_changed")); code_editor->update_line_and_column(); } @@ -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(); } @@ -196,8 +149,8 @@ void TextEditor::reload_text() { } void TextEditor::_validate_script() { - emit_signal("name_changed"); - emit_signal("edited_script_changed"); + emit_signal(SNAME("name_changed")); + emit_signal(SNAME("edited_script_changed")); } void TextEditor::_update_bookmark_list() { @@ -328,33 +281,37 @@ 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(); switch (p_op) { case EDIT_UNDO: { tx->undo(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_REDO: { tx->redo(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_CUT: { tx->cut(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_COPY: { tx->copy(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_PASTE: { tx->paste(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_SELECT_ALL: { tx->select_all(); - tx->call_deferred("grab_focus"); + tx->call_deferred(SNAME("grab_focus")); } break; case EDIT_MOVE_LINE_UP: { code_editor->move_lines_up(); @@ -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: { @@ -421,12 +378,12 @@ void TextEditor::_edit_option(int p_op) { // Yep, because it doesn't make sense to instance this dialog for every single script open... // So this will be delegated to the ScriptEditor. - emit_signal("search_in_files_requested", selected_text); + emit_signal(SNAME("search_in_files_requested"), selected_text); } break; case REPLACE_IN_FILES: { String selected_text = code_editor->get_text_editor()->get_selection_text(); - emit_signal("replace_in_files_requested", selected_text); + emit_signal(SNAME("replace_in_files_requested"), selected_text); } break; case SEARCH_GOTO_LINE: { goto_line_dialog->popup_find_line(tx); @@ -469,14 +426,14 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventMouseButton> mb = ev; if (mb.is_valid()) { - if (mb->get_button_index() == BUTTON_RIGHT) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { int col, row; CodeEdit *tx = code_editor->get_text_editor(); 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..3987cdd6a0 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -50,7 +50,7 @@ void Texture3DEditor::_notification(int p_what) { } if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon("Checkerboard", "EditorIcons"); + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); Size2 size = get_size(); draw_texture_rect(checkerboard, Rect2(Point2(), size), true); @@ -77,17 +77,18 @@ void Texture3DEditor::_update_material() { } void Texture3DEditor::_make_shaders() { - String shader_3d = "" - "shader_type canvas_item;\n" - "uniform sampler3D tex;\n" - "uniform float layer;\n" - "void fragment() {\n" - " COLOR = textureLod(tex,vec3(UV,layer),0.0);\n" - "}"; - - shader.instance(); - shader->set_code(shader_3d); - material.instance(); + shader.instantiate(); + shader->set_code(R"( +shader_type canvas_item; + +uniform sampler3D tex; +uniform float layer; + +void fragment() { + COLOR = textureLod(tex, vec3(UV, layer), 0.0); +} +)"); + material.instantiate(); material->set_shader(shader); } @@ -207,6 +208,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 253f8878d2..2eee9b8de3 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -30,136 +30,88 @@ #include "texture_editor_plugin.h" -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "editor/editor_settings.h" +#include "editor/editor_scale.h" -void TextureEditor::_gui_input(Ref<InputEvent> p_event) { +TextureRect *TexturePreview::get_texture_display() { + return texture_display; } -void TextureEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); +void TexturePreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (!is_inside_tree()) { + // TODO: This is a workaround because `NOTIFICATION_THEME_CHANGED` + // is getting called for some reason when the `TexturePreview` is + // getting destroyed, which causes `get_theme_font()` to return `nullptr`. + // See https://github.com/godotengine/godot/issues/50743. + break; + } + + Ref<Font> metadata_label_font = get_theme_font(SNAME("expression"), SNAME("EditorFonts")); + metadata_label->add_theme_font_override("font", metadata_label_font); + + checkerboard->set_texture(get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons"))); + } break; } +} - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon("Checkerboard", "EditorIcons"); - Size2 size = get_size(); - - draw_texture_rect(checkerboard, Rect2(Point2(), size), true); - - int tex_width = texture->get_width() * size.height / texture->get_height(); - int tex_height = size.height; - - if (tex_width > size.width) { - tex_width = size.width; - tex_height = texture->get_height() * tex_width / texture->get_width(); - } - - // Prevent the texture from being unpreviewable after the rescale, so that we can still see something - if (tex_height <= 0) { - tex_height = 1; - } - if (tex_width <= 0) { - tex_width = 1; - } - - int ofs_x = (size.width - tex_width) / 2; - int ofs_y = (size.height - tex_height) / 2; - - if (Object::cast_to<CurveTexture>(*texture)) { - // In the case of CurveTextures we know they are 1 in height, so fill the preview to see the gradient - ofs_y = 0; - tex_height = size.height; - } else if (Object::cast_to<GradientTexture>(*texture)) { - ofs_y = size.height / 4.0; - tex_height = size.height / 2.0; - } +TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { + checkerboard = memnew(TextureRect); + checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); + checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED); + checkerboard->set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE); + add_child(checkerboard); - draw_texture_rect(texture, Rect2(ofs_x, ofs_y, tex_width, tex_height)); + texture_display = memnew(TextureRect); + texture_display->set_texture(p_texture); + texture_display->set_anchors_preset(TextureRect::PRESET_WIDE); + texture_display->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + texture_display->set_expand(true); + add_child(texture_display); - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + if (p_show_metadata) { + metadata_label = memnew(Label); String format; - if (Object::cast_to<ImageTexture>(*texture)) { - format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format()); - } else if (Object::cast_to<StreamTexture2D>(*texture)) { - format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*texture)->get_format()); + if (Object::cast_to<ImageTexture>(*p_texture)) { + format = Image::get_format_name(Object::cast_to<ImageTexture>(*p_texture)->get_format()); + } else if (Object::cast_to<StreamTexture2D>(*p_texture)) { + format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*p_texture)->get_format()); } else { - format = texture->get_class(); + format = p_texture->get_class(); } - String text = itos(texture->get_width()) + "x" + itos(texture->get_height()) + " " + format; - Size2 rect = font->get_string_size(text, font_size); + metadata_label->set_text(itos(p_texture->get_width()) + "x" + itos(p_texture->get_height()) + " " + format); - Vector2 draw_from = size - rect + Size2(-2, font->get_ascent(font_size) - 2); - if (draw_from.x < 0) { - draw_from.x = 0; - } + // It's okay that these colors are static since the grid color is static too. + metadata_label->add_theme_color_override("font_color", Color::named("white")); + metadata_label->add_theme_color_override("font_color_shadow", Color::named("black")); - draw_string(font, draw_from + Vector2(2, 2), text, HALIGN_LEFT, size.width, font_size, Color(0, 0, 0, 0.5)); - draw_string(font, draw_from - Vector2(2, 2), text, HALIGN_LEFT, size.width, font_size, Color(0, 0, 0, 0.5)); - draw_string(font, draw_from, text, HALIGN_LEFT, size.width, font_size, Color(1, 1, 1, 1)); - } -} + metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE); + metadata_label->add_theme_color_override("font_outline_color", Color::named("black")); + metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE); -void TextureEditor::_texture_changed() { - if (!is_visible()) { - return; - } - update(); -} + metadata_label->add_theme_constant_override("shadow_as_outline", 1); + metadata_label->set_h_size_flags(Control::SIZE_SHRINK_END); + metadata_label->set_v_size_flags(Control::SIZE_SHRINK_END); -void TextureEditor::edit(Ref<Texture2D> p_texture) { - if (!texture.is_null()) { - texture->disconnect("changed", callable_mp(this, &TextureEditor::_texture_changed)); + add_child(metadata_label); } - - texture = p_texture; - - if (!texture.is_null()) { - texture->connect("changed", callable_mp(this, &TextureEditor::_texture_changed)); - update(); - } else { - hide(); - } -} - -void TextureEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &TextureEditor::_gui_input); } -TextureEditor::TextureEditor() { - set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); - set_custom_minimum_size(Size2(1, 150)); -} - -TextureEditor::~TextureEditor() { - if (!texture.is_null()) { - texture->disconnect("changed", callable_mp(this, &TextureEditor::_texture_changed)); - } -} - -// bool EditorInspectorPluginTexture::can_handle(Object *p_object) { - return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<StreamTexture2D>(p_object) != nullptr || Object::cast_to<LargeTexture>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr; + return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<StreamTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr; } void EditorInspectorPluginTexture::parse_begin(Object *p_object) { - Texture2D *texture = Object::cast_to<Texture2D>(p_object); - if (!texture) { - return; - } - Ref<Texture2D> m(texture); + Ref<Texture> texture(Object::cast_to<Texture>(p_object)); - TextureEditor *editor = memnew(TextureEditor); - editor->edit(m); - add_custom_control(editor); + add_custom_control(memnew(TexturePreview(texture, true))); } TextureEditorPlugin::TextureEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginTexture> plugin; - plugin.instance(); + plugin.instantiate(); add_inspector_plugin(plugin); } diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index ebe8882194..c7272c83bc 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -35,21 +35,21 @@ #include "editor/editor_plugin.h" #include "scene/resources/texture.h" -class TextureEditor : public Control { - GDCLASS(TextureEditor, Control); +class TexturePreview : public MarginContainer { + GDCLASS(TexturePreview, MarginContainer); - Ref<Texture2D> texture; +private: + TextureRect *texture_display; + + TextureRect *checkerboard; + Label *metadata_label; protected: void _notification(int p_what); - void _gui_input(Ref<InputEvent> p_event); - void _texture_changed(); - static void _bind_methods(); public: - void edit(Ref<Texture2D> p_texture); - TextureEditor(); - ~TextureEditor(); + TextureRect *get_texture_display(); + TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata); }; class EditorInspectorPluginTexture : public EditorInspectorPlugin { diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 254ad3d56e..80359452ac 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -35,8 +35,10 @@ #include "editor/editor_settings.h" void TextureLayeredEditor::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { y_rot += -mm->get_relative().x * 0.01; x_rot += mm->get_relative().y * 0.01; _update_material(); @@ -56,7 +58,7 @@ void TextureLayeredEditor::_notification(int p_what) { } if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon("Checkerboard", "EditorIcons"); + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); Size2 size = get_size(); draw_texture_rect(checkerboard, Rect2(Point2(), size), true); @@ -102,46 +104,49 @@ void TextureLayeredEditor::_update_material() { } void TextureLayeredEditor::_make_shaders() { - String shader_2d_array = "" - "shader_type canvas_item;\n" - "uniform sampler2DArray tex;\n" - "uniform float layer;\n" - "void fragment() {\n" - " COLOR = textureLod(tex,vec3(UV,layer),0.0);\n" - "}"; - - shaders[0].instance(); - shaders[0]->set_code(shader_2d_array); - - String shader_cube = "" - "shader_type canvas_item;\n" - "uniform samplerCube tex;\n" - "uniform vec3 normal;\n" - "uniform mat3 rot;\n" - "void fragment() {\n" - " vec3 n = rot * normalize(vec3(normal.xy*(UV * 2.0 - 1.0),normal.z));\n" - " COLOR = textureLod(tex,n,0.0);\n" - "}"; - - shaders[1].instance(); - shaders[1]->set_code(shader_cube); - - String shader_cube_array = "" - "shader_type canvas_item;\n" - "uniform samplerCubeArray tex;\n" - "uniform vec3 normal;\n" - "uniform mat3 rot;\n" - "uniform float layer;\n" - "void fragment() {\n" - " vec3 n = rot * normalize(vec3(normal.xy*(UV * 2.0 - 1.0),normal.z));\n" - " COLOR = textureLod(tex,vec4(n,layer),0.0);\n" - "}"; - - shaders[2].instance(); - shaders[2]->set_code(shader_cube_array); + shaders[0].instantiate(); + shaders[0]->set_code(R"( +shader_type canvas_item; + +uniform sampler2DArray tex; +uniform float layer; + +void fragment() { + COLOR = textureLod(tex, vec3(UV, layer), 0.0); +} +)"); + + shaders[1].instantiate(); + shaders[1]->set_code(R"( +shader_type canvas_item; + +uniform samplerCube tex; +uniform vec3 normal; +uniform mat3 rot; + +void fragment() { + vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z)); + COLOR = textureLod(tex, n, 0.0); +} +)"); + + shaders[2].instantiate(); + shaders[2]->set_code(R"( +shader_type canvas_item; + +uniform samplerCubeArray tex; +uniform vec3 normal; +uniform mat3 rot; +uniform float layer; + +void fragment() { + vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z)); + COLOR = textureLod(tex, vec4(n, layer), 0.0); +} +)"); for (int i = 0; i < 3; i++) { - materials[i].instance(); + materials[i].instantiate(); materials[i]->set_shader(shaders[i]); } } @@ -269,6 +274,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 63255e6547..e323f1571d 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -47,14 +47,14 @@ void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) { edit_draw->draw_line( from, to, - EditorNode::get_singleton()->get_theme_base()->get_theme_color("mono_color", "Editor").inverted() * Color(1, 1, 1, 0.5), + EditorNode::get_singleton()->get_theme_base()->get_theme_color(SNAME("mono_color"), SNAME("Editor")).inverted() * Color(1, 1, 1, 0.5), Math::round(2 * EDSCALE)); while ((to - from).length_squared() > 200) { edit_draw->draw_line( from, from + line, - EditorNode::get_singleton()->get_theme_base()->get_theme_color("mono_color", "Editor"), + EditorNode::get_singleton()->get_theme_base()->get_theme_color(SNAME("mono_color"), SNAME("Editor")), Math::round(2 * EDSCALE)); from += line * 2; @@ -159,7 +159,7 @@ void TextureRegionEditor::_region_draw() { } } - Ref<Texture2D> select_handle = get_theme_icon("EditorHandle", "EditorIcons"); + Ref<Texture2D> select_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); Rect2 scroll_rect(Point2(), base_tex->get_size()); @@ -175,7 +175,7 @@ void TextureRegionEditor::_region_draw() { mtx.basis_xform(raw_endpoints[2]), mtx.basis_xform(raw_endpoints[3]) }; - Color color = get_theme_color("mono_color", "Editor"); + Color color = get_theme_color(SNAME("mono_color"), SNAME("Editor")); for (int i = 0; i < 4; i++) { int prev = (i + 3) % 4; int next = (i + 1) % 4; @@ -285,7 +285,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { - if (mb->get_button_index() == BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { if (node_ninepatch || obj_styleBox.is_valid()) { edited_margin = -1; @@ -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(); @@ -447,7 +447,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { creating = false; } - } else if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { if (drag) { drag = false; if (edited_margin >= 0) { @@ -466,9 +466,9 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { drag_index = -1; } } - } else if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { _zoom_on_position(draw_zoom * ((0.95 + (0.05 * mb->get_factor())) / 0.95), mb->get_position()); - } else if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { _zoom_on_position(draw_zoom * (1 - (0.05 * mb->get_factor())), mb->get_position()); } } @@ -476,7 +476,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { Ref<InputEventMouseMotion> mm = p_input; if (mm.is_valid()) { - if (mm->get_button_mask() & BUTTON_MASK_MIDDLE || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { + if (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { Vector2 dragged(mm->get_relative().x / draw_zoom, mm->get_relative().y / draw_zoom); hscroll->set_value(hscroll->get_value() - dragged.x); vscroll->set_value(vscroll->get_value() - dragged.y); @@ -799,12 +799,12 @@ void TextureRegionEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { - edit_draw->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree")); + edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); } break; case NOTIFICATION_READY: { - zoom_out->set_icon(get_theme_icon("ZoomLess", "EditorIcons")); - zoom_reset->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); - zoom_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); + zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons"))); + zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons"))); + zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons"))); vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); @@ -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; @@ -897,7 +906,7 @@ void TextureRegionEditor::edit(Object *p_obj) { atlas_tex = Ref<AtlasTexture>(nullptr); } edit_draw->update(); - if ((node_sprite && !node_sprite->is_region()) || (node_sprite_3d && !node_sprite_3d->is_region())) { + if ((node_sprite && !node_sprite->is_region_enabled()) || (node_sprite_3d && !node_sprite_3d->is_region_enabled())) { set_process(true); } if (!p_obj) { @@ -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); @@ -1115,7 +1123,7 @@ void TextureRegionEditorPlugin::_editor_visiblity_changed() { void TextureRegionEditorPlugin::make_visible(bool p_visible) { if (p_visible) { texture_region_button->show(); - bool is_node_configured = region_editor->is_stylebox() || region_editor->is_atlas_texture() || region_editor->is_ninepatch() || (region_editor->get_sprite() && region_editor->get_sprite()->is_region()) || (region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region()); + bool is_node_configured = region_editor->is_stylebox() || region_editor->is_atlas_texture() || region_editor->is_ninepatch() || (region_editor->get_sprite() && region_editor->get_sprite()->is_region_enabled()) || (region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region_enabled()); if ((is_node_configured && !manually_hidden) || texture_region_button->is_pressed()) { editor->make_bottom_panel_item_visible(region_editor); } diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index dfa8c04145..9338ea6ddd 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -30,911 +30,3374 @@ #include "theme_editor_plugin.h" -#include "core/os/file_access.h" -#include "core/version.h" +#include "core/os/keyboard.h" +#include "editor/editor_resource_picker.h" #include "editor/editor_scale.h" -#include "scene/gui/progress_bar.h" +#include "editor/progress_dialog.h" -void ThemeEditor::edit(const Ref<Theme> &p_theme) { - theme = p_theme; - main_panel->set_theme(p_theme); - main_container->set_theme(p_theme); +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(SNAME("Color"), SNAME("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(SNAME("MemberConstant"), SNAME("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(SNAME("Font"), SNAME("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(SNAME("FontSize"), SNAME("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(SNAME("ImageTexture"), SNAME("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(SNAME("StyleBoxFlat"), SNAME("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 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); +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 ThemeEditor::_refresh_interval() { - _propagate_redraw(main_panel); - _propagate_redraw(main_container); +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 ThemeEditor::_type_menu_cbk(int p_option) { - type_edit->set_text(type_menu->get_popup()->get_item_text(p_option)); +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 ThemeEditor::_name_menu_about_to_show() { - String fromtype = type_edit->get_text(); - List<StringName> names; +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(SNAME("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; +} - if (popup_mode == POPUP_ADD) { - switch (type_select->get_selected()) { - case 0: - Theme::get_default()->get_icon_list(fromtype, &names); +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(SNAME("StatusWarning"), SNAME("EditorIcons"))); + select_icons_warning->add_theme_color_override("font_color", get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))); + + // Bottom panel buttons. + import_collapse_types_button->set_icon(get_theme_icon(SNAME("CollapseTree"), SNAME("EditorIcons"))); + import_expand_types_button->set_icon(get_theme_icon(SNAME("ExpandTree"), SNAME("EditorIcons"))); + + import_select_all_button->set_icon(get_theme_icon(SNAME("ThemeSelectAll"), SNAME("EditorIcons"))); + import_select_full_button->set_icon(get_theme_icon(SNAME("ThemeSelectFull"), SNAME("EditorIcons"))); + import_deselect_all_button->set_icon(get_theme_icon(SNAME("ThemeDeselectAll"), SNAME("EditorIcons"))); + + // Side panel buttons. + select_colors_icon->set_texture(get_theme_icon(SNAME("Color"), SNAME("EditorIcons"))); + deselect_all_colors_button->set_icon(get_theme_icon(SNAME("ThemeDeselectAll"), SNAME("EditorIcons"))); + select_all_colors_button->set_icon(get_theme_icon(SNAME("ThemeSelectAll"), SNAME("EditorIcons"))); + select_full_colors_button->set_icon(get_theme_icon(SNAME("ThemeSelectFull"), SNAME("EditorIcons"))); + + select_constants_icon->set_texture(get_theme_icon(SNAME("MemberConstant"), SNAME("EditorIcons"))); + deselect_all_constants_button->set_icon(get_theme_icon(SNAME("ThemeDeselectAll"), SNAME("EditorIcons"))); + select_all_constants_button->set_icon(get_theme_icon(SNAME("ThemeSelectAll"), SNAME("EditorIcons"))); + select_full_constants_button->set_icon(get_theme_icon(SNAME("ThemeSelectFull"), SNAME("EditorIcons"))); + + select_fonts_icon->set_texture(get_theme_icon(SNAME("Font"), SNAME("EditorIcons"))); + deselect_all_fonts_button->set_icon(get_theme_icon(SNAME("ThemeDeselectAll"), SNAME("EditorIcons"))); + select_all_fonts_button->set_icon(get_theme_icon(SNAME("ThemeSelectAll"), SNAME("EditorIcons"))); + select_full_fonts_button->set_icon(get_theme_icon(SNAME("ThemeSelectFull"), SNAME("EditorIcons"))); + + select_font_sizes_icon->set_texture(get_theme_icon(SNAME("FontSize"), SNAME("EditorIcons"))); + deselect_all_font_sizes_button->set_icon(get_theme_icon(SNAME("ThemeDeselectAll"), SNAME("EditorIcons"))); + select_all_font_sizes_button->set_icon(get_theme_icon(SNAME("ThemeSelectAll"), SNAME("EditorIcons"))); + select_full_font_sizes_button->set_icon(get_theme_icon(SNAME("ThemeSelectFull"), SNAME("EditorIcons"))); + + select_icons_icon->set_texture(get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons"))); + deselect_all_icons_button->set_icon(get_theme_icon(SNAME("ThemeDeselectAll"), SNAME("EditorIcons"))); + select_all_icons_button->set_icon(get_theme_icon(SNAME("ThemeSelectAll"), SNAME("EditorIcons"))); + select_full_icons_button->set_icon(get_theme_icon(SNAME("ThemeSelectFull"), SNAME("EditorIcons"))); + + select_styleboxes_icon->set_texture(get_theme_icon(SNAME("StyleBoxFlat"), SNAME("EditorIcons"))); + deselect_all_styleboxes_button->set_icon(get_theme_icon(SNAME("ThemeDeselectAll"), SNAME("EditorIcons"))); + select_all_styleboxes_button->set_icon(get_theme_icon(SNAME("ThemeSelectAll"), SNAME("EditorIcons"))); + select_full_styleboxes_button->set_icon(get_theme_icon(SNAME("ThemeSelectFull"), SNAME("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 1: - Theme::get_default()->get_stylebox_list(fromtype, &names); + + 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 2: - Theme::get_default()->get_font_list(fromtype, &names); + + 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 3: - Theme::get_default()->get_font_size_list(fromtype, &names); + + 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 4: - Theme::get_default()->get_color_list(fromtype, &names); + + 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 5: - Theme::get_default()->get_constant_list(fromtype, &names); + + 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_mode(Label::AUTOWRAP_WORD_SMART); + select_icons_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL); + select_icons_warning_hb->add_child(select_icons_warning); } - } else if (popup_mode == POPUP_REMOVE) { - theme->get_icon_list(fromtype, &names); - theme->get_stylebox_list(fromtype, &names); - theme->get_font_list(fromtype, &names); - theme->get_font_size_list(fromtype, &names); - theme->get_color_list(fromtype, &names); - theme->get_constant_list(fromtype, &names); } - name_menu->get_popup()->clear(); - name_menu->get_popup()->set_size(Size2()); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - name_menu->get_popup()->add_item(E->get()); + 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 ThemeEditor::_name_menu_cbk(int p_option) { - name_edit->set_text(name_menu->get_popup()->get_item_text(p_option)); +void ThemeItemEditorDialog::_close_dialog() { + hide(); } -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 ThemeItemEditorDialog::_dialog_about_to_show() { + ERR_FAIL_COND_MSG(edited_theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing."); - 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(""); + _update_edit_types(); - // Write default theme. - for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) { - _TECategory &tc = E->get(); + 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(); - String underline = "; "; - for (int i = 0; i < E->key().length(); i++) { - underline += "*"; - } + 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(); +} - file->store_line(""); - file->store_line(underline); - file->store_line("; " + E->key()); - file->store_line(underline); +void ThemeItemEditorDialog::_update_edit_types() { + Ref<Theme> base_theme = Theme::get_default(); - if (tc.stylebox_items.size()) { - file->store_line("\n; StyleBox Items:\n"); + List<StringName> theme_types; + edited_theme->get_type_list(&theme_types); + theme_types.sort_custom<StringName::AlphCompare>(); + + bool item_reselected = false; + edit_type_list->clear(); + 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(SNAME("NodeDisabled"), SNAME("EditorIcons")); + } else { + item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled"); } + edit_type_list->add_item(E->get(), item_icon); - 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 (E->get() == edited_item_type) { + edit_type_list->select(e_idx); + item_reselected = true; } + e_idx++; + } + if (!item_reselected) { + edited_item_type = ""; - if (tc.font_items.size()) { - file->store_line("\n; Font Items:\n"); + if (edit_type_list->get_item_count() > 0) { + edit_type_list->select(0); } + } + + List<StringName> default_types; + base_theme->get_type_list(&default_types); + default_types.sort_custom<StringName::AlphCompare>(); + + String selected_type = ""; + Vector<int> selected_ids = edit_type_list->get_selected_items(); + if (selected_ids.size() > 0) { + selected_type = edit_type_list->get_item_text(selected_ids[0]); + + edit_items_add_color->set_disabled(false); + edit_items_add_constant->set_disabled(false); + edit_items_add_font->set_disabled(false); + edit_items_add_font_size->set_disabled(false); + edit_items_add_icon->set_disabled(false); + edit_items_add_stylebox->set_disabled(false); + + edit_items_remove_class->set_disabled(false); + edit_items_remove_custom->set_disabled(false); + edit_items_remove_all->set_disabled(false); + } else { + edit_items_add_color->set_disabled(true); + edit_items_add_constant->set_disabled(true); + edit_items_add_font->set_disabled(true); + edit_items_add_font_size->set_disabled(true); + edit_items_add_icon->set_disabled(true); + edit_items_add_stylebox->set_disabled(true); + + edit_items_remove_class->set_disabled(true); + edit_items_remove_custom->set_disabled(true); + edit_items_remove_all->set_disabled(true); + } + _update_edit_item_tree(selected_type); +} + +void ThemeItemEditorDialog::_edited_type_selected(int p_item_idx) { + String selected_type = edit_type_list->get_item_text(p_item_idx); + _update_edit_item_tree(selected_type); +} - for (Set<_TECategory::RefItem<Font>>::Element *F = tc.font_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); +void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { + edited_item_type = p_item_type; + + edit_items_tree->clear(); + TreeItem *root = edit_items_tree->create_item(); + + List<StringName> names; + + { // Colors. + names.clear(); + edited_theme->get_color_list(p_item_type, &names); + + if (names.size() > 0) { + TreeItem *color_root = edit_items_tree->create_item(root); + color_root->set_metadata(0, Theme::DATA_TYPE_COLOR); + color_root->set_icon(0, get_theme_icon(SNAME("Color"), SNAME("EditorIcons"))); + color_root->set_text(0, TTR("Colors")); + color_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Color Items")); + + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + TreeItem *item = edit_items_tree->create_item(color_root); + item->set_text(0, E->get()); + item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item")); + item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item")); + } } + } - if (tc.font_size_items.size()) { - file->store_line("\n; Font Size Items:\n"); + { // Constants. + names.clear(); + edited_theme->get_constant_list(p_item_type, &names); + + if (names.size() > 0) { + TreeItem *constant_root = edit_items_tree->create_item(root); + constant_root->set_metadata(0, Theme::DATA_TYPE_CONSTANT); + constant_root->set_icon(0, get_theme_icon(SNAME("MemberConstant"), SNAME("EditorIcons"))); + constant_root->set_text(0, TTR("Constants")); + constant_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Constant Items")); + + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + TreeItem *item = edit_items_tree->create_item(constant_root); + item->set_text(0, E->get()); + item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item")); + item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item")); + } } + } - 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"); - } - - if (tc.icon_items.size()) { - file->store_line("\n; Icon Items:\n"); - } - - for (Set<_TECategory::RefItem<Texture2D>>::Element *F = tc.icon_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + { // Fonts. + names.clear(); + edited_theme->get_font_list(p_item_type, &names); + + if (names.size() > 0) { + TreeItem *font_root = edit_items_tree->create_item(root); + font_root->set_metadata(0, Theme::DATA_TYPE_FONT); + font_root->set_icon(0, get_theme_icon(SNAME("Font"), SNAME("EditorIcons"))); + font_root->set_text(0, TTR("Fonts")); + font_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Font Items")); + + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + TreeItem *item = edit_items_tree->create_item(font_root); + item->set_text(0, E->get()); + item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item")); + item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item")); + } } + } - if (tc.color_items.size()) { - file->store_line("\n; Color Items:\n"); + { // Font sizes. + names.clear(); + edited_theme->get_font_size_list(p_item_type, &names); + + if (names.size() > 0) { + TreeItem *font_size_root = edit_items_tree->create_item(root); + font_size_root->set_metadata(0, Theme::DATA_TYPE_FONT_SIZE); + font_size_root->set_icon(0, get_theme_icon(SNAME("FontSize"), SNAME("EditorIcons"))); + font_size_root->set_text(0, TTR("Font Sizes")); + font_size_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Font Size Items")); + + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + TreeItem *item = edit_items_tree->create_item(font_size_root); + item->set_text(0, E->get()); + item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item")); + item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item")); + } } + } - for (Set<_TECategory::Item<Color>>::Element *F = tc.color_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + { // Icons. + names.clear(); + edited_theme->get_icon_list(p_item_type, &names); + + if (names.size() > 0) { + TreeItem *icon_root = edit_items_tree->create_item(root); + icon_root->set_metadata(0, Theme::DATA_TYPE_ICON); + icon_root->set_icon(0, get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons"))); + icon_root->set_text(0, TTR("Icons")); + icon_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Icon Items")); + + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + TreeItem *item = edit_items_tree->create_item(icon_root); + item->set_text(0, E->get()); + item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item")); + item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item")); + } } + } - if (tc.constant_items.size()) { - file->store_line("\n; Constant Items:\n"); + { // Styleboxes. + names.clear(); + edited_theme->get_stylebox_list(p_item_type, &names); + + if (names.size() > 0) { + TreeItem *stylebox_root = edit_items_tree->create_item(root); + stylebox_root->set_metadata(0, Theme::DATA_TYPE_STYLEBOX); + stylebox_root->set_icon(0, get_theme_icon(SNAME("StyleBoxFlat"), SNAME("EditorIcons"))); + stylebox_root->set_text(0, TTR("Styleboxes")); + stylebox_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All StyleBox Items")); + + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + TreeItem *item = edit_items_tree->create_item(stylebox_root); + item->set_text(0, E->get()); + item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item")); + item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item")); + } } + } +} + +void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_column, int p_id) { + TreeItem *item = Object::cast_to<TreeItem>(p_item); + if (!item) { + return; + } - for (Set<_TECategory::Item<int>>::Element *F = tc.constant_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + switch (p_id) { + case ITEMS_TREE_RENAME_ITEM: { + String item_name = item->get_text(0); + int data_type = item->get_parent()->get_metadata(0); + _open_rename_theme_item_dialog((Theme::DataType)data_type, item_name); + } break; + case ITEMS_TREE_REMOVE_ITEM: { + String item_name = item->get_text(0); + int data_type = item->get_parent()->get_metadata(0); + edited_theme->clear_theme_item((Theme::DataType)data_type, item_name, edited_item_type); + } break; + case ITEMS_TREE_REMOVE_DATA_TYPE: { + int data_type = item->get_metadata(0); + _remove_data_type_items((Theme::DataType)data_type, edited_item_type); + } break; + } + + _update_edit_item_tree(edited_item_type); +} + +void ThemeItemEditorDialog::_add_theme_type(const String &p_new_text) { + const String new_type = edit_add_type_value->get_text().strip_edges(); + edit_add_type_value->clear(); + + edited_theme->add_icon_type(new_type); + edited_theme->add_stylebox_type(new_type); + edited_theme->add_font_type(new_type); + edited_theme->add_font_size_type(new_type); + edited_theme->add_color_type(new_type); + edited_theme->add_constant_type(new_type); + _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) { + switch (p_data_type) { + case Theme::DATA_TYPE_ICON: + edited_theme->set_icon(p_item_name, p_item_type, Ref<Texture2D>()); + break; + case Theme::DATA_TYPE_STYLEBOX: + edited_theme->set_stylebox(p_item_name, p_item_type, Ref<StyleBox>()); + break; + case Theme::DATA_TYPE_FONT: + edited_theme->set_font(p_item_name, p_item_type, Ref<Font>()); + break; + case Theme::DATA_TYPE_FONT_SIZE: + edited_theme->set_font_size(p_item_name, p_item_type, -1); + break; + case Theme::DATA_TYPE_COLOR: + edited_theme->set_color(p_item_name, p_item_type, Color()); + break; + case Theme::DATA_TYPE_CONSTANT: + edited_theme->set_constant(p_item_name, p_item_type, 0); + break; + case Theme::DATA_TYPE_MAX: + break; // Can't happen, but silences warning. + } +} + +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; + + names.clear(); + Theme::get_default()->get_theme_item_list(data_type, edited_item_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (edited_theme->has_theme_item_nocheck(data_type, E->get(), edited_item_type)) { + edited_theme->clear_theme_item(data_type, E->get(), edited_item_type); + } } } - file->close(); - memdelete(file); + // 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 ThemeEditor::_dialog_cbk() { - switch (popup_mode) { - case POPUP_ADD: { - switch (type_select->get_selected()) { - case 0: - theme->set_icon(name_edit->get_text(), type_edit->get_text(), Ref<Texture2D>()); - break; - case 1: - theme->set_stylebox(name_edit->get_text(), type_edit->get_text(), Ref<StyleBox>()); - break; - case 2: - theme->set_font(name_edit->get_text(), type_edit->get_text(), Ref<Font>()); - break; - case 3: - theme->set_font_size(name_edit->get_text(), type_edit->get_text(), -1); - break; - case 4: - theme->set_color(name_edit->get_text(), type_edit->get_text(), Color()); - break; - case 5: - theme->set_constant(name_edit->get_text(), type_edit->get_text(), 0); - break; +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; + + names.clear(); + edited_theme->get_theme_item_list(data_type, edited_item_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!Theme::get_default()->has_theme_item_nocheck(data_type, E->get(), edited_item_type)) { + edited_theme->clear_theme_item(data_type, E->get(), edited_item_type); } + } + } + + // 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; + + names.clear(); + edited_theme->get_theme_item_list(data_type, edited_item_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + edited_theme->clear_theme_item(data_type, E->get(), edited_item_type); + } + } + + // 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::_open_add_theme_item_dialog(int p_data_type) { + ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds."); + + item_popup_mode = CREATE_THEME_ITEM; + edit_item_data_type = (Theme::DataType)p_data_type; + + switch (edit_item_data_type) { + case Theme::DATA_TYPE_COLOR: + edit_theme_item_dialog->set_title(TTR("Add Color Item")); + break; + case Theme::DATA_TYPE_CONSTANT: + edit_theme_item_dialog->set_title(TTR("Add Constant Item")); + break; + case Theme::DATA_TYPE_FONT: + edit_theme_item_dialog->set_title(TTR("Add Font Item")); + break; + case Theme::DATA_TYPE_FONT_SIZE: + edit_theme_item_dialog->set_title(TTR("Add Font Size Item")); + break; + case Theme::DATA_TYPE_ICON: + edit_theme_item_dialog->set_title(TTR("Add Icon Item")); + break; + case Theme::DATA_TYPE_STYLEBOX: + edit_theme_item_dialog->set_title(TTR("Add Stylebox Item")); + break; + case Theme::DATA_TYPE_MAX: + break; // Can't happen, but silences warning. + } + + edit_theme_item_old_vb->hide(); + theme_item_name->clear(); + edit_theme_item_dialog->popup_centered(Size2(380, 110) * EDSCALE); + theme_item_name->grab_focus(); +} + +void ThemeItemEditorDialog::_open_rename_theme_item_dialog(Theme::DataType p_data_type, String p_item_name) { + ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds."); + + item_popup_mode = RENAME_THEME_ITEM; + edit_item_data_type = p_data_type; + edit_item_old_name = p_item_name; + + switch (edit_item_data_type) { + case Theme::DATA_TYPE_COLOR: + edit_theme_item_dialog->set_title(TTR("Rename Color Item")); + break; + case Theme::DATA_TYPE_CONSTANT: + edit_theme_item_dialog->set_title(TTR("Rename Constant Item")); + break; + case Theme::DATA_TYPE_FONT: + edit_theme_item_dialog->set_title(TTR("Rename Font Item")); + break; + case Theme::DATA_TYPE_FONT_SIZE: + edit_theme_item_dialog->set_title(TTR("Rename Font Size Item")); + break; + case Theme::DATA_TYPE_ICON: + edit_theme_item_dialog->set_title(TTR("Rename Icon Item")); + break; + case Theme::DATA_TYPE_STYLEBOX: + edit_theme_item_dialog->set_title(TTR("Rename Stylebox Item")); + break; + case Theme::DATA_TYPE_MAX: + break; // Can't happen, but silences warning. + } + + edit_theme_item_old_vb->show(); + theme_item_old_name->set_text(p_item_name); + theme_item_name->set_text(p_item_name); + edit_theme_item_dialog->popup_centered(Size2(380, 140) * EDSCALE); + theme_item_name->grab_focus(); +} + +void ThemeItemEditorDialog::_confirm_edit_theme_item() { + if (item_popup_mode == CREATE_THEME_ITEM) { + _add_theme_item(edit_item_data_type, theme_item_name->get_text(), edited_item_type); + } else if (item_popup_mode == RENAME_THEME_ITEM) { + edited_theme->rename_theme_item(edit_item_data_type, edit_item_old_name, theme_item_name->get_text(), edited_item_type); + } + + item_popup_mode = ITEM_POPUP_MODE_MAX; + edit_item_data_type = Theme::DATA_TYPE_MAX; + edit_item_old_name = ""; + + _update_edit_item_tree(edited_item_type); +} + +void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventKey> k = p_event; + + if (k.is_valid()) { + if (!k->is_pressed()) { + return; + } + + switch (k->get_keycode()) { + case KEY_KP_ENTER: + case KEY_ENTER: { + _confirm_edit_theme_item(); + edit_theme_item_dialog->hide(); + edit_theme_item_dialog->set_input_as_handled(); + } break; + case KEY_ESCAPE: { + edit_theme_item_dialog->hide(); + edit_theme_item_dialog->set_input_as_handled(); + } break; + } + } +} + +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: { + connect("about_to_popup", callable_mp(this, &ThemeItemEditorDialog::_dialog_about_to_show)); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + edit_items_add_color->set_icon(get_theme_icon(SNAME("Color"), SNAME("EditorIcons"))); + edit_items_add_constant->set_icon(get_theme_icon(SNAME("MemberConstant"), SNAME("EditorIcons"))); + edit_items_add_font->set_icon(get_theme_icon(SNAME("Font"), SNAME("EditorIcons"))); + edit_items_add_font_size->set_icon(get_theme_icon(SNAME("FontSize"), SNAME("EditorIcons"))); + edit_items_add_icon->set_icon(get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons"))); + edit_items_add_stylebox->set_icon(get_theme_icon(SNAME("StyleBoxFlat"), SNAME("EditorIcons"))); + + edit_items_remove_class->set_icon(get_theme_icon(SNAME("Control"), SNAME("EditorIcons"))); + edit_items_remove_custom->set_icon(get_theme_icon(SNAME("ThemeRemoveCustomItems"), SNAME("EditorIcons"))); + edit_items_remove_all->set_icon(get_theme_icon(SNAME("ThemeRemoveAllItems"), SNAME("EditorIcons"))); + + import_another_theme_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); + + tc->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("tab_selected_odd"), SNAME("TabContainer"))); + tc->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel_odd"), SNAME("TabContainer"))); } break; - case POPUP_CLASS_ADD: { - StringName fromtype = type_edit->get_text(); - List<StringName> names; + } +} - { - names.clear(); - Theme::get_default()->get_icon_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->set_icon(E->get(), fromtype, Ref<Texture2D>()); - } +void ThemeItemEditorDialog::set_edited_theme(const Ref<Theme> &p_theme) { + edited_theme = p_theme; +} + +ThemeItemEditorDialog::ThemeItemEditorDialog() { + 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); + 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); + edit_dialog_hs->add_child(edit_dialog_side_vb); + + Label *edit_type_label = memnew(Label); + edit_type_label->set_text(TTR("Types:")); + edit_dialog_side_vb->add_child(edit_type_label); + + edit_type_list = memnew(ItemList); + edit_type_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + 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_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_value->connect("text_submitted", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type)); + 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), varray("")); + + VBoxContainer *edit_items_vb = memnew(VBoxContainer); + edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + edit_dialog_hs->add_child(edit_items_vb); + + HBoxContainer *edit_items_toolbar = memnew(HBoxContainer); + 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 Item:")); + edit_items_toolbar->add_child(edit_items_toolbar_add_label); + + edit_items_add_color = memnew(Button); + edit_items_add_color->set_tooltip(TTR("Add Color Item")); + edit_items_add_color->set_flat(true); + edit_items_add_color->set_disabled(true); + edit_items_toolbar->add_child(edit_items_add_color); + edit_items_add_color->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_COLOR)); + + edit_items_add_constant = memnew(Button); + edit_items_add_constant->set_tooltip(TTR("Add Constant Item")); + edit_items_add_constant->set_flat(true); + edit_items_add_constant->set_disabled(true); + edit_items_toolbar->add_child(edit_items_add_constant); + edit_items_add_constant->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_CONSTANT)); + + edit_items_add_font = memnew(Button); + edit_items_add_font->set_tooltip(TTR("Add Font Item")); + edit_items_add_font->set_flat(true); + edit_items_add_font->set_disabled(true); + edit_items_toolbar->add_child(edit_items_add_font); + edit_items_add_font->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_FONT)); + + edit_items_add_font_size = memnew(Button); + edit_items_add_font_size->set_tooltip(TTR("Add Font Size Item")); + edit_items_add_font_size->set_flat(true); + edit_items_add_font_size->set_disabled(true); + edit_items_toolbar->add_child(edit_items_add_font_size); + edit_items_add_font_size->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_FONT_SIZE)); + + edit_items_add_icon = memnew(Button); + edit_items_add_icon->set_tooltip(TTR("Add Icon Item")); + edit_items_add_icon->set_flat(true); + edit_items_add_icon->set_disabled(true); + edit_items_toolbar->add_child(edit_items_add_icon); + edit_items_add_icon->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_ICON)); + + edit_items_add_stylebox = memnew(Button); + edit_items_add_stylebox->set_tooltip(TTR("Add StyleBox Item")); + edit_items_add_stylebox->set_flat(true); + edit_items_add_stylebox->set_disabled(true); + edit_items_toolbar->add_child(edit_items_add_stylebox); + edit_items_add_stylebox->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_add_theme_item_dialog), varray(Theme::DATA_TYPE_STYLEBOX)); + + edit_items_toolbar->add_child(memnew(VSeparator)); + + Label *edit_items_toolbar_remove_label = memnew(Label); + 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); + edit_items_remove_class->set_tooltip(TTR("Remove Class Items")); + edit_items_remove_class->set_flat(true); + edit_items_remove_class->set_disabled(true); + edit_items_toolbar->add_child(edit_items_remove_class); + edit_items_remove_class->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_class_items)); + + edit_items_remove_custom = memnew(Button); + edit_items_remove_custom->set_tooltip(TTR("Remove Custom Items")); + edit_items_remove_custom->set_flat(true); + edit_items_remove_custom->set_disabled(true); + edit_items_toolbar->add_child(edit_items_remove_custom); + edit_items_remove_custom->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_custom_items)); + + edit_items_remove_all = memnew(Button); + edit_items_remove_all->set_tooltip(TTR("Remove All Items")); + edit_items_remove_all->set_flat(true); + edit_items_remove_all->set_disabled(true); + edit_items_toolbar->add_child(edit_items_remove_all); + edit_items_remove_all->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_remove_all_items)); + + edit_items_tree = memnew(Tree); + edit_items_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + edit_items_tree->set_hide_root(true); + edit_items_tree->set_columns(1); + edit_items_vb->add_child(edit_items_tree); + edit_items_tree->connect("button_pressed", callable_mp(this, &ThemeItemEditorDialog::_item_tree_button_pressed)); + + edit_theme_item_dialog = memnew(ConfirmationDialog); + edit_theme_item_dialog->set_title(TTR("Add Theme Item")); + add_child(edit_theme_item_dialog); + VBoxContainer *edit_theme_item_vb = memnew(VBoxContainer); + edit_theme_item_dialog->add_child(edit_theme_item_vb); + + edit_theme_item_old_vb = memnew(VBoxContainer); + edit_theme_item_vb->add_child(edit_theme_item_old_vb); + Label *edit_theme_item_old = memnew(Label); + edit_theme_item_old->set_text(TTR("Old Name:")); + edit_theme_item_old_vb->add_child(edit_theme_item_old); + theme_item_old_name = memnew(Label); + edit_theme_item_old_vb->add_child(theme_item_old_name); + + Label *edit_theme_item_label = memnew(Label); + edit_theme_item_label->set_text(TTR("Name:")); + edit_theme_item_vb->add_child(edit_theme_item_label); + theme_item_name = memnew(LineEdit); + 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)); + + // 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 ThemeTypeDialog::_dialog_about_to_show() { + add_type_filter->set_text(""); + add_type_filter->grab_focus(); + + _update_add_type_options(); +} + +void ThemeTypeDialog::ok_pressed() { + emit_signal(SNAME("type_selected"), add_type_filter->get_text().strip_edges()); +} + +void ThemeTypeDialog::_update_add_type_options(const String &p_filter) { + add_type_options->clear(); + + List<StringName> names; + Theme::get_default()->get_type_list(&names); + if (include_own_types) { + edited_theme->get_type_list(&names); + } + names.sort_custom<StringName::AlphCompare>(); + + Vector<StringName> unique_names; + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + // Filter out undesired values. + if (!p_filter.is_subsequence_ofi(String(E->get()))) { + continue; + } + + // Skip duplicate values. + if (unique_names.has(E->get())) { + continue; + } + unique_names.append(E->get()); + + Ref<Texture2D> item_icon; + if (E->get() == "") { + item_icon = get_theme_icon(SNAME("NodeDisabled"), SNAME("EditorIcons")); + } else { + item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled"); + } + + add_type_options->add_item(E->get(), item_icon); + } +} + +void ThemeTypeDialog::_add_type_filter_cbk(const String &p_value) { + _update_add_type_options(p_value); +} + +void ThemeTypeDialog::_add_type_options_cbk(int p_index) { + add_type_filter->set_text(add_type_options->get_item_text(p_index)); +} + +void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) { + emit_signal(SNAME("type_selected"), p_value.strip_edges()); + hide(); +} + +void ThemeTypeDialog::_add_type_dialog_activated(int p_index) { + emit_signal(SNAME("type_selected"), add_type_options->get_item_text(p_index)); + hide(); +} + +void ThemeTypeDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + connect("about_to_popup", callable_mp(this, &ThemeTypeDialog::_dialog_about_to_show)); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + _update_add_type_options(); + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (is_visible()) { + add_type_filter->grab_focus(); } - { - names.clear(); - Theme::get_default()->get_stylebox_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->set_stylebox(E->get(), fromtype, Ref<StyleBox>()); - } + } break; + } +} + +void ThemeTypeDialog::_bind_methods() { + ADD_SIGNAL(MethodInfo("type_selected", PropertyInfo(Variant::STRING, "type_name"))); +} + +void ThemeTypeDialog::set_edited_theme(const Ref<Theme> &p_theme) { + edited_theme = p_theme; +} + +void ThemeTypeDialog::set_include_own_types(bool p_enable) { + include_own_types = p_enable; +} + +ThemeTypeDialog::ThemeTypeDialog() { + VBoxContainer *add_type_vb = memnew(VBoxContainer); + 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, &ThemeTypeDialog::_add_type_filter_cbk)); + add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_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(Control::SIZE_EXPAND_FILL); + add_type_vb->add_child(add_type_options); + add_type_options->connect("item_selected", callable_mp(this, &ThemeTypeDialog::_add_type_options_cbk)); + add_type_options->connect("item_activated", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_activated)); +} + +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; +} + +void ThemeTypeEditor::_update_type_list() { + ERR_FAIL_COND(edited_theme.is_null()); + + 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; + } + + 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; } - { - names.clear(); - Theme::get_default()->get_font_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->set_font(E->get(), fromtype, Ref<Font>()); - } + + 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(); + + if (theme_types.size() > 0) { + theme_type_list->set_disabled(false); + + 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(SNAME("NodeDisabled"), SNAME("EditorIcons")); + } else { + item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled"); } - { - names.clear(); - Theme::get_default()->get_font_size_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->set_font_size(E->get(), fromtype, Theme::get_default()->get_font_size(E->get(), fromtype)); - } + theme_type_list->add_icon_item(item_icon, E->get()); + + if (E->get() == edited_type) { + theme_type_list->select(e_idx); + item_reselected = true; } - { - names.clear(); - Theme::get_default()->get_color_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->set_color(E->get(), fromtype, Theme::get_default()->get_color(E->get(), fromtype)); - } + e_idx++; + } + + 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(); +} + +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(); + String default_type = p_type_name; + if (edited_theme->get_type_variation_base(p_type_name) != StringName()) { + default_type = edited_theme->get_type_variation_base(p_type_name); + } + + (Theme::get_default().operator->()->*get_list_func)(default_type, &names); + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + items[E->get()] = false; + } + } + + { + 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()]; + } + + 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(SNAME("Edit"), SNAME("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(SNAME("Remove"), SNAME("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(SNAME("ImportCheck"), SNAME("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(SNAME("ImportFail"), SNAME("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(SNAME("disabled_font_color"), SNAME("Editor"))); + + Button *item_override_button = memnew(Button); + item_override_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("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); + } + + 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); } - { - names.clear(); - Theme::get_default()->get_constant_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->set_constant(E->get(), fromtype, Theme::get_default()->get_constant(E->get(), fromtype)); + + _add_focusable(item_editor); + color_items_list->add_child(item_control); + } + } + + // 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); + } + + 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); + } + } + + // 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); + } + + 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); } - } break; - case POPUP_REMOVE: { - switch (type_select->get_selected()) { - case 0: - theme->clear_icon(name_edit->get_text(), type_edit->get_text()); - break; - case 1: - theme->clear_stylebox(name_edit->get_text(), type_edit->get_text()); - break; - case 2: - theme->clear_font(name_edit->get_text(), type_edit->get_text()); - break; - case 3: - theme->clear_font_size(name_edit->get_text(), type_edit->get_text()); - break; - case 4: - theme->clear_color(name_edit->get_text(), type_edit->get_text()); - break; - case 5: - theme->clear_constant(name_edit->get_text(), type_edit->get_text()); - break; + + _add_focusable(item_editor); + font_items_list->add_child(item_control); + } + } + + // 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); + } + + 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); } - } break; - case POPUP_CLASS_REMOVE: { - StringName fromtype = type_edit->get_text(); - List<StringName> names; + _add_focusable(item_editor); + font_size_items_list->add_child(item_control); + } + } - { - names.clear(); - Theme::get_default()->get_icon_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->clear_icon(E->get(), fromtype); + // Icons. + { + 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()); } - } - { - names.clear(); - Theme::get_default()->get_stylebox_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->clear_stylebox(E->get(), fromtype); + 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); } - { - names.clear(); - Theme::get_default()->get_font_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->clear_font(E->get(), fromtype); - } + + _add_focusable(item_editor); + icon_items_list->add_child(item_control); + } + } + + // 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); + } + + 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(SNAME("Pin"), SNAME("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()); } - { - names.clear(); - Theme::get_default()->get_font_size_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->clear_font_size(E->get(), fromtype); - } + 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)); + + stylebox_items_list->add_child(item_control); + stylebox_items_list->add_child(memnew(HSeparator)); + } + + 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; } - { - names.clear(); - Theme::get_default()->get_color_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->clear_color(E->get(), fromtype); + + 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()); } - } - { - names.clear(); - Theme::get_default()->get_constant_list(fromtype, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - theme->clear_constant(E->get(), fromtype); + 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(SNAME("Pin"), SNAME("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); } - } break; + item_control->add_child(item_editor); + _add_focusable(item_editor); + stylebox_items_list->add_child(item_control); + } + } + + // Various type settings. + if (ClassDB::class_exists(edited_type)) { + type_variation_edit->set_editable(false); + type_variation_edit->set_tooltip(TTR("A type associated with a built-in class cannot be marked as a variation of another type.")); + type_variation_edit->set_text(""); + type_variation_button->hide(); + } else { + type_variation_edit->set_editable(true); + type_variation_edit->set_tooltip(""); + type_variation_edit->set_text(edited_theme->get_type_variation_base(edited_type)); + _add_focusable(type_variation_edit); + type_variation_button->show(); } } -void ThemeEditor::_theme_menu_cbk(int p_option) { - if (p_option == POPUP_CREATE_EMPTY || p_option == POPUP_CREATE_EDITOR_EMPTY || p_option == POPUP_IMPORT_EDITOR_THEME) { - bool import = (p_option == POPUP_IMPORT_EDITOR_THEME); +void ThemeTypeEditor::_list_type_selected(int p_index) { + edited_type = theme_type_list->get_item_text(p_index); + _update_type_items(); +} - Ref<Theme> base_theme; +void ThemeTypeEditor::_add_type_button_cbk() { + add_type_mode = ADD_THEME_TYPE; + add_type_dialog->set_title(TTR("Add Item Type")); + add_type_dialog->set_include_own_types(false); + add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); +} - if (p_option == POPUP_CREATE_EMPTY) { - base_theme = Theme::get_default(); - } else { - base_theme = EditorNode::get_singleton()->get_theme_base()->get_theme(); +void ThemeTypeEditor::_add_default_type_items() { + List<StringName> names; + String default_type = edited_type; + if (edited_theme->get_type_variation_base(edited_type) != StringName()) { + default_type = edited_theme->get_type_variation_base(edited_type); + } + + updating = true; + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); + + { + names.clear(); + Theme::get_default()->get_icon_list(default_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(default_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(default_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(default_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(), default_type)); + } + } + } + { + names.clear(); + Theme::get_default()->get_color_list(default_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(), default_type)); + } + } + } + { + names.clear(); + Theme::get_default()->get_constant_list(default_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(), default_type)); + } + } + } - { - List<StringName> types; - base_theme->get_type_list(&types); + // Allow changes to be reported now that the operation is finished. + edited_theme->_unfreeze_and_propagate_changes(); + updating = false; - for (List<StringName>::Element *T = types.front(); T; T = T->next()) { - StringName type = T->get(); + _update_type_items(); +} - List<StringName> icons; - base_theme->get_icon_list(type, &icons); +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; + } - 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>()); - } + 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; + } - List<StringName> styleboxs; - base_theme->get_stylebox_list(type, &styleboxs); + le->set_text(""); +} - 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>()); - } +void ThemeTypeEditor::_item_add_lineedit_cbk(String p_value, int p_data_type, Control *p_control) { + _item_add_cbk(p_data_type, p_control); +} - List<StringName> fonts; - base_theme->get_font_list(type, &fonts); +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; + } +} - for (List<StringName>::Element *E = fonts.front(); E; E = E->next()) { - theme->set_font(E->get(), type, Ref<Font>()); - } +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); - List<StringName> font_sizes; - base_theme->get_font_size_list(type, &font_sizes); + if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) { + _unpin_leading_stylebox(); + } + } break; + } +} - 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)); - } +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(); +} - List<StringName> colors; - base_theme->get_color_list(type, &colors); +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; + } - 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()); - } + 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; + } - List<StringName> constants; - base_theme->get_constant_list(type, &constants); + 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 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 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; } - Ref<Theme> base_theme; + // 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::_type_variation_changed(const String p_value) { + if (p_value.is_empty()) { + edited_theme->clear_type_variation(edited_type); + } else { + edited_theme->set_type_variation(edited_type, StringName(p_value)); + } +} + +void ThemeTypeEditor::_add_type_variation_cbk() { + add_type_mode = ADD_VARIATION_BASE; + add_type_dialog->set_title(TTR("Add Variation Base Type")); + add_type_dialog->set_include_own_types(true); + add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); +} - name_select_label->show(); - name_hbc->show(); - type_select_label->show(); - type_select->show(); +void ThemeTypeEditor::_add_type_dialog_selected(const String p_type_name) { + if (add_type_mode == ADD_THEME_TYPE) { + select_type(p_type_name); + } else if (add_type_mode == ADD_VARIATION_BASE) { + _type_variation_changed(p_type_name); + _update_type_items(); + } +} - if (p_option == POPUP_ADD) { // Add. +void ThemeTypeEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + add_type_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - add_del_dialog->set_title(TTR("Add Item")); - add_del_dialog->get_ok_button()->set_text(TTR("Add")); - add_del_dialog->popup_centered(Size2(490, 85) * EDSCALE); + data_type_tabs->set_tab_icon(0, get_theme_icon(SNAME("Color"), SNAME("EditorIcons"))); + data_type_tabs->set_tab_icon(1, get_theme_icon(SNAME("MemberConstant"), SNAME("EditorIcons"))); + data_type_tabs->set_tab_icon(2, get_theme_icon(SNAME("Font"), SNAME("EditorIcons"))); + data_type_tabs->set_tab_icon(3, get_theme_icon(SNAME("FontSize"), SNAME("EditorIcons"))); + data_type_tabs->set_tab_icon(4, get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons"))); + data_type_tabs->set_tab_icon(5, get_theme_icon(SNAME("StyleBoxFlat"), SNAME("EditorIcons"))); + data_type_tabs->set_tab_icon(6, get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); - base_theme = Theme::get_default(); + data_type_tabs->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("tab_selected_odd"), SNAME("TabContainer"))); + data_type_tabs->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel_odd"), SNAME("TabContainer"))); - } else if (p_option == POPUP_CLASS_ADD) { // Add. + type_variation_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + } break; + } +} - add_del_dialog->set_title(TTR("Add All Items")); - add_del_dialog->get_ok_button()->set_text(TTR("Add All")); - add_del_dialog->popup_centered(Size2(240, 85) * EDSCALE); +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)); + } - base_theme = Theme::get_default(); + edited_theme = p_theme; + edited_theme->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); + _update_type_list(); - name_select_label->hide(); - name_hbc->hide(); - type_select_label->hide(); - type_select->hide(); + add_type_dialog->set_edited_theme(edited_theme); +} - } else if (p_option == POPUP_REMOVE) { - add_del_dialog->set_title(TTR("Remove Item")); - add_del_dialog->get_ok_button()->set_text(TTR("Remove")); - add_del_dialog->popup_centered(Size2(490, 85) * EDSCALE); +void ThemeTypeEditor::select_type(String p_type_name) { + edited_type = p_type_name; + bool type_exists = false; - base_theme = theme; + 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; + } + } - } else if (p_option == POPUP_CLASS_REMOVE) { - add_del_dialog->set_title(TTR("Remove All Items")); - add_del_dialog->get_ok_button()->set_text(TTR("Remove All")); - add_del_dialog->popup_centered(Size2(240, 85) * EDSCALE); + 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(); + } +} - base_theme = Theme::get_default(); +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 a type from a list of available types or create a new one.")); + type_list_hb->add_child(add_type_button); + add_type_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_button_cbk)); + + 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); + + VBoxContainer *type_settings_tab = memnew(VBoxContainer); + type_settings_tab->set_custom_minimum_size(Size2(0, 160) * EDSCALE); + data_type_tabs->add_child(type_settings_tab); + data_type_tabs->set_tab_title(data_type_tabs->get_tab_count() - 1, ""); + + ScrollContainer *type_settings_sc = memnew(ScrollContainer); + type_settings_sc->set_v_size_flags(SIZE_EXPAND_FILL); + type_settings_sc->set_enable_h_scroll(false); + type_settings_tab->add_child(type_settings_sc); + VBoxContainer *type_settings_list = memnew(VBoxContainer); + type_settings_list->set_h_size_flags(SIZE_EXPAND_FILL); + type_settings_sc->add_child(type_settings_list); + + HBoxContainer *type_variation_hb = memnew(HBoxContainer); + type_settings_list->add_child(type_variation_hb); + Label *type_variation_label = memnew(Label); + type_variation_hb->add_child(type_variation_label); + type_variation_label->set_text(TTR("Base Type")); + type_variation_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + type_variation_edit = memnew(LineEdit); + type_variation_hb->add_child(type_variation_edit); + type_variation_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + type_variation_edit->connect("text_changed", callable_mp(this, &ThemeTypeEditor::_type_variation_changed)); + type_variation_edit->connect("focus_exited", callable_mp(this, &ThemeTypeEditor::_update_type_items)); + type_variation_button = memnew(Button); + type_variation_hb->add_child(type_variation_button); + type_variation_button->set_tooltip(TTR("Select the variation base type from a list of available types.")); + type_variation_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_variation_cbk)); + + add_type_dialog = memnew(ThemeTypeDialog); + add_child(add_type_dialog); + add_type_dialog->connect("type_selected", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_selected)); + + 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); +} - name_select_label->hide(); - name_hbc->hide(); - type_select_label->hide(); - type_select->hide(); +void ThemeEditor::edit(const Ref<Theme> &p_theme) { + if (theme == p_theme) { + return; } - popup_mode = p_option; - ERR_FAIL_COND(theme.is_null()); + theme = p_theme; + theme_type_editor->set_edited_theme(p_theme); + theme_edit_dialog->set_edited_theme(p_theme); - List<StringName> types; - base_theme->get_type_list(&types); + 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; + } - type_menu->get_popup()->clear(); + preview_tab->set_preview_theme(p_theme); + } - if (p_option == 0 || p_option == 1) { // Add. + theme_name->set_text(TTR("Theme") + ": " + theme->get_path().get_file()); +} - List<StringName> new_types; - theme->get_type_list(&new_types); - for (List<StringName>::Element *F = new_types.front(); F; F = F->next()) { - bool found = false; - for (List<StringName>::Element *E = types.front(); E; E = E->next()) { - if (E->get() == F->get()) { - found = true; - break; - } - } +Ref<Theme> ThemeEditor::get_edited_theme() { + return theme; +} - if (!found) { - types.push_back(F->get()); - } +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(SNAME("PackedScene"), SNAME("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(SNAME("close"), SNAME("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); } +} - types.sort_custom<StringName::AlphCompare>(); - for (List<StringName>::Element *E = types.front(); E; E = E->next()) { - type_menu->get_popup()->add_item(E->get()); +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_PROCESS: { - time_left -= get_process_delta_time(); - if (time_left < 0) { - time_left = 1.5; - _refresh_interval(); - } - } break; + case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { - theme_menu->set_icon(get_theme_icon("Theme", "EditorIcons")); + preview_tabs->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("ThemeEditorPreviewFG"), SNAME("EditorStyles"))); + preview_tabs->add_theme_style_override("tab_unselected", get_theme_stylebox(SNAME("ThemeEditorPreviewBG"), SNAME("EditorStyles"))); + preview_tabs_content->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel_odd"), SNAME("TabContainer"))); + + add_preview_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); } break; } } -void ThemeEditor::_bind_methods() { -} - ThemeEditor::ThemeEditor() { - time_left = 0; - 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") + ": "); + theme_name->set_theme_type_variation("HeaderSmall"); + top_menu->add_child(theme_name); + top_menu->add_spacer(false); - theme_menu = memnew(MenuButton); - theme_menu->set_text(TTR("Edit Theme")); - theme_menu->set_tooltip(TTR("Theme editing menu.")); - theme_menu->get_popup()->add_item(TTR("Add Item"), POPUP_ADD); - theme_menu->get_popup()->add_item(TTR("Add Class Items"), POPUP_CLASS_ADD); - theme_menu->get_popup()->add_item(TTR("Remove Item"), POPUP_REMOVE); - theme_menu->get_popup()->add_item(TTR("Remove Class Items"), POPUP_CLASS_REMOVE); - theme_menu->get_popup()->add_separator(); - theme_menu->get_popup()->add_item(TTR("Create Empty Template"), POPUP_CREATE_EMPTY); - theme_menu->get_popup()->add_item(TTR("Create Empty Editor Template"), POPUP_CREATE_EDITOR_EMPTY); - theme_menu->get_popup()->add_item(TTR("Create From Current Editor Theme"), POPUP_IMPORT_EDITOR_THEME); - top_menu->add_child(theme_menu); - theme_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_theme_menu_cbk)); - - 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); - - //////// - - add_del_dialog = memnew(ConfirmationDialog); - add_del_dialog->hide(); - add_child(add_del_dialog); - - VBoxContainer *dialog_vbc = memnew(VBoxContainer); - add_del_dialog->add_child(dialog_vbc); - - Label *l = memnew(Label); - l->set_text(TTR("Type:")); - dialog_vbc->add_child(l); - - type_hbc = memnew(HBoxContainer); - dialog_vbc->add_child(type_hbc); - - type_edit = memnew(LineEdit); - type_edit->set_h_size_flags(SIZE_EXPAND_FILL); - type_hbc->add_child(type_edit); - type_menu = memnew(MenuButton); - type_menu->set_flat(false); - type_menu->set_text("..."); - type_hbc->add_child(type_menu); - - type_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_type_menu_cbk)); - - l = memnew(Label); - l->set_text(TTR("Name:")); - dialog_vbc->add_child(l); - name_select_label = l; - - name_hbc = memnew(HBoxContainer); - dialog_vbc->add_child(name_hbc); - - name_edit = memnew(LineEdit); - name_edit->set_h_size_flags(SIZE_EXPAND_FILL); - name_hbc->add_child(name_edit); - name_menu = memnew(MenuButton); - type_menu->set_flat(false); - name_menu->set_text("..."); - name_hbc->add_child(name_menu); - - name_menu->get_popup()->connect("about_to_popup", callable_mp(this, &ThemeEditor::_name_menu_about_to_show)); - name_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_name_menu_cbk)); - - type_select_label = memnew(Label); - type_select_label->set_text(TTR("Data Type:")); - dialog_vbc->add_child(type_select_label); - - type_select = memnew(OptionButton); - type_select->add_item(TTR("Icon")); - type_select->add_item(TTR("Style")); - type_select->add_item(TTR("Font")); - type_select->add_item(TTR("Font Size")); - type_select->add_item(TTR("Color")); - type_select->add_item(TTR("Constant")); - - dialog_vbc->add_child(type_select); - - add_del_dialog->get_ok_button()->connect("pressed", callable_mp(this, &ThemeEditor::_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)); + 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); + + theme_edit_dialog = memnew(ThemeItemEditorDialog); + theme_edit_dialog->hide(); + 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)); + + theme_type_editor = memnew(ThemeTypeEditor); + main_hs->add_child(theme_type_editor); + theme_type_editor->set_custom_minimum_size(Size2(280, 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 ab199f8e51..e78b244a42 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -31,68 +31,392 @@ #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; + LineEdit *edit_add_type_value; + String edited_item_type; + + Button *edit_items_add_color; + Button *edit_items_add_constant; + Button *edit_items_add_font; + Button *edit_items_add_font_size; + Button *edit_items_add_icon; + Button *edit_items_add_stylebox; + Button *edit_items_remove_class; + Button *edit_items_remove_custom; + Button *edit_items_remove_all; + Tree *edit_items_tree; + + enum ItemsTreeAction { + ITEMS_TREE_RENAME_ITEM, + ITEMS_TREE_REMOVE_ITEM, + ITEMS_TREE_REMOVE_DATA_TYPE, + }; + + ConfirmationDialog *edit_theme_item_dialog; + VBoxContainer *edit_theme_item_old_vb; + Label *theme_item_old_name; + LineEdit *theme_item_name; + + enum ItemPopupMode { + CREATE_THEME_ITEM, + RENAME_THEME_ITEM, + ITEM_POPUP_MODE_MAX + }; + + ItemPopupMode item_popup_mode = ITEM_POPUP_MODE_MAX; + 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); + + 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_theme_type(const String &p_new_text); + 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(); + void _remove_custom_items(); + void _remove_all_items(); + + void _open_add_theme_item_dialog(int p_data_type); + void _open_rename_theme_item_dialog(Theme::DataType p_data_type, String p_item_name); + 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); + +public: + void set_edited_theme(const Ref<Theme> &p_theme); + + ThemeItemEditorDialog(); +}; + +class ThemeTypeDialog : public ConfirmationDialog { + GDCLASS(ThemeTypeDialog, ConfirmationDialog); + + Ref<Theme> edited_theme; + bool include_own_types = false; + + LineEdit *add_type_filter; + ItemList *add_type_options; + + void _dialog_about_to_show(); + void ok_pressed() override; + + void _update_add_type_options(const String &p_filter = ""); + + void _add_type_filter_cbk(const String &p_value); + void _add_type_options_cbk(int p_index); + void _add_type_dialog_entered(const String &p_value); + void _add_type_dialog_activated(int p_index); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_edited_theme(const Ref<Theme> &p_theme); + void set_include_own_types(bool p_enable); + + ThemeTypeDialog(); +}; + +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; + + 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; + + LineEdit *type_variation_edit; + Button *type_variation_button; + + enum TypeDialogMode { + ADD_THEME_TYPE, + ADD_VARIATION_BASE, + }; + + TypeDialogMode add_type_mode = ADD_THEME_TYPE; + ThemeTypeDialog *add_type_dialog; + + 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(); + 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_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(); + + void _type_variation_changed(const String p_value); + void _add_type_variation_cbk(); + + void _add_type_dialog_selected(const String p_type_name); + +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; - - double time_left; - - MenuButton *theme_menu; - ConfirmationDialog *add_del_dialog; - HBoxContainer *type_hbc; - MenuButton *type_menu; - LineEdit *type_edit; - HBoxContainer *name_hbc; - MenuButton *name_menu; - LineEdit *name_edit; - OptionButton *type_select; - Label *type_select_label; - Label *name_select_label; - - enum PopupMode { - POPUP_ADD, - POPUP_CLASS_ADD, - POPUP_REMOVE, - POPUP_CLASS_REMOVE, - POPUP_CREATE_EMPTY, - POPUP_CREATE_EDITOR_EMPTY, - POPUP_IMPORT_EDITOR_THEME - }; + Tabs *preview_tabs; + PanelContainer *preview_tabs_content; + Button *add_preview_button; + EditorFileDialog *preview_scene_dialog; + + ThemeTypeEditor *theme_type_editor; - int popup_mode; + Label *theme_name; + ThemeItemEditorDialog *theme_edit_dialog; - Tree *test_tree; + void _theme_save_button_cbk(bool p_save_as); + void _theme_edit_button_cbk(); - void _save_template_cbk(String fname); - void _dialog_cbk(); - void _type_menu_cbk(int p_option); - void _name_menu_about_to_show(); - void _name_menu_cbk(int p_option); - void _theme_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..801ee0eac2 --- /dev/null +++ b/editor/plugins/theme_editor_preview.cpp @@ -0,0 +1,499 @@ +/*************************************************************************/ +/* 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 = p_parent->get_child_count() - 1; i >= 0; 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()), theme_cache.preview_picker_overlay_color); + 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(theme_cache.preview_picker_overlay, highlight_rect); + + String highlight_name = hovered_control->get_theme_type_variation(); + if (highlight_name == StringName()) { + highlight_name = hovered_control->get_class_name(); + } + + Rect2 highlight_label_rect = highlight_rect; + highlight_label_rect.size = theme_cache.preview_picker_font->get_string_size(highlight_name); + + int margin_top = theme_cache.preview_picker_label->get_margin(SIDE_TOP); + int margin_left = theme_cache.preview_picker_label->get_margin(SIDE_LEFT); + int margin_bottom = theme_cache.preview_picker_label->get_margin(SIDE_BOTTOM); + int margin_right = theme_cache.preview_picker_label->get_margin(SIDE_RIGHT); + highlight_label_rect.size.x += margin_left + margin_right; + highlight_label_rect.size.y += margin_top + margin_bottom; + + highlight_label_rect.position.x = CLAMP(highlight_label_rect.position.x, 0.0, picker_overlay->get_size().width); + highlight_label_rect.position.y = CLAMP(highlight_label_rect.position.y, 0.0, picker_overlay->get_size().height); + picker_overlay->draw_style_box(theme_cache.preview_picker_label, highlight_label_rect); + + Point2 label_pos = highlight_label_rect.position; + label_pos.y += highlight_label_rect.size.y - margin_bottom; + label_pos.x += margin_left; + picker_overlay->draw_string(theme_cache.preview_picker_font, label_pos, highlight_name); + } +} + +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_type_variation(); + if (theme_type == StringName()) { + theme_type = hovered_control->get_class_name(); + } + + emit_signal(SNAME("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::_reset_picker_overlay() { + hovered_control = nullptr; + 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(SNAME("ColorPick"), SNAME("EditorIcons"))); + + theme_cache.preview_picker_overlay = get_theme_stylebox(SNAME("preview_picker_overlay"), SNAME("ThemeEditor")); + theme_cache.preview_picker_overlay_color = get_theme_color(SNAME("preview_picker_overlay_color"), SNAME("ThemeEditor")); + theme_cache.preview_picker_label = get_theme_stylebox(SNAME("preview_picker_label"), SNAME("ThemeEditor")); + theme_cache.preview_picker_font = get_theme_font(SNAME("status_source"), SNAME("EditorFonts")); + } 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_overlay->set_clip_contents(true); + 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)); + picker_overlay->connect("mouse_exited", callable_mp(this, &ThemeEditorPreview::_reset_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(SNAME("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(SNAME("scene_invalidated")); + return; + } + + preview_content->add_child(instance); + emit_signal(SNAME("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(SNAME("Reload"), SNAME("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..4e1b149e70 --- /dev/null +++ b/editor/plugins/theme_editor_preview.h @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* 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; + + struct ThemeCache { + Ref<StyleBox> preview_picker_overlay; + Color preview_picker_overlay_color; + Ref<StyleBox> preview_picker_label; + Ref<Font> preview_picker_font; + } theme_cache; + + 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); + void _reset_picker_overlay(); + +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 6a16aa28a9..0000000000 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ /dev/null @@ -1,2318 +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) { - // 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_mp(this, &TileMapEditor::_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() == BUTTON_WHEEL_UP) { - size_slider->set_value(size_slider->get_value() + 0.2); - } - - if (mb->is_pressed() && mb->get_button_index() == 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() == 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() == 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(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) { - node->disconnect("settings_changed", callable_mp(this, &TileMapEditor::_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) { - node->connect("settings_changed", callable_mp(this, &TileMapEditor::_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 c628fe8367..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()) { - 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("LargeTexture", "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() == 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() == 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() == 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() & 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() == 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() == BUTTON_RIGHT) { - dragging = false; - edited_region = Rect2(); - workspace->update(); - workspace_overlay->update(); - return; - } else if (dragging && !mb->is_pressed() && mb->get_button_index() == 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() == 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() == BUTTON_RIGHT || mb->get_button_index() == BUTTON_LEFT) && current_tile_region.has_point(mb->get_position())) { - dragging = true; - erasing = (mb->get_button_index() == 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() == BUTTON_RIGHT) || (!erasing && mb->get_button_index() == 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() == 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() == 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() == 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() == 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() == 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() == BUTTON_RIGHT) { - if (creating_shape) { - creating_shape = false; - _select_edited_shape_coord(); - workspace->update(); - } - } else if (!mb->is_pressed() && mb->get_button_index() == 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.invert(); - } - - 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/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp new file mode 100644 index 0000000000..bbafc7802b --- /dev/null +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -0,0 +1,320 @@ +/*************************************************************************/ +/* atlas_merging_dialog.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 "atlas_merging_dialog.h" + +#include "editor/editor_scale.h" + +#include "scene/gui/control.h" +#include "scene/gui/split_container.h" + +void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) { + _set(p_property, p_value); +} + +void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns) { + merged.instantiate(); + merged_mapping.clear(); + + if (p_atlas_sources.size() >= 2) { + Ref<Image> output_image; + output_image.instantiate(); + output_image->create(1, 1, false, Image::FORMAT_RGBA8); + + // Compute the new texture region size. + Vector2i new_texture_region_size; + for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { + Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size()); + } + + // Generate the merged TileSetAtlasSource. + Vector2i atlas_offset; + int line_height = 0; + for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { + Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + merged_mapping.push_back(Map<Vector2i, Vector2i>()); + + // Layout the tiles. + Vector2i atlas_size; + + for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) { + Vector2i tile_id = atlas_source->get_tile_id(tile_index); + atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id)); + + Rect2i new_tile_rect_in_altas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id)); + + // Create tiles and alternatives, then copy their properties. + for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_id); alternative_index++) { + int alternative_id = atlas_source->get_alternative_tile_id(tile_id, alternative_index); + if (alternative_id == 0) { + merged->create_tile(new_tile_rect_in_altas.position, new_tile_rect_in_altas.size); + } else { + merged->create_alternative_tile(new_tile_rect_in_altas.position, alternative_index); + } + + // Copy the properties. + TileData *original_tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + List<PropertyInfo> properties; + original_tile_data->get_property_list(&properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + const StringName &property_name = E->get().name; + merged->set(property_name, original_tile_data->get(property_name)); + } + + // Add to the mapping. + merged_mapping[source_index][tile_id] = new_tile_rect_in_altas.position; + } + + // Copy the texture. + Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id); + Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.size * new_texture_region_size); + if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) { + output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height())); + } + output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, (dst_rect_wide.get_position() + dst_rect_wide.get_end()) / 2 - src_rect.size / 2); + } + + // Compute the atlas offset. + line_height = MAX(atlas_size.y, line_height); + atlas_offset.x += atlas_size.x; + if (atlas_offset.x >= p_max_columns) { + atlas_offset.x = 0; + atlas_offset.y += line_height; + line_height = 0; + } + } + + Ref<ImageTexture> output_image_texture; + output_image_texture.instantiate(); + output_image_texture->create_from_image(output_image); + + merged->set_texture(output_image_texture); + merged->set_texture_region_size(new_texture_region_size); + } +} + +void AtlasMergingDialog::_update_texture() { + Vector<int> selected = atlas_merging_atlases_list->get_selected_items(); + if (selected.size() >= 2) { + Vector<Ref<TileSetAtlasSource>> to_merge; + for (int i = 0; i < selected.size(); i++) { + int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]); + to_merge.push_back(tile_set->get_source(source_id)); + } + _generate_merged(to_merge, next_line_after_column); + preview->set_texture(merged->get_texture()); + preview->show(); + select_2_atlases_label->hide(); + get_ok_button()->set_disabled(false); + merge_button->set_disabled(false); + } else { + _generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column); + preview->set_texture(Ref<Texture2D>()); + preview->hide(); + select_2_atlases_label->show(); + get_ok_button()->set_disabled(true); + merge_button->set_disabled(true); + } +} + +void AtlasMergingDialog::_merge_confirmed(String p_path) { + ERR_FAIL_COND(!merged.is_valid()); + + Ref<ImageTexture> output_image_texture = merged->get_texture(); + output_image_texture->get_image()->save_png(p_path); + + Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D"); + merged->set_texture(new_texture_resource); + + undo_redo->create_action("Merge TileSetAtlasSource"); + int next_id = tile_set->get_next_source_id(); + undo_redo->add_do_method(*tile_set, "add_source", merged, next_id); + undo_redo->add_undo_method(*tile_set, "remove_source", next_id); + + if (delete_original_atlases) { + // Delete originals if needed. + Vector<int> selected = atlas_merging_atlases_list->get_selected_items(); + for (int i = 0; i < selected.size(); i++) { + int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]); + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + undo_redo->add_do_method(*tile_set, "remove_source", source_id); + undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id); + + // Add the tile proxies. + for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) { + Vector2i tile_id = tas->get_tile_id(tile_index); + undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]); + if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) { + Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id); + } + } + } + } + undo_redo->commit_action(); + commited_actions_count++; + + hide(); +} + +void AtlasMergingDialog::ok_pressed() { + delete_original_atlases = false; + editor_file_dialog->popup_file_dialog(); +} + +void AtlasMergingDialog::cancel_pressed() { + for (int i = 0; i < commited_actions_count; i++) { + undo_redo->undo(); + } + commited_actions_count = 0; +} + +void AtlasMergingDialog::custom_action(const String &p_action) { + if (p_action == "merge") { + delete_original_atlases = true; + editor_file_dialog->popup_file_dialog(); + } +} + +bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) { + next_line_after_column = p_value; + _update_texture(); + return true; + } + return false; +} + +bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "next_line_after_column") { + r_ret = next_line_after_column; + return true; + } + return false; +} + +void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; + + atlas_merging_atlases_list->clear(); + for (int i = 0; i < p_tile_set->get_source_count(); i++) { + int source_id = p_tile_set->get_source_id(i); + Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id); + if (atlas_source.is_valid()) { + Ref<Texture2D> texture = atlas_source->get_texture(); + if (texture.is_valid()) { + String item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + atlas_merging_atlases_list->add_item(item_text, texture); + atlas_merging_atlases_list->set_item_metadata(atlas_merging_atlases_list->get_item_count() - 1, source_id); + } + } + } + + get_ok_button()->set_disabled(true); + merge_button->set_disabled(true); + + commited_actions_count = 0; +} + +AtlasMergingDialog::AtlasMergingDialog() { + // Atlas merging window. + set_title(TTR("Atlas Merging")); + set_hide_on_ok(false); + + // Ok buttons + get_ok_button()->set_text(TTR("Merge (Keep original Atlases)")); + get_ok_button()->set_disabled(true); + merge_button = add_button(TTR("Merge"), true, "merge"); + merge_button->set_disabled(true); + + HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer); + atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + add_child(atlas_merging_h_split_container); + + // Atlas sources item list. + atlas_merging_atlases_list = memnew(ItemList); + atlas_merging_atlases_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200)); + atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI); + atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2)); + atlas_merging_h_split_container->add_child(atlas_merging_atlases_list); + + VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer); + atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_h_split_container->add_child(atlas_merging_right_panel); + + // Settings. + Label *settings_label = memnew(Label); + settings_label->set_text(TTR("Settings:")); + atlas_merging_right_panel->add_child(settings_label); + + columns_editor_property = memnew(EditorPropertyInteger); + columns_editor_property->set_label(TTR("Next Line After Column")); + columns_editor_property->set_object_and_property(this, "next_line_after_column"); + columns_editor_property->update_property(); + columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed)); + atlas_merging_right_panel->add_child(columns_editor_property); + + // Preview. + Label *preview_label = memnew(Label); + preview_label->set_text(TTR("Preview:")); + atlas_merging_right_panel->add_child(preview_label); + + preview = memnew(TextureRect); + preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_expand(true); + preview->hide(); + preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + atlas_merging_right_panel->add_child(preview); + + select_2_atlases_label = memnew(Label); + select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + select_2_atlases_label->set_align(Label::ALIGN_CENTER); + select_2_atlases_label->set_valign(Label::VALIGN_CENTER); + select_2_atlases_label->set_text(TTR("Please select two atlases or more.")); + atlas_merging_right_panel->add_child(select_2_atlases_label); + + // The file dialog to choose the texture path. + editor_file_dialog = memnew(EditorFileDialog); + editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + editor_file_dialog->add_filter("*.png"); + editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed)); + add_child(editor_file_dialog); +} diff --git a/editor/plugins/tiles/atlas_merging_dialog.h b/editor/plugins/tiles/atlas_merging_dialog.h new file mode 100644 index 0000000000..7cb54bc17e --- /dev/null +++ b/editor/plugins/tiles/atlas_merging_dialog.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* atlas_merging_dialog.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 ATLAS_MERGING_DIALOG_H +#define ATLAS_MERGING_DIALOG_H + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/gui/texture_rect.h" +#include "scene/resources/tile_set.h" + +class AtlasMergingDialog : public ConfirmationDialog { + GDCLASS(AtlasMergingDialog, ConfirmationDialog); + +private: + int commited_actions_count = 0; + bool delete_original_atlases = true; + Ref<TileSetAtlasSource> merged; + LocalVector<Map<Vector2i, Vector2i>> merged_mapping; + Ref<TileSet> tile_set; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + // Settings. + int next_line_after_column = 30; + + // GUI. + ItemList *atlas_merging_atlases_list; + EditorPropertyVector2i *texture_region_size_editor_property; + EditorPropertyInteger *columns_editor_property; + TextureRect *preview; + Label *select_2_atlases_label; + EditorFileDialog *editor_file_dialog; + Button *merge_button; + + void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); + + void _generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns); + void _update_texture(); + void _merge_confirmed(String p_path); + +protected: + virtual void ok_pressed() override; + virtual void cancel_pressed() override; + virtual void custom_action(const String &) override; + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + +public: + void update_tile_set(Ref<TileSet> p_tile_set); + + AtlasMergingDialog(); +}; + +#endif // ATLAS_MERGING_DIALOG_H diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp new file mode 100644 index 0000000000..84e40e2ffa --- /dev/null +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -0,0 +1,687 @@ +/*************************************************************************/ +/* 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) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + drag_type = DRAG_TYPE_NONE; + + Vector2i scroll_vec = Vector2((mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT) - (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT), (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) - (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN)); + if (scroll_vec != Vector2()) { + if (mb->is_ctrl_pressed()) { + if (mb->is_shift_pressed()) { + panning.x += 32 * mb->get_factor() * scroll_vec.y; + panning.y += 32 * mb->get_factor() * scroll_vec.x; + } else { + panning.y += 32 * mb->get_factor() * scroll_vec.y; + panning.x += 32 * mb->get_factor() * scroll_vec.x; + } + + emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); + _update_zoom_and_panning(true); + accept_event(); + + } else if (!mb->is_shift_pressed()) { + zoom_widget->set_zoom_by_increments(scroll_vec.y * 2); + emit_signal(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("Checkerboard"), SNAME("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(SNAME("Checkerboard"), SNAME("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(SNAME("CenterView"), SNAME("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(SNAME("CenterView"), SNAME("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); + button_center_view->set_tooltip(TTR("Center View")); + 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..b2046f4322 --- /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 = TileSet::INVALID_SOURCE; + + 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..dd2922286f --- /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(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); + const Ref<Texture2D> add_handle = get_theme_icon(SNAME("EditorHandleAdd"), SNAME("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(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("CurveCreate"), SNAME("EditorIcons"))); + button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); + button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenu"), SNAME("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(SNAME("CenterView"), SNAME("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(SNAME("bold"), SNAME("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(SNAME("ColorPick"), SNAME("EditorIcons"))); + tile_bool_checked = get_theme_icon(SNAME("TileChecked"), SNAME("EditorIcons")); + tile_bool_unchecked = get_theme_icon(SNAME("TileUnchecked"), SNAME("EditorIcons")); + break; + default: + break; + } +} + +TileDataDefaultEditor::TileDataDefaultEditor() { + label = memnew(Label); + label->set_text(TTR("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(SNAME("EditorPosition"), SNAME("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(SNAME("EditorPosition"), SNAME("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(SNAME("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(SNAME("bold"), SNAME("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(SNAME("bold"), SNAME("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(SNAME("ColorPick"), SNAME("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..44822bec3a --- /dev/null +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -0,0 +1,3570 @@ +/*************************************************************************/ +/* 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(SNAME("ToolSelect"), SNAME("EditorIcons"))); + paint_tool_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + line_tool_button->set_icon(get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons"))); + rect_tool_button->set_icon(get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons"))); + bucket_tool_button->set_icon(get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons"))); + + picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); + erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); + + missing_atlas_texture_icon = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); + break; + case NOTIFICATION_VISIBILITY_CHANGED: + _stop_dragging(); + 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::_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(SNAME("PackedScene"), SNAME("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(SNAME("item_selected"), sources_list->get_current()); + } + + // Synchronize + TilesEditor::get_singleton()->set_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(SNAME("PackedScene"), SNAME("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(), TileSet::INVALID_SOURCE, 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(), TileSet::INVALID_SOURCE, 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 == TileSet::INVALID_SOURCE) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, tile_map->get_cell(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 == TileSet::INVALID_SOURCE) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, tile_map->get_cell(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, tile_map->get_cell(coords)); + tile_map->set_cell(coords, TileSet::INVALID_SOURCE, 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 == TileSet::INVALID_SOURCE) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, tile_map->get_cell(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 == TileSet::INVALID_SOURCE) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, tile_map->get_cell(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) != TileSet::INVALID_SOURCE) { + Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size)); + tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false); + } + } + } + } 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) != TileSet::INVALID_SOURCE) { + 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), TileSet::INVALID_SOURCE, 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), TileSet::INVALID_SOURCE, 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), TileSet::INVALID_SOURCE, 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 == TileSet::INVALID_SOURCE) { + 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 != TileSet::INVALID_SOURCE || 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 == TileSet::INVALID_SOURCE) { + 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 != TileSet::INVALID_SOURCE || 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) != TileSet::INVALID_SOURCE) { + 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) != TileSet::INVALID_SOURCE) { + 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 == TileSet::INVALID_SOURCE) { + 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 == TileSet::INVALID_SOURCE) { + 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 == TileSet::INVALID_SOURCE) { + 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 = TileSet::INVALID_SOURCE; + 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 = TileSet::INVALID_SOURCE; + 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 = TileSet::INVALID_SOURCE; + 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 = TileSet::INVALID_SOURCE; + 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) != TileSet::INVALID_SOURCE) { + 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 = TileSet::INVALID_SOURCE; + 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 = TileSet::INVALID_SOURCE; + 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); + + // 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_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_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(SNAME("Edit"), SNAME("EditorIcons"))); + picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); + erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("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 = TileSet::INVALID_SOURCE; + 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(SNAME("TerrainMatchCornersAndSides"), SNAME("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(SNAME("TerrainMatchCorners"), SNAME("EditorIcons"))); + matches = String(TTR("Matches Corners Only")); + } else { + terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchSides"), SNAME("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(SNAME("StatusWarning"), SNAME("EditorIcons")); + warning_pattern_texture = get_theme_icon(SNAME("WarningPattern"), SNAME("EditorIcons")); + advanced_menu_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); + toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); + 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; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); + break; + } +} + +void TileMapEditor::_on_grid_toggled(bool p_pressed) { + EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed); +} + +void TileMapEditor::_advanced_menu_button_id_pressed(int p_id) { + 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 (p_id == 0) { // Replace Tile Proxies + undo_redo->create_action(TTR("Replace Tiles with Proxies")); + TypedArray<Vector2i> used_cells = tile_map->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i cell_coords = used_cells[i]; + TileMapCell from = tile_map->get_cell(cell_coords); + Array to_array = tile_set->map_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile); + TileMapCell to; + to.source_id = to_array[0]; + to.set_atlas_coords(to_array[1]); + to.alternative_tile = to_array[2]; + if (from != to) { + undo_redo->add_do_method(tile_map, "set_cell", cell_coords, to.source_id, to.get_atlas_coords(), to.alternative_tile); + undo_redo->add_undo_method(tile_map, "set_cell", cell_coords, from.source_id, from.get_atlas_coords(), from.alternative_tile); + } + } + undo_redo->commit_action(); + } +} + +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. + 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 a = tile_set->map_tile_proxy(tile_source_id, tile_atlas_coords, tile_alternative_tile); + if (int(a[0]) == tile_source_id && Vector2i(a[1]) == tile_atlas_coords && int(a[2]) == tile_alternative_tile) { + // Only display the pattern if we have no proxy tile. + 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. + int min_axis = missing_tile_texture->get_size().min_axis(); + Vector2 icon_size; + icon_size[min_axis] = tile_set->get_tile_size()[min_axis] / 3; + icon_size[(min_axis + 1) % 2] = (icon_size[min_axis] * missing_tile_texture->get_size()[(min_axis + 1) % 2] / missing_tile_texture->get_size()[min_axis]); + Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_size * xform.get_scale() / 2), icon_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(SNAME("font"), SNAME("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()); + } + + // Wide empty separation control. + Control *h_empty_space = memnew(Control); + h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL); + tilemap_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->set_tooltip(TTR("Toggle grid visibility.")); + toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditor::_on_grid_toggled)); + tilemap_toolbar->add_child(toggle_grid_button); + + // Advanced settings menu button. + advanced_menu_button = memnew(MenuButton); + advanced_menu_button->set_flat(true); + advanced_menu_button->get_popup()->add_item(TTR("Automatically Replace Tiles with Proxies")); + advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileMapEditor::_advanced_menu_button_id_pressed)); + tilemap_toolbar->add_child(advanced_menu_button); + + 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..236774a06b --- /dev/null +++ b/editor/plugins/tiles/tile_map_editor.h @@ -0,0 +1,347 @@ +/*************************************************************************/ +/* 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); + + 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: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + bool tileset_changed_needs_update = false; + ObjectID tile_map_id; + + // Vector to keep plugins. + Vector<TileMapEditorPlugin *> tile_map_editor_plugins; + + // Toolbar. + HBoxContainer *tilemap_toolbar; + + Button *toggle_grid_button; + void _on_grid_toggled(bool p_pressed); + + MenuButton *advanced_menu_button; + void _advanced_menu_button_id_pressed(int p_id); + + // 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_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp new file mode 100644 index 0000000000..e5e7efa531 --- /dev/null +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -0,0 +1,476 @@ +/*************************************************************************/ +/* tile_proxies_manager_dialog.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_proxies_manager_dialog.h" + +#include "editor/editor_scale.h" + +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list) { + ItemList *item_list = Object::cast_to<ItemList>(p_item_list); + popup_menu->set_size(Vector2(1, 1)); + popup_menu->set_position(get_position() + item_list->get_global_mouse_position()); + popup_menu->popup(); +} + +void TileProxiesManagerDialog::_menu_id_pressed(int p_id) { + if (p_id == 0) { + // Delete. + _delete_selected_bindings(); + } +} + +void TileProxiesManagerDialog::_delete_selected_bindings() { + undo_redo->create_action("Remove Tile Proxies"); + + Vector<int> source_level_selected = source_level_list->get_selected_items(); + for (int i = 0; i < source_level_selected.size(); i++) { + int key = source_level_list->get_item_metadata(source_level_selected[i]); + int val = tile_set->get_source_level_tile_proxy(key); + undo_redo->add_do_method(*tile_set, "remove_source_level_tile_proxy", key); + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", key, val); + } + + Vector<int> coords_level_selected = coords_level_list->get_selected_items(); + for (int i = 0; i < coords_level_selected.size(); i++) { + Array key = coords_level_list->get_item_metadata(coords_level_selected[i]); + Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + undo_redo->add_do_method(*tile_set, "remove_coords_level_tile_proxy", key[0], key[1]); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", key[0], key[1], val[0], val[1]); + } + + Vector<int> alternative_level_selected = alternative_level_list->get_selected_items(); + for (int i = 0; i < alternative_level_selected.size(); i++) { + Array key = alternative_level_list->get_item_metadata(alternative_level_selected[i]); + Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + undo_redo->add_do_method(*tile_set, "remove_alternative_level_tile_proxy", key[0], key[1], key[2]); + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", key[0], key[1], key[2], val[0], val[1], val[2]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); + + commited_actions_count += 1; +} + +void TileProxiesManagerDialog::_update_lists() { + source_level_list->clear(); + coords_level_list->clear(); + alternative_level_list->clear(); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s", proxy[0]).rpad(5) + "-> " + vformat("%s", proxy[1]); + int id = source_level_list->add_item(text); + source_level_list->set_item_metadata(id, proxy[0]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s, %s", proxy[0], proxy[1]).rpad(17) + "-> " + vformat("%s, %s", proxy[2], proxy[3]); + int id = coords_level_list->add_item(text); + coords_level_list->set_item_metadata(id, proxy.slice(0, 2)); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s, %s, %s", proxy[0], proxy[1], proxy[2]).rpad(24) + "-> " + vformat("%s, %s, %s", proxy[3], proxy[4], proxy[5]); + int id = alternative_level_list->add_item(text); + alternative_level_list->set_item_metadata(id, proxy.slice(0, 3)); + } +} + +void TileProxiesManagerDialog::_update_enabled_property_editors() { + if (from.source_id == TileSet::INVALID_SOURCE) { + from.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + to.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + coords_from_property_editor->hide(); + coords_to_property_editor->hide(); + alternative_from_property_editor->hide(); + alternative_to_property_editor->hide(); + } else if (from.get_atlas_coords().x == -1 || from.get_atlas_coords().y == -1) { + from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + coords_from_property_editor->show(); + coords_to_property_editor->show(); + alternative_from_property_editor->hide(); + alternative_to_property_editor->hide(); + } else { + coords_from_property_editor->show(); + coords_to_property_editor->show(); + alternative_from_property_editor->show(); + alternative_to_property_editor->show(); + } + + source_from_property_editor->update_property(); + source_to_property_editor->update_property(); + coords_from_property_editor->update_property(); + coords_to_property_editor->update_property(); + alternative_from_property_editor->update_property(); + alternative_to_property_editor->update_property(); +} + +void TileProxiesManagerDialog::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { + _set(p_path, p_value); +} + +void TileProxiesManagerDialog::_add_button_pressed() { + if (from.source_id != TileSet::INVALID_SOURCE && to.source_id != TileSet::INVALID_SOURCE) { + Vector2i from_coords = from.get_atlas_coords(); + Vector2i to_coords = to.get_atlas_coords(); + if (from_coords.x >= 0 && from_coords.y >= 0 && to_coords.x >= 0 && to_coords.y >= 0) { + if (from.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE && to.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE) { + undo_redo->create_action("Create Alternative-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile, to.source_id, to.get_atlas_coords(), to.alternative_tile); + if (tile_set->has_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile)) { + Array a = tile_set->get_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile); + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", to.source_id, to.get_atlas_coords(), to.alternative_tile, a[0], a[1], a[2]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile); + } + } else { + undo_redo->create_action("Create Coords-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", from.source_id, from.get_atlas_coords(), to.source_id, to.get_atlas_coords()); + if (tile_set->has_coords_level_tile_proxy(from.source_id, from.get_atlas_coords())) { + Array a = tile_set->get_coords_level_tile_proxy(from.source_id, from.get_atlas_coords()); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", to.source_id, to.get_atlas_coords(), a[0], a[1]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", from.source_id, from.get_atlas_coords()); + } + } + } else { + undo_redo->create_action("Create source-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_source_level_tile_proxy", from.source_id, to.source_id); + if (tile_set->has_source_level_tile_proxy(from.source_id)) { + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", to.source_id, tile_set->get_source_level_tile_proxy(from.source_id)); + } else { + undo_redo->add_undo_method(*tile_set, "remove_source_level_tile_proxy", from.source_id); + } + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); + commited_actions_count++; + } +} + +void TileProxiesManagerDialog::_clear_invalid_button_pressed() { + undo_redo->create_action("Delete All Invalid Tile Proxies"); + + undo_redo->add_do_method(*tile_set, "cleanup_invalid_tile_proxies"); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); +} + +void TileProxiesManagerDialog::_clear_all_button_pressed() { + undo_redo->create_action("Delete All Tile Proxies"); + + undo_redo->add_do_method(*tile_set, "clear_tile_proxies"); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); +} + +bool TileProxiesManagerDialog::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "from_source") { + from.source_id = MAX(int(p_value), -1); + } else if (p_name == "from_coords") { + from.set_atlas_coords(Vector2i(p_value).max(Vector2i(-1, -1))); + } else if (p_name == "from_alternative") { + from.alternative_tile = MAX(int(p_value), -1); + } else if (p_name == "to_source") { + to.source_id = MAX(int(p_value), 0); + } else if (p_name == "to_coords") { + to.set_atlas_coords(Vector2i(p_value).max(Vector2i(0, 0))); + } else if (p_name == "to_alternative") { + to.alternative_tile = MAX(int(p_value), 0); + } else { + return false; + } + _update_enabled_property_editors(); + return true; +} + +bool TileProxiesManagerDialog::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "from_source") { + r_ret = from.source_id; + } else if (p_name == "from_coords") { + r_ret = from.get_atlas_coords(); + } else if (p_name == "from_alternative") { + r_ret = from.alternative_tile; + } else if (p_name == "to_source") { + r_ret = to.source_id; + } else if (p_name == "to_coords") { + r_ret = to.get_atlas_coords(); + } else if (p_name == "to_alternative") { + r_ret = to.alternative_tile; + } else { + return false; + } + return true; +} + +void TileProxiesManagerDialog::_unhandled_key_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + + if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event))) { + if (!is_inside_tree() || !is_visible()) { + return; + } + + if (popup_menu->activate_item_by_event(p_event, false)) { + set_input_as_handled(); + } + } +} + +void TileProxiesManagerDialog::cancel_pressed() { + for (int i = 0; i < commited_actions_count; i++) { + undo_redo->undo(); + } + commited_actions_count = 0; +} + +void TileProxiesManagerDialog::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_lists"), &TileProxiesManagerDialog::_update_lists); + ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileProxiesManagerDialog::_unhandled_key_input); +} + +void TileProxiesManagerDialog::update_tile_set(Ref<TileSet> p_tile_set) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; + commited_actions_count = 0; + _update_lists(); +} + +TileProxiesManagerDialog::TileProxiesManagerDialog() { + // Tile proxy management window. + set_title(TTR("Tile Proxies Management")); + set_process_unhandled_key_input(true); + + to.source_id = 0; + to.set_atlas_coords(Vector2i()); + to.alternative_tile = 0; + + VBoxContainer *vbox_container = memnew(VBoxContainer); + vbox_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vbox_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + add_child(vbox_container); + + Label *source_level_label = memnew(Label); + source_level_label->set_text(TTR("Source-level proxies")); + vbox_container->add_child(source_level_label); + + source_level_list = memnew(ItemList); + source_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + source_level_list->set_select_mode(ItemList::SELECT_MULTI); + source_level_list->set_allow_rmb_select(true); + source_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(source_level_list)); + vbox_container->add_child(source_level_list); + + Label *coords_level_label = memnew(Label); + coords_level_label->set_text(TTR("Coords-level proxies")); + vbox_container->add_child(coords_level_label); + + coords_level_list = memnew(ItemList); + coords_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + coords_level_list->set_select_mode(ItemList::SELECT_MULTI); + coords_level_list->set_allow_rmb_select(true); + coords_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(coords_level_list)); + vbox_container->add_child(coords_level_list); + + Label *alternative_level_label = memnew(Label); + alternative_level_label->set_text(TTR("Alternative-level proxies")); + vbox_container->add_child(alternative_level_label); + + alternative_level_list = memnew(ItemList); + alternative_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + alternative_level_list->set_select_mode(ItemList::SELECT_MULTI); + alternative_level_list->set_allow_rmb_select(true); + alternative_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(alternative_level_list)); + vbox_container->add_child(alternative_level_list); + + popup_menu = memnew(PopupMenu); + popup_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_delete")); + popup_menu->connect("id_pressed", callable_mp(this, &TileProxiesManagerDialog::_menu_id_pressed)); + add_child(popup_menu); + + // Add proxy panel. + HSeparator *h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); + + Label *add_label = memnew(Label); + add_label->set_text(TTR("Add a new tile proxy:")); + vbox_container->add_child(add_label); + + HBoxContainer *hboxcontainer = memnew(HBoxContainer); + vbox_container->add_child(hboxcontainer); + + // From + VBoxContainer *vboxcontainer_from = memnew(VBoxContainer); + vboxcontainer_from->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hboxcontainer->add_child(vboxcontainer_from); + + source_from_property_editor = memnew(EditorPropertyInteger); + source_from_property_editor->set_label(TTR("From Source")); + source_from_property_editor->set_object_and_property(this, "from_source"); + source_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + source_from_property_editor->set_selectable(false); + source_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + source_from_property_editor->setup(-1, 99999, 1, true, false); + vboxcontainer_from->add_child(source_from_property_editor); + + coords_from_property_editor = memnew(EditorPropertyVector2i); + coords_from_property_editor->set_label(TTR("From Coords")); + coords_from_property_editor->set_object_and_property(this, "from_coords"); + coords_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + coords_from_property_editor->set_selectable(false); + coords_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + coords_from_property_editor->setup(-1, 99999, true); + coords_from_property_editor->hide(); + vboxcontainer_from->add_child(coords_from_property_editor); + + alternative_from_property_editor = memnew(EditorPropertyInteger); + alternative_from_property_editor->set_label(TTR("From Alternative")); + alternative_from_property_editor->set_object_and_property(this, "from_alternative"); + alternative_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + alternative_from_property_editor->set_selectable(false); + alternative_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + alternative_from_property_editor->setup(-1, 99999, 1, true, false); + alternative_from_property_editor->hide(); + vboxcontainer_from->add_child(alternative_from_property_editor); + + // To + VBoxContainer *vboxcontainer_to = memnew(VBoxContainer); + vboxcontainer_to->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hboxcontainer->add_child(vboxcontainer_to); + + source_to_property_editor = memnew(EditorPropertyInteger); + source_to_property_editor->set_label(TTR("To Source")); + source_to_property_editor->set_object_and_property(this, "to_source"); + source_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + source_to_property_editor->set_selectable(false); + source_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + source_to_property_editor->setup(-1, 99999, 1, true, false); + vboxcontainer_to->add_child(source_to_property_editor); + + coords_to_property_editor = memnew(EditorPropertyVector2i); + coords_to_property_editor->set_label(TTR("To Coords")); + coords_to_property_editor->set_object_and_property(this, "to_coords"); + coords_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + coords_to_property_editor->set_selectable(false); + coords_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + coords_to_property_editor->setup(-1, 99999, true); + coords_to_property_editor->hide(); + vboxcontainer_to->add_child(coords_to_property_editor); + + alternative_to_property_editor = memnew(EditorPropertyInteger); + alternative_to_property_editor->set_label(TTR("To Alternative")); + alternative_to_property_editor->set_object_and_property(this, "to_alternative"); + alternative_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + alternative_to_property_editor->set_selectable(false); + alternative_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + alternative_to_property_editor->setup(-1, 99999, 1, true, false); + alternative_to_property_editor->hide(); + vboxcontainer_to->add_child(alternative_to_property_editor); + + Button *add_button = memnew(Button); + add_button->set_text(TTR("Add")); + add_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + add_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_add_button_pressed)); + vbox_container->add_child(add_button); + + h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); + + // Generic actions. + Label *generic_actions_label = memnew(Label); + generic_actions_label->set_text(TTR("Global actions:")); + vbox_container->add_child(generic_actions_label); + + hboxcontainer = memnew(HBoxContainer); + vbox_container->add_child(hboxcontainer); + + Button *clear_invalid_button = memnew(Button); + clear_invalid_button->set_text(TTR("Clear Invalid")); + clear_invalid_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + clear_invalid_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_clear_invalid_button_pressed)); + hboxcontainer->add_child(clear_invalid_button); + + Button *clear_all_button = memnew(Button); + clear_all_button->set_text(TTR("Clear All")); + clear_all_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + clear_all_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_clear_all_button_pressed)); + hboxcontainer->add_child(clear_all_button); + + h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); +} diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h new file mode 100644 index 0000000000..f6898e960b --- /dev/null +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* tile_proxies_manager_dialog.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_PROXIES_MANAGER_DIALOG_H +#define TILE_PROXIES_MANAGER_DIALOG_H + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/resources/tile_set.h" + +class TileProxiesManagerDialog : public ConfirmationDialog { + GDCLASS(TileProxiesManagerDialog, ConfirmationDialog); + +private: + int commited_actions_count = 0; + Ref<TileSet> tile_set; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + TileMapCell from; + TileMapCell to; + + // GUI + ItemList *source_level_list; + ItemList *coords_level_list; + ItemList *alternative_level_list; + + EditorPropertyInteger *source_from_property_editor; + EditorPropertyVector2i *coords_from_property_editor; + EditorPropertyInteger *alternative_from_property_editor; + EditorPropertyInteger *source_to_property_editor; + EditorPropertyVector2i *coords_to_property_editor; + EditorPropertyInteger *alternative_to_property_editor; + + PopupMenu *popup_menu; + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list); + void _menu_id_pressed(int p_id); + void _delete_selected_bindings(); + void _update_lists(); + void _update_enabled_property_editors(); + void _property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing); + void _add_button_pressed(); + + void _clear_invalid_button_pressed(); + void _clear_all_button_pressed(); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _unhandled_key_input(Ref<InputEvent> p_event); + virtual void cancel_pressed() override; + static void _bind_methods(); + +public: + void update_tile_set(Ref<TileSet> p_tile_set); + + TileProxiesManagerDialog(); +}; + +#endif // TILE_PROXIES_MANAGER_DIALOG_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..de910dfd32 --- /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(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("prop_category"), SNAME("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(SNAME("arrow"), SNAME("OptionButton"))) { + return; + } + + RID ci = tile_data_editor_dropdown_button->get_canvas_item(); + Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow"), SNAME("OptionButton")); + Color clr = Color(1, 1, 1); + if (get_theme_constant(SNAME("modulate_arrow"))) { + switch (tile_data_editor_dropdown_button->get_draw_mode()) { + case BaseButton::DRAW_PRESSED: + clr = get_theme_color(SNAME("font_pressed_color")); + break; + case BaseButton::DRAW_HOVER: + clr = get_theme_color(SNAME("font_hover_color")); + break; + case BaseButton::DRAW_DISABLED: + clr = get_theme_color(SNAME("font_disabled_color")); + break; + default: + clr = get_theme_color(SNAME("font_color")); + } + } + + Size2 size = tile_data_editor_dropdown_button->get_size(); + + Point2 ofs; + if (is_layout_rtl()) { + ofs = Point2(get_theme_constant(SNAME("arrow_margin"), SNAME("OptionButton")), int(Math::abs((size.height - arrow->get_height()) / 2))); + } else { + ofs = Point2(size.width - arrow->get_width() - get_theme_constant(SNAME("arrow_margin"), SNAME("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(SNAME("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(SNAME("Add"), SNAME("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, TileSetSource::INVALID_TILE_ALTERNATIVE)); + 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(SNAME("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(SNAME("Tools"), SNAME("EditorIcons"))); + tool_select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_paint_button->set_icon(get_theme_icon(SNAME("CanvasItem"), SNAME("EditorIcons"))); + + tools_settings_erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); + + tool_advanced_menu_buttom->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + + resize_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("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("Auto Create Tiles 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..501416c340 --- /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 = TileSet::INVALID_SOURCE; + + 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 = TileSet::INVALID_SOURCE; + + 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..ba98a7d6b3 --- /dev/null +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -0,0 +1,541 @@ +/*************************************************************************/ +/* 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 = TileSet::INVALID_SOURCE; + 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_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_sources_list(int force_selected_id) { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Get the previously selected id. + int old_selected = TileSet::INVALID_SOURCE; + 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 = TileSet::INVALID_SOURCE; + 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(SNAME("PackedScene"), SNAME("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(SNAME("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(SNAME("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_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_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_sources_list(); +} + +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_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_sources_list(source_id); + } break; + default: + ERR_FAIL(); + } +} + +void TileSetEditor::_sources_advanced_menu_id_pressed(int p_id_pressed) { + ERR_FAIL_COND(!tile_set.is_valid()); + + switch (p_id_pressed) { + case 0: { + atlas_merging_dialog->update_tile_set(tile_set); + atlas_merging_dialog->popup_centered_ratio(0.5); + } break; + case 1: { + tile_proxies_manager_dialog->update_tile_set(tile_set); + tile_proxies_manager_dialog->popup_centered_ratio(0.5); + } break; + } +} + +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(SNAME("Remove"), SNAME("EditorIcons"))); + sources_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + sources_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + missing_texture_texture = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); + break; + case NOTIFICATION_INTERNAL_PROCESS: + if (tile_set_changed_needs_update) { + if (tile_set.is_valid()) { + tile_set->set_edited(true); + } + _update_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_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_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_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); + + sources_advanced_menu_button = memnew(MenuButton); + sources_advanced_menu_button->set_flat(true); + sources_advanced_menu_button->get_popup()->add_item(TTR("Open Atlas Merging Tool")); + sources_advanced_menu_button->get_popup()->add_item(TTR("Manage Tile Proxies")); + sources_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_sources_advanced_menu_id_pressed)); + sources_bottom_actions->add_child(sources_advanced_menu_button); + + atlas_merging_dialog = memnew(AtlasMergingDialog); + add_child(atlas_merging_dialog); + + tile_proxies_manager_dialog = memnew(TileProxiesManagerDialog); + add_child(tile_proxies_manager_dialog); + + // 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_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_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..970e3fabb6 --- /dev/null +++ b/editor/plugins/tiles/tile_set_editor.h @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* 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 "atlas_merging_dialog.h" +#include "scene/gui/box_container.h" +#include "scene/resources/tile_set.h" +#include "tile_proxies_manager_dialog.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_sources_list(int force_selected_id = -1); + + // Sources management. + Button *sources_delete_button; + MenuButton *sources_add_button; + MenuButton *sources_advanced_menu_button; + ItemList *sources_list; + Ref<Texture2D> missing_texture_texture; + void _source_selected(int p_source_index); + void _source_delete_pressed(); + void _source_add_id_pressed(int p_id_pressed); + void _sources_advanced_menu_id_pressed(int p_id_pressed); + + AtlasMergingDialog *atlas_merging_dialog; + TileProxiesManagerDialog *tile_proxies_manager_dialog; + + 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..f74b3bf9c2 --- /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(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("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(SNAME("PackedScene"), SNAME("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(SNAME("Add"), SNAME("EditorIcons"))); + scene_tile_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("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..79b869b511 --- /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(SNAME("TileSet"), SNAME("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_sources_lists_current(int p_current) { + atlas_sources_lists_current = p_current; +} + +void TilesEditor::synchronize_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(SNAME("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..f976d68938 --- /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_sources_lists_current(int p_current); + void synchronize_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..5804f54649 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -164,7 +164,7 @@ void VersionControlEditorPlugin::_refresh_stage_area() { _refresh_file_diff(); } } - commit_status->set_text("New changes detected"); + commit_status->set_text(TTR("New changes detected")); } } else { WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu."); @@ -180,15 +180,15 @@ 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)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color("success_color", "Editor")); + file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); staged_files_count++; } else { EditorVCSInterface::get_singleton()->unstage_file(file_entry->get_metadata(0)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor")); + file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); } file_entry = file_entry->get_next(); @@ -207,10 +207,10 @@ 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")); + file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); file_entry->set_checked(0, true); staged_files_count++; @@ -235,16 +235,16 @@ void VersionControlEditorPlugin::_display_file_diff(String p_file_path) { diff_file_name->set_text(p_file_path); diff->clear(); - diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font("source", "EditorFonts")); + diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("source"), SNAME("EditorFonts"))); for (int i = 0; i < diff_content.size(); i++) { Dictionary line_result = diff_content[i]; if (line_result["status"] == "+") { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color("success_color", "Editor")); + diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); } else if (line_result["status"] == "-") { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor")); + diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); } else { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color("font_color", "Label")); + diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label"))); } diff->add_text((String)line_result["content"]); @@ -270,9 +270,9 @@ void VersionControlEditorPlugin::_clear_file_diff() { void VersionControlEditorPlugin::_update_stage_status() { String status; if (staged_files_count == 1) { - status = "Stage contains 1 file"; + status = TTR("Stage contains 1 file"); } else { - status = "Stage contains " + String::num_int64(staged_files_count) + " files"; + status = vformat(TTR("Stage contains %d files"), staged_files_count); } commit_status->set_text(status); } @@ -280,9 +280,9 @@ void VersionControlEditorPlugin::_update_stage_status() { void VersionControlEditorPlugin::_update_commit_status() { String status; if (staged_files_count == 1) { - status = "Committed 1 file"; + status = TTR("Committed 1 file"); } else { - status = "Committed " + String::num_int64(staged_files_count) + " files "; + status = vformat(TTR("Committed %d files"), staged_files_count); } commit_status->set_text(status); staged_files_count = 0; @@ -407,7 +407,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { refresh_button = memnew(Button); refresh_button->set_tooltip(TTR("Detect new changes")); refresh_button->set_text(TTR("Refresh")); - refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Reload", "EditorIcons")); + refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); refresh_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); stage_tools->add_child(refresh_button); @@ -432,11 +432,11 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { change_type_to_strings[CHANGE_TYPE_DELETED] = TTR("Deleted"); change_type_to_strings[CHANGE_TYPE_TYPECHANGE] = TTR("Typechange"); - change_type_to_color[CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_color("success_color", "Editor"); - change_type_to_color[CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color("warning_color", "Editor"); - change_type_to_color[CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color("disabled_font_color", "Editor"); - change_type_to_color[CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor"); - change_type_to_color[CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_color("font_color", "Editor"); + change_type_to_color[CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); + change_type_to_color[CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); + change_type_to_color[CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor")); + change_type_to_color[CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); + change_type_to_color[CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Editor")); stage_buttons = memnew(HSplitContainer); stage_buttons->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN_COLLAPSED); @@ -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); @@ -500,7 +501,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { diff_refresh_button = memnew(Button); diff_refresh_button->set_tooltip(TTR("Detect changes in file diff")); - diff_refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Reload", "EditorIcons")); + diff_refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); diff_refresh_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_refresh_file_diff)); diff_hbc->add_child(diff_refresh_button); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index a63e641c2b..f49279aa33 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_curve_xyz", &VisualShaderGraphPlugin::update_curve_xyz); ClassDB::bind_method("update_constant", &VisualShaderGraphPlugin::update_constant); } @@ -162,7 +164,7 @@ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p } void VisualShaderGraphPlugin::update_node_deferred(VisualShader::Type p_type, int p_node_id) { - call_deferred("update_node", p_type, p_node_id); + call_deferred(SNAME("update_node"), p_type, p_node_id); } void VisualShaderGraphPlugin::update_node(VisualShader::Type p_type, int p_node_id) { @@ -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_curve_xyz(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 (((VisualShaderNodeCurveXYZTexture *)links[p_node_id].visual_node)->get_texture().is_valid()) { + links[p_node_id].curve_editors[0]->set_curve(((VisualShaderNodeCurveXYZTexture *)links[p_node_id].visual_node)->get_texture()->get_curve_x()); + links[p_node_id].curve_editors[1]->set_curve(((VisualShaderNodeCurveXYZTexture *)links[p_node_id].visual_node)->get_texture()->get_curve_y()); + links[p_node_id].curve_editors[2]->set_curve(((VisualShaderNodeCurveXYZTexture *)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(SNAME("axis_x_color"), SNAME("Editor")); // red + vector_expanded_color[1] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); // green + vector_expanded_color[2] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("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,10 +373,17 @@ 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 = ""; + VisualShaderNodeCustom *custom_node = Object::cast_to<VisualShaderNodeCustom>(vsnode.ptr()); + if (custom_node) { + custom_node->_set_initialized(true); + } + GraphNode *node = memnew(GraphNode); register_link(p_type, p_id, vsnode.ptr(), node); @@ -384,6 +415,25 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { port_offset += 2; } + 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_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); @@ -393,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) == "") { @@ -415,7 +465,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { vsnode->remove_meta("shader_type"); if (custom_editor) { if (vsnode->is_show_prop_names()) { - custom_editor->call_deferred("_show_prop_names", true); + custom_editor->call_deferred(SNAME("_show_prop_names"), true); } break; } @@ -433,6 +483,18 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { custom_editor = hbox; } + Ref<VisualShaderNodeCurveXYZTexture> curve_xyz = vsnode; + if (curve_xyz.is_valid()) { + if (curve_xyz->get_texture().is_valid() && !curve_xyz->get_texture()->is_connected("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve_xyz))) { + curve_xyz->get_texture()->connect("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve_xyz), 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); @@ -456,36 +518,76 @@ 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() || curve_xyz.is_valid(); + + if (is_curve) { VisualShaderEditor::get_singleton()->graph->add_child(node); VisualShaderEditor::get_singleton()->_update_created_node(node); + TextureButton *preview = memnew(TextureButton); + preview->set_toggle_mode(true); + preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); + preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); + preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + + register_output_port(p_id, 0, preview); + + 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, 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()); } + } - 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 (curve_xyz.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 (curve_xyz->get_texture().is_valid()) { + curve_editor_x->set_curve(curve_xyz->get_texture()->get_curve_x()); + } - register_output_port(p_id, 0, preview); + 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 (curve_xyz->get_texture().is_valid()) { + curve_editor_y->set_curve(curve_xyz->get_texture()->get_curve_y()); + } - preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, 0), CONNECT_DEFERRED); - custom_editor->add_child(preview); + 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 (curve_xyz->get_texture().is_valid()) { + curve_editor_z->set_curve(curve_xyz->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); + VisualShaderEditor::get_singleton()->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); } + if (vsnode->is_use_prop_slots()) { return; } @@ -532,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; @@ -546,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); @@ -602,11 +729,11 @@ 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); - remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Remove", "EditorIcons")); + remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); remove_btn->set_tooltip(TTR("Remove") + " " + name_left); remove_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_remove_input_port), varray(p_id, i), CONNECT_DEFERRED); hb->add_child(remove_btn); @@ -619,7 +746,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (vsnode->get_input_port_default_hint(i) != "" && !port_left_used) { Label *hint_label = memnew(Label); hint_label->set_text("[" + vsnode->get_input_port_default_hint(i) + "]"); - hint_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color("font_readonly_color", "TextEdit")); + hint_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("font_readonly_color"), SNAME("TextEdit"))); hint_label->add_theme_style_override("normal", label_style); hb->add_child(hint_label); } @@ -633,7 +760,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (valid_right) { if (is_group) { Button *remove_btn = memnew(Button); - remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Remove", "EditorIcons")); + remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); remove_btn->set_tooltip(TTR("Remove") + " " + name_left); remove_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_remove_output_port), varray(p_id, i), CONNECT_DEFERRED); hb->add_child(remove_btn); @@ -643,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); @@ -665,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(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); + expand->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("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(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); + preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("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) { @@ -687,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) { @@ -701,7 +873,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { String error = vsnode->get_warning(visual_shader->get_mode(), p_type); if (error != String()) { Label *error_label = memnew(Label); - error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color("error_color", "Editor")); + error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_label->set_text(error); node->add_child(error_label); } @@ -709,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"); @@ -727,11 +900,15 @@ 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")); - expression_box->add_theme_font_size_override("font_size", VisualShaderEditor::get_singleton()->get_theme_font_size("expression_size", "EditorFonts")); + expression_box->add_theme_font_override("font", VisualShaderEditor::get_singleton()->get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); + expression_box->add_theme_font_size_override("font_size", VisualShaderEditor::get_singleton()->get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); expression_box->add_theme_color_override("font_color", text_color); expression_syntax_highlighter->set_number_color(number_color); expression_syntax_highlighter->set_symbol_color(symbol_color); @@ -740,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); @@ -749,9 +930,12 @@ 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); + VisualShaderEditor::get_singleton()->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); } } } @@ -767,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(); } @@ -776,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)); @@ -898,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: @@ -932,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; @@ -1020,8 +1211,8 @@ void VisualShaderEditor::_update_options_menu() { bool is_first_item = true; - Color unsupported_color = get_theme_color("error_color", "Editor"); - Color supported_color = get_theme_color("warning_color", "Editor"); + Color unsupported_color = get_theme_color(SNAME("error_color"), SNAME("Editor")); + Color supported_color = get_theme_color(SNAME("warning_color"), SNAME("Editor")); static bool low_driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name") == "GLES2"; @@ -1094,22 +1285,22 @@ void VisualShaderEditor::_update_options_menu() { } switch (options[i].return_type) { case VisualShaderNode::PORT_TYPE_SCALAR: - item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("float", "EditorIcons")); + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons"))); break; case VisualShaderNode::PORT_TYPE_SCALAR_INT: - item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("int", "EditorIcons")); + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons"))); break; case VisualShaderNode::PORT_TYPE_VECTOR: - item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Vector3", "EditorIcons")); + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons"))); break; case VisualShaderNode::PORT_TYPE_BOOLEAN: - item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("bool", "EditorIcons")); + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("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(SNAME("Transform3D"), SNAME("EditorIcons"))); break; case VisualShaderNode::PORT_TYPE_SAMPLER: - item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons")); + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons"))); break; default: break; @@ -1119,16 +1310,31 @@ void VisualShaderEditor::_update_options_menu() { } void VisualShaderEditor::_set_mode(int p_which) { - if (p_which == VisualShader::MODE_PARTICLES) { - edit_type_standart->set_visible(false); + if (p_which == VisualShader::MODE_SKY) { + 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_standard->set_visible(false); edit_type_particles->set_visible(true); + edit_type_sky->set_visible(false); edit_type = edit_type_particles; - particles_mode = true; + 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 = edit_type_standart; - particles_mode = false; + edit_type_standard->set_visible(true); + edit_type_sky->set_visible(false); + 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()); } @@ -1143,31 +1349,20 @@ void VisualShaderEditor::_draw_color_over_button(Object *obj, Color p_color) { return; } - Ref<StyleBox> normal = get_theme_stylebox("normal", "Button"); + Ref<StyleBox> normal = get_theme_stylebox(SNAME("normal"), SNAME("Button")); button->draw_rect(Rect2(normal->get_offset(), button->get_size() - normal->get_minimum_size()), 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(SNAME("frame"), SNAME("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) { @@ -1267,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]); @@ -1289,8 +1485,10 @@ void VisualShaderEditor::_update_graph() { VisualShader::Type VisualShaderEditor::get_current_shader_type() const { VisualShader::Type type; - if (particles_mode) { - type = VisualShader::Type(edit_type->get_selected() + 3); + if (mode & MODE_FLAGS_PARTICLES) { + 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() + 8); } else { type = VisualShader::Type(edit_type->get_selected()); } @@ -1413,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); @@ -1561,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)); } @@ -1624,6 +1908,92 @@ void VisualShaderEditor::_preview_select_port(int p_node, int p_port) { undo_redo->commit_action(); } +void VisualShaderEditor::_comment_title_popup_show(const Point2 &p_position, int p_node_id) { + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, p_node_id); + if (node.is_null()) { + return; + } + comment_title_change_edit->set_text(node->get_title()); + comment_title_change_popup->set_meta("id", p_node_id); + comment_title_change_popup->popup(); + comment_title_change_popup->set_position(p_position); +} + +void VisualShaderEditor::_comment_title_text_changed(const String &p_new_text) { + comment_title_change_edit->set_size(Size2(-1, -1)); + comment_title_change_popup->set_size(Size2(-1, -1)); +} + +void VisualShaderEditor::_comment_title_text_submitted(const String &p_new_text) { + comment_title_change_popup->hide(); +} + +void VisualShaderEditor::_comment_title_popup_focus_out() { + comment_title_change_popup->hide(); +} + +void VisualShaderEditor::_comment_title_popup_hide() { + ERR_FAIL_COND(!comment_title_change_popup->has_meta("id")); + int node_id = (int)comment_title_change_popup->get_meta("id"); + + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, node_id); + + ERR_FAIL_COND(node.is_null()); + + if (node->get_title() == comment_title_change_edit->get_text()) { + return; // nothing changed - ignored + } + undo_redo->create_action(TTR("Set Comment Node Title")); + undo_redo->add_do_method(node.ptr(), "set_title", comment_title_change_edit->get_text()); + undo_redo->add_undo_method(node.ptr(), "set_title", node->get_title()); + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, node_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", (int)type, node_id); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_comment_desc_popup_show(const Point2 &p_position, int p_node_id) { + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, p_node_id); + if (node.is_null()) { + return; + } + comment_desc_change_edit->set_text(node->get_description()); + comment_desc_change_popup->set_meta("id", p_node_id); + comment_desc_change_popup->popup(); + comment_desc_change_popup->set_position(p_position); +} + +void VisualShaderEditor::_comment_desc_text_changed() { + comment_desc_change_edit->set_size(Size2(-1, -1)); + comment_desc_change_popup->set_size(Size2(-1, -1)); +} + +void VisualShaderEditor::_comment_desc_confirm() { + comment_desc_change_popup->hide(); +} + +void VisualShaderEditor::_comment_desc_popup_hide() { + ERR_FAIL_COND(!comment_desc_change_popup->has_meta("id")); + int node_id = (int)comment_desc_change_popup->get_meta("id"); + + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, node_id); + + ERR_FAIL_COND(node.is_null()); + + if (node->get_description() == comment_desc_change_edit->get_text()) { + return; // nothing changed - ignored + } + undo_redo->create_action(TTR("Set Comment Node Description")); + undo_redo->add_do_method(node.ptr(), "set_description", comment_desc_change_edit->get_text()); + undo_redo->add_undo_method(node.ptr(), "set_description", node->get_title()); + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, node_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", (int)type, node_id); + undo_redo->commit_action(); +} + void VisualShaderEditor::_uniform_line_edit_changed(const String &p_text, int p_node_id) { VisualShader::Type type = get_current_shader_type(); @@ -1672,8 +2042,15 @@ void VisualShaderEditor::_port_edited() { ERR_FAIL_COND(!vsn.is_valid()); undo_redo->create_action(TTR("Set Input Default Port")); - undo_redo->add_do_method(vsn.ptr(), "set_input_port_default_value", editing_port, value); - undo_redo->add_undo_method(vsn.ptr(), "set_input_port_default_value", editing_port, vsn->get_input_port_default_value(editing_port)); + + Ref<VisualShaderNodeCustom> custom = Object::cast_to<VisualShaderNodeCustom>(vsn.ptr()); + if (custom.is_valid()) { + undo_redo->add_do_method(custom.ptr(), "_set_input_port_default_value", editing_port, value); + undo_redo->add_undo_method(custom.ptr(), "_set_input_port_default_value", editing_port, vsn->get_input_port_default_value(editing_port)); + } else { + undo_redo->add_do_method(vsn.ptr(), "set_input_port_default_value", editing_port, value); + undo_redo->add_undo_method(vsn.ptr(), "set_input_port_default_value", editing_port, vsn->get_input_port_default_value(editing_port)); + } undo_redo->add_do_method(graph_plugin.ptr(), "set_input_port_default_value", type, editing_node, editing_port, value); undo_redo->add_undo_method(graph_plugin.ptr(), "set_input_port_default_value", type, editing_node, editing_port, vsn->get_input_port_default_value(editing_port)); undo_redo->commit_action(); @@ -1696,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 { @@ -1828,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); @@ -1925,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); @@ -1954,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); } @@ -1974,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); @@ -2035,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)) { @@ -2090,18 +2470,45 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) { VisualShaderNodeCurveTexture *curve = Object::cast_to<VisualShaderNodeCurveTexture>(vsnode.ptr()); if (curve) { - graph_plugin->call_deferred("update_curve", id_to_use); + graph_plugin->call_deferred(SNAME("update_curve"), id_to_use); } - undo_redo->commit_action(); - return vsnode.ptr(); + VisualShaderNodeCurveXYZTexture *curve_xyz = Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()); + if (curve_xyz) { + graph_plugin->call_deferred(SNAME("update_curve_xyz"), 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 || curve_xyz) { + 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) { VisualShader::Type type = get_current_shader_type(); drag_buffer.push_back({ type, p_node, p_from, p_to }); if (!drag_dirty) { - call_deferred("_nodes_dragged"); + call_deferred(SNAME("_nodes_dragged")); } drag_dirty = true; } @@ -2311,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 @@ -2320,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) { @@ -2504,9 +2911,10 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; VisualShader::Type type = get_current_shader_type(); - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { selected_constants.clear(); selected_uniforms.clear(); + selected_comment = -1; List<int> to_change; for (int i = 0; i < graph->get_child_count(); i++) { @@ -2517,17 +2925,27 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { to_change.push_back(id); Ref<VisualShaderNode> node = visual_shader->get_node(type, id); - VisualShaderNodeConstant *cnode = Object::cast_to<VisualShaderNodeConstant>(node.ptr()); - if (cnode != nullptr) { + + VisualShaderNodeComment *comment_node = Object::cast_to<VisualShaderNodeComment>(node.ptr()); + if (comment_node != nullptr) { + selected_comment = id; + } + VisualShaderNodeConstant *constant_node = Object::cast_to<VisualShaderNodeConstant>(node.ptr()); + if (constant_node != nullptr) { selected_constants.insert(id); } - VisualShaderNodeUniform *unode = Object::cast_to<VisualShaderNodeUniform>(node.ptr()); - if (unode != nullptr) { + VisualShaderNodeUniform *uniform_node = Object::cast_to<VisualShaderNodeUniform>(node.ptr()); + if (uniform_node != nullptr && uniform_node->is_convertible_to_constant()) { selected_uniforms.insert(id); } } } } + + if (to_change.size() > 1) { + selected_comment = -1; + } + if (to_change.is_empty() && copy_nodes_buffer.is_empty()) { _show_members_dialog(true); } else { @@ -2548,16 +2966,34 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { if (temp != -1) { popup_menu->remove_item(temp); } + temp = popup_menu->get_item_index(NodeMenuOptions::SEPARATOR3); + if (temp != -1) { + popup_menu->remove_item(temp); + } + temp = popup_menu->get_item_index(NodeMenuOptions::SET_COMMENT_TITLE); + if (temp != -1) { + popup_menu->remove_item(temp); + } + temp = popup_menu->get_item_index(NodeMenuOptions::SET_COMMENT_DESCRIPTION); + if (temp != -1) { + popup_menu->remove_item(temp); + } - if (selected_constants.size() > 0 || selected_uniforms.size() > 0) { + if (selected_comment != -1) { popup_menu->add_separator("", NodeMenuOptions::SEPARATOR2); + popup_menu->add_item(TTR("Set Comment Title"), NodeMenuOptions::SET_COMMENT_TITLE); + popup_menu->add_item(TTR("Set Comment Description"), NodeMenuOptions::SET_COMMENT_DESCRIPTION); + } + + if (selected_constants.size() > 0 || selected_uniforms.size() > 0) { + popup_menu->add_separator("", NodeMenuOptions::SEPARATOR3); if (selected_constants.size() > 0) { popup_menu->add_item(TTR("Convert Constant(s) to Uniform(s)"), NodeMenuOptions::CONVERT_CONSTANTS_TO_UNIFORMS); } if (selected_uniforms.size() > 0) { - popup_menu->add_item(TTR("Convert Uniforms(s) to Constant(s)"), NodeMenuOptions::CONVERT_UNIFORMS_TO_CONSTANTS); + popup_menu->add_item(TTR("Convert Uniform(s) to Constant(s)"), NodeMenuOptions::CONVERT_UNIFORMS_TO_CONSTANTS); } } @@ -2596,7 +3032,7 @@ void VisualShaderEditor::_show_members_dialog(bool at_mouse_pos) { members_dialog->set_position(members_dialog->get_position() - Point2(difference, 0)); } - node_filter->call_deferred("grab_focus"); // still not visible + node_filter->call_deferred(SNAME("grab_focus")); // still not visible node_filter->select_all(); } @@ -2617,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(); @@ -2639,19 +3075,17 @@ 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")); + highend_label->set_modulate(get_theme_color(SNAME("vulkan_color"), SNAME("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(SNAME("Search"), SNAME("EditorIcons"))); - node_filter->set_right_icon(Control::get_theme_icon("Search", "EditorIcons")); - - preview_shader->set_icon(Control::get_theme_icon("Shader", "EditorIcons")); + preview_shader->set_icon(Control::get_theme_icon(SNAME("Shader"), SNAME("EditorIcons"))); { 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"); @@ -2661,11 +3095,15 @@ 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")); - preview_text->add_theme_font_size_override("font_size", get_theme_font_size("expression_size", "EditorFonts")); + preview_text->add_theme_font_override("font", get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); + preview_text->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); preview_text->add_theme_color_override("font_color", text_color); syntax_highlighter->set_number_color(number_color); syntax_highlighter->set_symbol_color(symbol_color); @@ -2675,12 +3113,17 @@ 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(SNAME("panel"), SNAME("Panel"))); + error_label->add_theme_font_override("font", get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + error_label->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); } - tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Tools", "EditorIcons")); + tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { _update_graph(); @@ -2896,7 +3339,39 @@ void VisualShaderEditor::_paste_nodes(bool p_use_custom_position, const Vector2 } void VisualShaderEditor::_mode_selected(int p_id) { - visual_shader->set_shader_type(particles_mode ? VisualShader::Type(p_id + 3) : VisualShader::Type(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 = 8; + } + + visual_shader->set_shader_type(VisualShader::Type(p_id + offset)); + _update_options_menu(); + _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(); } @@ -3053,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(); @@ -3074,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(); @@ -3111,6 +3586,12 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { case NodeMenuOptions::CONVERT_UNIFORMS_TO_CONSTANTS: _convert_constants_to_uniforms(true); break; + case NodeMenuOptions::SET_COMMENT_TITLE: + _comment_title_popup_show(get_global_mouse_position(), selected_comment); + break; + case NodeMenuOptions::SET_COMMENT_DESCRIPTION: + _comment_desc_popup_show(get_global_mouse_position(), selected_comment); + break; default: break; } @@ -3170,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 == "CurveXYZTexture") { + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); + saved_node_pos_dirty = true; + _add_node(curve_xyz_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(); } } } @@ -3263,20 +3757,21 @@ void VisualShaderEditor::_update_preview() { ShaderLanguage sl; - 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())), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type); + 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; } } @@ -3306,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); } @@ -3382,26 +3878,41 @@ 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)); - edit_type = edit_type_standart; + edit_type_sky = memnew(OptionButton); + edit_type_sky->add_item(TTR("Sky")); + edit_type_sky->select(0); + edit_type_sky->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected)); + + 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_standart); - graph->get_zoom_hbox()->move_child(edit_type_standart, 0); + graph->get_zoom_hbox()->add_child(edit_type_sky); + graph->get_zoom_hbox()->move_child(edit_type_sky, 0); add_node = memnew(Button); add_node->set_flat(true); @@ -3430,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_mode(Label::AUTOWRAP_WORD_SMART); /////////////////////////////////////// // POPUP MENU @@ -3528,12 +4043,41 @@ VisualShaderEditor::VisualShaderEditor() { add_child(members_dialog); alert = memnew(AcceptDialog); - alert->get_label()->set_autowrap(true); + alert->get_label()->set_autowrap_mode(Label::AUTOWRAP_WORD); alert->get_label()->set_align(Label::ALIGN_CENTER); alert->get_label()->set_valign(Label::VALIGN_CENTER); alert->get_label()->set_custom_minimum_size(Size2(400, 60) * EDSCALE); add_child(alert); + comment_title_change_popup = memnew(PopupPanel); + 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_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)); + comment_title_change_popup->connect("focus_exited", callable_mp(this, &VisualShaderEditor::_comment_title_popup_focus_out)); + comment_title_change_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_comment_title_popup_hide)); + add_child(comment_title_change_popup); + + comment_desc_change_popup = memnew(PopupPanel); + VBoxContainer *comment_desc_vbox = memnew(VBoxContainer); + comment_desc_change_popup->add_child(comment_desc_vbox); + comment_desc_change_edit = memnew(TextEdit); + comment_desc_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_comment_desc_text_changed)); + comment_desc_vbox->add_child(comment_desc_change_edit); + comment_desc_change_edit->set_custom_minimum_size(Size2(300 * EDSCALE, 150 * EDSCALE)); + comment_desc_change_edit->set_size(Size2(-1, -1)); + comment_desc_change_popup->set_size(Size2(-1, -1)); + comment_desc_change_popup->connect("focus_exited", callable_mp(this, &VisualShaderEditor::_comment_desc_confirm)); + comment_desc_change_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_comment_desc_popup_hide)); + Button *comment_desc_confirm_button = memnew(Button); + comment_desc_confirm_button->set_text(TTR("OK")); + comment_desc_vbox->add_child(comment_desc_confirm_button); + comment_desc_confirm_button->connect("pressed", callable_mp(this, &VisualShaderEditor::_comment_desc_confirm)); + add_child(comment_desc_change_popup); + /////////////////////////////////////// // SHADER NODES TREE OPTIONS /////////////////////////////////////// @@ -3588,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)); @@ -3609,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."))); @@ -3618,13 +4180,15 @@ VisualShaderEditor::VisualShaderEditor() { const String input_param_for_vertex_and_fragment_shader_modes = TTR("'%s' input parameter for vertex and fragment shader modes."); const String input_param_for_fragment_and_light_shader_modes = TTR("'%s' input parameter for fragment and light shader modes."); const String input_param_for_fragment_shader_mode = TTR("'%s' input parameter for fragment shader mode."); + 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)); @@ -3701,81 +4265,53 @@ 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", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "at_cubemap_pass"), "at_cubemap_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("AtHalfResPass", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "at_half_res_pass"), "at_half_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("AtQuarterResPass", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "at_quarter_res_pass"), "at_quarter_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("EyeDir", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "eyedir"), "eyedir", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("HalfResColor", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "half_res_color"), "half_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("HalfResAlpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "half_res_alpha"), "half_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Color", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light0_color"), "light0_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Direction", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light0_direction"), "light0_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Enabled", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light0_enabled"), "light0_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Energy", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light0_energy"), "light0_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Color", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light1_color"), "light1_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Direction", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light1_direction"), "light1_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Enabled", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light1_enabled"), "light1_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Energy", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light1_energy"), "light1_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Color", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light2_color"), "light2_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Direction", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light2_direction"), "light2_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Enabled", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light2_enabled"), "light2_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Energy", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light2_energy"), "light2_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Color", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light3_color"), "light3_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Direction", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light3_direction"), "light3_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Enabled", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light3_enabled"), "light3_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Energy", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "light3_energy"), "light3_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Position", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "position"), "position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("QuarterResColor", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "quarter_res_color"), "quarter_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("QuarterResAlpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "quarter_res_alpha"), "quarter_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Radiance", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "radiance"), "radiance", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("SkyCoords", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "sky_coords"), "sky_coords", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); - add_options.push_back(AddOption("Time", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SKY)); + 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)); + add_options.push_back(AddOption("AtHalfResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_half_res_pass"), "at_half_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("AtQuarterResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_quarter_res_pass"), "at_quarter_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("EyeDir", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "eyedir"), "eyedir", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("HalfResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_color"), "half_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("HalfResAlpha", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_alpha"), "half_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_color"), "light0_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_direction"), "light0_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_enabled"), "light0_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_energy"), "light0_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_color"), "light1_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_direction"), "light1_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_enabled"), "light1_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_energy"), "light1_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_color"), "light2_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_direction"), "light2_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_enabled"), "light2_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_energy"), "light2_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_color"), "light3_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_direction"), "light3_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_enabled"), "light3_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_energy"), "light3_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Position", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "position"), "position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("QuarterResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_color"), "quarter_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("QuarterResAlpha", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_alpha"), "quarter_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Radiance", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "radiance"), "radiance", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("ScreenUV", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + 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 @@ -3864,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)); + curve_xyz_node_option_idx = add_options.size(); + add_options.push_back(AddOption("CurveXYZTexture", "Textures", "Functions", "VisualShaderNodeCurveXYZTexture", TTR("Perform the three components 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)); @@ -3890,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)); @@ -3971,6 +4514,7 @@ VisualShaderEditor::VisualShaderEditor() { // SPECIAL + add_options.push_back(AddOption("Comment", "Special", "", "VisualShaderNodeComment", TTR("A rectangular area with a description string for better graph organization."))); add_options.push_back(AddOption("Expression", "Special", "", "VisualShaderNodeExpression", TTR("Custom Godot Shader Language expression, with custom amount of input and output ports. This is a direct injection of code into the vertex/fragment/light function, do not use it to write the function declarations inside."))); add_options.push_back(AddOption("Fresnel", "Special", "", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("GlobalExpression", "Special", "", "VisualShaderNodeGlobalExpression", TTR("Custom Godot Shader Language expression, which is placed on top of the resulted shader. You can place various function definitions inside and call it later in the Expressions. You can also declare varyings, uniforms and constants."))); @@ -3991,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); @@ -4068,18 +4605,18 @@ public: } void _item_selected(int p_item) { - VisualShaderEditor::get_singleton()->call_deferred("_input_select_item", input, get_item_text(p_item)); + VisualShaderEditor::get_singleton()->call_deferred(SNAME("_input_select_item"), input, get_item_text(p_item)); } void setup(const Ref<VisualShaderNodeInput> &p_input) { input = p_input; Ref<Texture2D> type_icon[6] = { - EditorNode::get_singleton()->get_gui_base()->get_theme_icon("float", "EditorIcons"), - 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("ImageTexture", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons")), }; add_item("[None]"); @@ -4112,20 +4649,20 @@ public: } void _item_selected(int p_item) { - VisualShaderEditor::get_singleton()->call_deferred("_uniform_select_item", uniform_ref, get_item_text(p_item)); + VisualShaderEditor::get_singleton()->call_deferred(SNAME("_uniform_select_item"), uniform_ref, get_item_text(p_item)); } void setup(const Ref<VisualShaderNodeUniformRef> &p_uniform_ref) { uniform_ref = p_uniform_ref; Ref<Texture2D> type_icon[7] = { - EditorNode::get_singleton()->get_gui_base()->get_theme_icon("float", "EditorIcons"), - 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("Color", "EditorIcons"), - EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons")), }; add_item("[None]"); @@ -4314,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)); @@ -4333,7 +4870,7 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par void EditorPropertyShaderMode::_option_selected(int p_which) { //will not use this, instead will do all the logic setting manually - //emit_signal("property_changed", get_edited_property(), p_which); + //emit_signal(SNAME("property_changed"), get_edited_property(), p_which); Ref<VisualShader> visual_shader(Object::cast_to<VisualShader>(get_edited_object())); @@ -4431,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(","); @@ -4459,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 @@ -4551,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 182bed6ba6..f53726edb9 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 { @@ -67,7 +67,7 @@ private: VisualShader::Type type = VisualShader::Type::TYPE_MAX; VisualShaderNode *visual_node = nullptr; GraphNode *graph_node = nullptr; - bool preview_visible = 0; + bool preview_visible = false; int preview_pos = 0; Map<int, InputPort> input_ports; Map<int, Port> output_ports; @@ -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_curve_xyz(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,11 +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; - - PanelContainer *error_panel; - Label *error_label; + OptionButton *edit_type_sky; + CheckBox *custom_mode_box; + bool custom_mode_enabled = false; bool pending_update_preview; bool shader_error; @@ -151,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; @@ -161,9 +166,22 @@ class VisualShaderEditor : public VBoxContainer { PopupMenu *popup_menu; MenuButton *tools; + PopupPanel *comment_title_change_popup = nullptr; + LineEdit *comment_title_change_edit = nullptr; + + PopupPanel *comment_desc_change_popup = nullptr; + TextEdit *comment_desc_change_edit = nullptr; + bool preview_first = true; bool preview_showed = false; - bool particles_mode; + + enum ShaderModeFlags { + MODE_FLAGS_SPATIAL_CANVASITEM = 1, + MODE_FLAGS_SKY = 2, + MODE_FLAGS_PARTICLES = 4 + }; + + int mode = MODE_FLAGS_SPATIAL_CANVASITEM; enum TypeFlags { TYPE_FLAGS_VERTEX = 1, @@ -174,7 +192,13 @@ 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 { + TYPE_FLAGS_SKY = 1, }; enum ToolsMenuOptions { @@ -192,6 +216,9 @@ class VisualShaderEditor : public VBoxContainer { SEPARATOR2, // ignore CONVERT_CONSTANTS_TO_UNIFORMS, CONVERT_UNIFORMS_TO_CONSTANTS, + SEPARATOR3, // ignore + SET_COMMENT_TITLE, + SET_COMMENT_DESCRIPTION, }; Tree *members; @@ -263,21 +290,15 @@ class VisualShaderEditor : public VBoxContainer { int texture3d_node_option_idx; int custom_node_option_idx; int curve_node_option_idx; + int curve_xyz_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); @@ -325,6 +346,7 @@ class VisualShaderEditor : public VBoxContainer { Set<int> selected_constants; Set<int> selected_uniforms; + int selected_comment = -1; void _convert_constants_to_uniforms(bool p_vice_versa); void _replace_node(VisualShader::Type p_type_id, int p_node_id, const StringName &p_from, const StringName &p_to); @@ -334,6 +356,17 @@ class VisualShaderEditor : public VBoxContainer { void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); + void _comment_title_popup_show(const Point2 &p_position, int p_node_id); + 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_submitted(const String &p_new_text); + + void _comment_desc_popup_show(const Point2 &p_position, int p_node_id); + void _comment_desc_popup_hide(); + void _comment_desc_confirm(); + void _comment_desc_text_changed(); + void _uniform_line_edit_changed(const String &p_text, int p_node_id); void _uniform_line_edit_focus_out(Object *line_edit, int p_node_id); @@ -358,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); @@ -375,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); @@ -472,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..5bbc0c9dd5 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)); @@ -84,13 +81,13 @@ void GIProbeEditorPlugin::_notification(int p_what) { Color color; if (size_mb <= 16.0 + CMP_EPSILON) { // Fast. - color = bake_info->get_theme_color("success_color", "Editor"); + color = bake_info->get_theme_color(SNAME("success_color"), SNAME("Editor")); } else if (size_mb <= 64.0 + CMP_EPSILON) { // Medium. - color = bake_info->get_theme_color("warning_color", "Editor"); + color = bake_info->get_theme_color(SNAME("warning_color"), SNAME("Editor")); } else { // Slow. - color = bake_info->get_theme_color("error_color", "Editor"); + color = bake_info->get_theme_color(SNAME("error_color"), SNAME("Editor")); } bake_info->add_theme_color_override("font_color", color); @@ -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,47 +105,47 @@ 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); bake_hb->hide(); bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon("Bake", "EditorIcons")); + bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("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 |