diff --git a/editor/icons/SnapDisable.svg b/editor/icons/SnapDisable.svg new file mode 100644 index 000000000000..9bc0a6ad94f2 --- /dev/null +++ b/editor/icons/SnapDisable.svg @@ -0,0 +1 @@ + diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 8327fa576264..ef805e652477 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -41,9 +41,12 @@ #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "scene/gui/control.h" +#include "scene/gui/label.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" #include "scene/gui/separator.h" +#include "scene/gui/spin_box.h" #ifdef DEBUG_ENABLED #include "servers/navigation_server_3d.h" @@ -168,6 +171,17 @@ void GenericTilePolygonEditor::_base_control_draw() { base_control->draw_texture_rect_region(background_texture, Rect2(-background_region.size / 2 - background_offset, region_size), background_region, background_modulate, background_transpose); } + // Draw grid. + if (current_snap_option == SNAP_GRID) { + Vector2 spacing = tile_size / snap_subdivision->get_value(); + Vector2 offset = -tile_size / 2; + + for (int i = 1; i < snap_subdivision->get_value(); i++) { + base_control->draw_line(Vector2(spacing.x * i, 0) + offset, Vector2(spacing.x * i, tile_size.y) + offset, Color(1, 1, 1, 0.33)); + base_control->draw_line(Vector2(0, spacing.y * i) + offset, Vector2(tile_size.x, spacing.y * i) + offset, Color(1, 1, 1, 0.33)); + } + } + // Draw the polygons. for (const Vector &polygon : polygons) { Color color = polygon_color; @@ -195,9 +209,7 @@ void GenericTilePolygonEditor::_base_control_draw() { 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); - } + _snap_point(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)); @@ -443,8 +455,20 @@ void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_cur r_point = snapped_point; } -void GenericTilePolygonEditor::_snap_to_half_pixel(Point2 &r_point) { - r_point = (r_point * 2).round() / 2.0; +void GenericTilePolygonEditor::_snap_point(Point2 &r_point) { + switch (current_snap_option) { + case SNAP_NONE: + break; + + case SNAP_HALF_PIXEL: + r_point = r_point.snapped(Vector2(0.5, 0.5)); + break; + + case SNAP_GRID: { + const Vector2 tile_size = tile_set->get_tile_size(); + r_point = (r_point + tile_size / 2).snapped(tile_size / snap_subdivision->get_value()) - tile_size / 2; + } break; + } } void GenericTilePolygonEditor::_base_control_gui_input(Ref p_event) { @@ -475,9 +499,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref p_event) 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); - } + _snap_point(point); polygons[drag_polygon_index].write[drag_point_index] = point; } else if (drag_type == DRAG_TYPE_PAN) { panning += mm->get_position() - drag_last_pos; @@ -592,9 +614,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref p_event) 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); - } + _snap_point(point); in_creation_polygon.push_back(point); } drag_type = DRAG_TYPE_NONE; @@ -652,6 +672,19 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref p_event) } } +void GenericTilePolygonEditor::_set_snap_option(int p_index) { + current_snap_option = p_index; + button_pixel_snap->set_icon(button_pixel_snap->get_popup()->get_item_icon(p_index)); + snap_subdivision->set_visible(p_index == SNAP_GRID); + base_control->queue_redraw(); + _store_snap_options(); +} + +void GenericTilePolygonEditor::_store_snap_options() { + EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "tile_snap_option", current_snap_option); + EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "tile_snap_subdiv", snap_subdivision->get_value()); +} + void GenericTilePolygonEditor::set_use_undo_redo(bool p_use_undo_redo) { use_undo_redo = p_use_undo_redo; } @@ -766,8 +799,11 @@ void GenericTilePolygonEditor::_notification(int p_what) { button_edit->set_icon(get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); button_delete->set_icon(get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); - button_pixel_snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); button_advanced_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + button_pixel_snap->get_popup()->set_item_icon(0, get_theme_icon(SNAME("SnapDisable"), SNAME("EditorIcons"))); + button_pixel_snap->get_popup()->set_item_icon(1, get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + button_pixel_snap->get_popup()->set_item_icon(2, get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + button_pixel_snap->set_icon(button_pixel_snap->get_popup()->get_item_icon(current_snap_option)); PopupMenu *p = button_advanced_menu->get_popup(); p->set_item_icon(p->get_item_index(ROTATE_RIGHT), get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons"))); @@ -833,12 +869,20 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { 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); - button_pixel_snap->set_tooltip_text(TTR("Snap to half-pixel")); + button_pixel_snap = memnew(MenuButton); toolbar->add_child(button_pixel_snap); + button_pixel_snap->set_flat(true); + button_pixel_snap->set_tooltip_text(TTR("Toggle Grid Snap")); + button_pixel_snap->get_popup()->add_item(TTR("Disable Snap"), SNAP_NONE); + button_pixel_snap->get_popup()->add_item(TTR("Half-Pixel Snap"), SNAP_HALF_PIXEL); + button_pixel_snap->get_popup()->add_item(TTR("Grid Snap"), SNAP_GRID); + button_pixel_snap->get_popup()->connect("index_pressed", callable_mp(this, &GenericTilePolygonEditor::_set_snap_option)); + + snap_subdivision = memnew(SpinBox); + toolbar->add_child(snap_subdivision); + snap_subdivision->get_line_edit()->add_theme_constant_override("minimum_character_width", 2); + snap_subdivision->set_min(1); + snap_subdivision->set_max(99); Control *root = memnew(Control); root->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -859,6 +903,8 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { base_control->set_clip_contents(true); base_control->set_focus_mode(Control::FOCUS_CLICK); root->add_child(base_control); + snap_subdivision->connect("value_changed", callable_mp((CanvasItem *)base_control, &CanvasItem::queue_redraw).unbind(1)); + snap_subdivision->connect("value_changed", callable_mp(this, &GenericTilePolygonEditor::_store_snap_options).unbind(1)); editor_zoom_widget = memnew(EditorZoomWidget); editor_zoom_widget->set_position(Vector2(5, 5)); @@ -873,6 +919,9 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { button_center_view->set_flat(true); button_center_view->set_disabled(true); root->add_child(button_center_view); + + snap_subdivision->set_value_no_signal(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "tile_snap_subdiv", 4)); + _set_snap_option(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "tile_snap_option", SNAP_NONE)); } void TileDataDefaultEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 1ebf30aecd58..b3ecdb8cfbaf 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -36,10 +36,10 @@ #include "editor/editor_properties.h" #include "scene/2d/tile_map.h" #include "scene/gui/box_container.h" -#include "scene/gui/control.h" -#include "scene/gui/label.h" class MenuButton; +class SpinBox; +class Label; class EditorUndoRedoManager; class TileDataEditor : public VBoxContainer { @@ -120,9 +120,17 @@ class GenericTilePolygonEditor : public VBoxContainer { Button *button_create = nullptr; Button *button_edit = nullptr; Button *button_delete = nullptr; - Button *button_pixel_snap = nullptr; MenuButton *button_advanced_menu = nullptr; + enum Snap { + SNAP_NONE, + SNAP_HALF_PIXEL, + SNAP_GRID, + }; + int current_snap_option = SNAP_HALF_PIXEL; + MenuButton *button_pixel_snap = nullptr; + SpinBox *snap_subdivision = nullptr; + Vector in_creation_polygon; Panel *panel = nullptr; @@ -155,9 +163,11 @@ class GenericTilePolygonEditor : public VBoxContainer { void _advanced_menu_item_pressed(int p_item_pressed); void _center_view(); void _base_control_gui_input(Ref p_event); + void _set_snap_option(int p_index); + void _store_snap_options(); 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 _snap_point(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);