diff --git a/project.godot b/project.godot index eb6bc5336..36f391b24 100644 --- a/project.godot +++ b/project.godot @@ -123,6 +123,11 @@ _global_script_classes=[ { "class": "SymmetryGuide", "language": "GDScript", "path": "res://src/UI/Canvas/Rulers/SymmetryGuide.gd" +}, { +"base": "Reference", +"class": "Tiles", +"language": "GDScript", +"path": "res://src/Classes/Tiles.gd" } ] _global_script_class_icons={ "AnimationTag": "", @@ -147,7 +152,8 @@ _global_script_class_icons={ "SelectionTool": "", "ShaderImageEffect": "", "ShortcutProfile": "", -"SymmetryGuide": "" +"SymmetryGuide": "", +"Tiles": "" } [application] diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 5a79c26c8..167819ea7 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -4,7 +4,6 @@ signal project_changed enum GridTypes { CARTESIAN, ISOMETRIC, ALL } enum PressureSensitivity { NONE, ALPHA, SIZE, ALPHA_AND_SIZE } -enum TileMode { NONE, BOTH, X_AXIS, Y_AXIS } enum IconColorFrom { THEME, CUSTOM } enum ButtonSize { SMALL, BIG } @@ -12,6 +11,7 @@ enum FileMenu { NEW, OPEN, OPEN_LAST_PROJECT, RECENT, SAVE, SAVE_AS, EXPORT, EXP enum EditMenu { UNDO, REDO, COPY, CUT, PASTE, DELETE, NEW_BRUSH, PREFERENCES } enum ViewMenu { TILE_MODE, + TILE_MODE_OFFSETS, GREYSCALE_VIEW, MIRROR_VIEW, SHOW_GRID, diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index a09eb8059..4fa9437df 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -5,8 +5,7 @@ extends Reference var name := "" setget _name_changed var size: Vector2 setget _size_changed var undo_redo := UndoRedo.new() -var tile_mode: int = Global.TileMode.NONE -var tile_mode_rects := [] # Cached to avoid recalculation +var tiles: Tiles var undos := 0 # The number of times we added undo properties var fill_color := Color(0) var has_changed := false setget _has_changed_changed @@ -55,8 +54,8 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> frames = _frames name = _name size = _size + tiles = Tiles.new(size) selection_bitmap.create(size) - _update_tile_mode_rects() Global.tabs.add_tab(name) OpenSave.current_save_paths.append("") @@ -257,8 +256,8 @@ func change_project() -> void: 6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format)) ) - for j in Global.TileMode.values(): - Global.top_menu_container.tile_mode_submenu.set_item_checked(j, j == tile_mode) + for j in Tiles.MODE.values(): + Global.top_menu_container.tile_mode_submenu.set_item_checked(j, j == tiles.mode) # Change selection effect & bounding rectangle Global.canvas.selection.marching_ants_outline.offset = selection_offset @@ -353,6 +352,10 @@ func serialize() -> Dictionary: "name": name, "size_x": size.x, "size_y": size.y, + "tile_mode_x_basis_x": tiles.x_basis.x, + "tile_mode_x_basis_y": tiles.x_basis.y, + "tile_mode_y_basis_x": tiles.y_basis.x, + "tile_mode_y_basis_y": tiles.y_basis.y, "save_path": OpenSave.current_save_paths[Global.projects.find(self)], "layers": layer_data, "tags": tag_data, @@ -375,8 +378,14 @@ func deserialize(dict: Dictionary) -> void: if dict.has("size_x") and dict.has("size_y"): size.x = dict.size_x size.y = dict.size_y - _update_tile_mode_rects() + tiles.tile_size = size selection_bitmap = resize_bitmap(selection_bitmap, size) + if dict.has("tile_mode_x_basis_x") and dict.has("tile_mode_x_basis_y"): + tiles.x_basis.x = dict.tile_mode_x_basis_x + tiles.x_basis.y = dict.tile_mode_x_basis_y + if dict.has("tile_mode_y_basis_x") and dict.has("tile_mode_y_basis_y"): + tiles.y_basis.x = dict.tile_mode_y_basis_x + tiles.y_basis.y = dict.tile_mode_y_basis_y if dict.has("save_path"): OpenSave.current_save_paths[Global.projects.find(self)] = dict.save_path if dict.has("frames"): @@ -452,8 +461,16 @@ func _name_changed(value: String) -> void: func _size_changed(value: Vector2) -> void: + if size.x != 0: + tiles.x_basis = (tiles.x_basis * value.x / size.x).round() + else: + tiles.x_basis = Vector2(value.x, 0) + if size.y != 0: + tiles.y_basis = (tiles.y_basis * value.y / size.y).round() + else: + tiles.y_basis = Vector2(0, value.y) + tiles.tile_size = value size = value - _update_tile_mode_rects() func _frames_changed(value: Array) -> void: @@ -685,18 +702,6 @@ func _has_changed_changed(value: bool) -> void: Global.tabs.set_tab_title(Global.tabs.current_tab, name) -func get_tile_mode_rect() -> Rect2: - return tile_mode_rects[tile_mode] - - -func _update_tile_mode_rects() -> void: - tile_mode_rects.resize(Global.TileMode.size()) - tile_mode_rects[Global.TileMode.NONE] = Rect2(Vector2.ZERO, size) - tile_mode_rects[Global.TileMode.BOTH] = Rect2(Vector2(-1, -1) * size, Vector2(3, 3) * size) - tile_mode_rects[Global.TileMode.X_AXIS] = Rect2(Vector2(-1, 0) * size, Vector2(3, 1) * size) - tile_mode_rects[Global.TileMode.Y_AXIS] = Rect2(Vector2(0, -1) * size, Vector2(1, 3) * size) - - func is_empty() -> bool: return ( frames.size() == 1 @@ -731,6 +736,9 @@ func can_pixel_get_drawn( if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y: return false + if tiles.mode != Tiles.MODE.NONE and tiles.get_nearest_tile(pixel).position != Vector2.ZERO: + return false + if has_selection: if selection_position.x < 0: pixel.x -= selection_position.x diff --git a/src/Classes/Tiles.gd b/src/Classes/Tiles.gd new file mode 100644 index 000000000..564bc2c72 --- /dev/null +++ b/src/Classes/Tiles.gd @@ -0,0 +1,90 @@ +class_name Tiles +extends Reference + +enum MODE { NONE, BOTH, X_AXIS, Y_AXIS } + +var mode: int = MODE.NONE +var x_basis: Vector2 +var y_basis: Vector2 +var tile_size: Vector2 + + +func _init(size: Vector2): + x_basis = Vector2(size.x, 0) + y_basis = Vector2(0, size.y) + tile_size = size + + +func get_bounding_rect() -> Rect2: + match mode: + MODE.BOTH: + var diagonal := x_basis + y_basis + var cross_diagonal := x_basis - y_basis + var bounding_rect := Rect2(-diagonal, Vector2.ZERO) + bounding_rect = bounding_rect.expand(diagonal) + bounding_rect = bounding_rect.expand(-cross_diagonal) + bounding_rect = bounding_rect.expand(cross_diagonal) + bounding_rect = bounding_rect.grow_individual(0, 0, tile_size.x, tile_size.y) + return bounding_rect + MODE.X_AXIS: + var bounding_rect := Rect2(-x_basis, Vector2.ZERO) + bounding_rect = bounding_rect.expand(x_basis) + bounding_rect = bounding_rect.grow_individual(0, 0, tile_size.x, tile_size.y) + return bounding_rect + MODE.Y_AXIS: + var bounding_rect := Rect2(-y_basis, Vector2.ZERO) + bounding_rect = bounding_rect.expand(y_basis) + bounding_rect = bounding_rect.grow_individual(0, 0, tile_size.x, tile_size.y) + return bounding_rect + _: + return Rect2(Vector2.ZERO, tile_size) + + +func get_nearest_tile(point: Vector2) -> Rect2: + var tile_to_screen_space := Transform2D(x_basis, y_basis, Vector2.ZERO) + # Transform2D.basis_xform_inv() is broken so compute the inverse explicitly: + # https://github.com/godotengine/godot/issues/58556 + var screen_to_tile_space := tile_to_screen_space.affine_inverse() + var p := point - tile_size / 2.0 + Vector2(0.5, 0.5) # p relative to center of tiles + var p_tile_space := screen_to_tile_space.basis_xform(p) + var tl_tile := tile_to_screen_space.basis_xform(p_tile_space.floor()) + var tr_tile := tl_tile + x_basis + var bl_tile := tl_tile + y_basis + var br_tile := tl_tile + x_basis + y_basis + var tl_tile_dist := (p - tl_tile).length_squared() + var tr_tile_dist := (p - tr_tile).length_squared() + var bl_tile_dist := (p - bl_tile).length_squared() + var br_tile_dist := (p - br_tile).length_squared() + match [tl_tile_dist, tr_tile_dist, bl_tile_dist, br_tile_dist].min(): + tl_tile_dist: + return Rect2(tl_tile, tile_size) + tr_tile_dist: + return Rect2(tr_tile, tile_size) + bl_tile_dist: + return Rect2(bl_tile, tile_size) + _: + return Rect2(br_tile, tile_size) + + +func get_canon_position(position: Vector2) -> Vector2: + if mode == MODE.NONE: + return position + var nearest_tile = get_nearest_tile(position) + if nearest_tile.has_point(position): + position -= nearest_tile.position + return position + + +func has_point(point: Vector2) -> bool: + var screen_to_tile_space := Transform2D(x_basis, y_basis, Vector2.ZERO).affine_inverse() + var nearest_tile := get_nearest_tile(point) + var nearest_tile_tile_space := screen_to_tile_space.basis_xform(nearest_tile.position).round() + match mode: + MODE.BOTH: + return abs(nearest_tile_tile_space.x) <= 1 and abs(nearest_tile_tile_space.y) <= 1 + MODE.X_AXIS: + return abs(nearest_tile_tile_space.x) <= 1 and abs(nearest_tile_tile_space.y) == 0 + MODE.Y_AXIS: + return abs(nearest_tile_tile_space.x) == 0 and abs(nearest_tile_tile_space.y) <= 1 + _: + return nearest_tile_tile_space == Vector2.ZERO diff --git a/src/Main.tscn b/src/Main.tscn index b3f2bdec2..fcc00da13 100644 --- a/src/Main.tscn +++ b/src/Main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=19 format=2] +[gd_scene load_steps=20 format=2] [ext_resource path="res://assets/themes/dark/theme.tres" type="Theme" id=1] [ext_resource path="res://src/Main.gd" type="Script" id=2] @@ -13,6 +13,7 @@ [ext_resource path="res://src/UI/Dialogs/SaveSprite.tscn" type="PackedScene" id=11] [ext_resource path="res://src/UI/Dialogs/OpenSprite.tscn" type="PackedScene" id=12] [ext_resource path="res://src/UI/Dialogs/ManageLayouts.tscn" type="PackedScene" id=13] +[ext_resource path="res://src/UI/Dialogs/TileModeOffsetsDialog.tscn" type="PackedScene" id=14] [ext_resource path="res://src/UI/Dialogs/SplashDialog.tscn" type="PackedScene" id=27] [ext_resource path="res://src/UI/Dialogs/CreateNewImage.tscn" type="PackedScene" id=28] [ext_resource path="res://src/Preferences/PreferencesDialog.tscn" type="PackedScene" id=32] @@ -121,6 +122,8 @@ dialog_autowrap = true [node name="WindowOpacityDialog" parent="Dialogs" instance=ExtResource( 10 )] +[node name="TileModeOffsetsDialog" parent="Dialogs" instance=ExtResource( 14 )] + [node name="Extensions" type="Control" parent="."] __meta__ = { "_edit_use_anchors_": false diff --git a/src/Tools/BaseTool.gd b/src/Tools/BaseTool.gd index cd0aaf04c..387fc41f9 100644 --- a/src/Tools/BaseTool.gd +++ b/src/Tools/BaseTool.gd @@ -76,7 +76,7 @@ func _get_draw_rect() -> Rect2: if Global.current_project.has_selection: return Global.current_project.get_selection_rectangle() else: - return Global.current_project.tile_mode_rects[Global.TileMode.NONE] + return Rect2(Vector2.ZERO, Global.current_project.size) func _get_draw_image() -> Image: diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index c80af0a1c..f1f550ae8 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -145,7 +145,7 @@ func draw_start(position: Vector2) -> void: Global.canvas.selection.transform_content_confirm() if ( !Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn() - or !Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(position) + or Global.current_project.tiles.get_nearest_tile(position).position != Vector2.ZERO ): return if ( @@ -444,8 +444,7 @@ func _get_undo_data() -> Dictionary: func _pick_color(position: Vector2) -> void: var project: Project = Global.current_project - if project.tile_mode and project.get_tile_mode_rect().has_point(position): - position = position.posmodv(project.size) + position = project.tiles.get_canon_position(position) if position.x < 0 or position.y < 0: return diff --git a/src/Tools/ColorPicker.gd b/src/Tools/ColorPicker.gd index 8aa08ec47..179176792 100644 --- a/src/Tools/ColorPicker.gd +++ b/src/Tools/ColorPicker.gd @@ -53,8 +53,7 @@ func draw_end(position: Vector2) -> void: func _pick_color(position: Vector2) -> void: var project: Project = Global.current_project - if project.tile_mode and project.get_tile_mode_rect().has_point(position): - position = position.posmodv(project.size) + position = project.tiles.get_canon_position(position) if position.x < 0 or position.y < 0: return diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index 160279ef3..2dfd004cf 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -25,7 +25,6 @@ var _line_polylines := [] # Memorize some stuff when doing brush strokes var _stroke_project: Project var _stroke_images := [] # Array of Images -var _tile_mode_rect: Rect2 var _is_mask_size_zero := true var _circle_tool_shortcut: PoolVector2Array @@ -223,8 +222,6 @@ func _prepare_tool() -> void: _stroke_project = Global.current_project # Memorize the frame/layer we are drawing on rather than fetching it on every pixel _stroke_images = _get_selected_draw_images() - # Memorize current tile mode - _tile_mode_rect = _stroke_project.get_tile_mode_rect() # This may prevent a few tests when setting pixels _is_mask_size_zero = _mask.size() == 0 match _brush.type: @@ -354,9 +351,7 @@ func _draw_tool_circle_from_map(position: Vector2) -> PoolVector2Array: func draw_tool_brush(position: Vector2) -> void: var project: Project = Global.current_project - - if project.tile_mode and project.get_tile_mode_rect().has_point(position): - position = position.posmodv(project.size) + position = project.tiles.get_canon_position(position) var size := _brush_image.get_size() var dst := position - (size / 2).floor() @@ -410,13 +405,11 @@ func remove_unselected_parts_of_brush(brush: Image, dst: Vector2) -> Image: func draw_indicator() -> void: draw_indicator_at(_cursor, Vector2.ZERO, Color.blue) - if ( - Global.current_project.tile_mode - and Global.current_project.get_tile_mode_rect().has_point(_cursor) - ): - var tile := _line_start if _draw_line else _cursor - if not Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(tile): - var offset := tile - tile.posmodv(Global.current_project.size) + if Global.current_project.tiles.mode and Global.current_project.tiles.has_point(_cursor): + var position := _line_start if _draw_line else _cursor + var nearest_tile := Global.current_project.tiles.get_nearest_tile(position) + if nearest_tile.position != Vector2.ZERO: + var offset := nearest_tile.position draw_indicator_at(_cursor, offset, Color.green) @@ -452,8 +445,7 @@ func _set_pixel(position: Vector2, ignore_mirroring := false) -> void: func _set_pixel_no_cache(position: Vector2, ignore_mirroring := false) -> void: - if _stroke_project.tile_mode and _tile_mode_rect.has_point(position): - position = position.posmodv(_stroke_project.size) + position = _stroke_project.tiles.get_canon_position(position) if !_stroke_project.can_pixel_get_drawn(position): return @@ -652,8 +644,7 @@ func _get_undo_data() -> Dictionary: func _pick_color(position: Vector2) -> void: var project: Project = Global.current_project - if project.tile_mode and project.get_tile_mode_rect().has_point(position): - position = position.posmodv(project.size) + position = project.tiles.get_canon_position(position) if position.x < 0 or position.y < 0: return diff --git a/src/UI/Canvas/Canvas.gd b/src/UI/Canvas/Canvas.gd index cbdb41caf..3cf54e767 100644 --- a/src/UI/Canvas/Canvas.gd +++ b/src/UI/Canvas/Canvas.gd @@ -52,7 +52,7 @@ func _draw() -> void: refresh_onion() currently_visible_frame.size = Global.current_project.size current_frame_drawer.update() - if Global.current_project.tile_mode != Global.TileMode.NONE: + if Global.current_project.tiles.mode != Tiles.MODE.NONE: tile_mode.update() draw_set_transform(position, rotation, scale) diff --git a/src/UI/Canvas/Grid.gd b/src/UI/Canvas/Grid.gd index 52162ec96..a5e9f13e7 100644 --- a/src/UI/Canvas/Grid.gd +++ b/src/UI/Canvas/Grid.gd @@ -7,7 +7,7 @@ func _draw() -> void: var target_rect: Rect2 if Global.grid_draw_over_tile_mode: - target_rect = Global.current_project.get_tile_mode_rect() + target_rect = Global.current_project.tiles.get_bounding_rect() else: target_rect = Rect2(Vector2.ZERO, Global.current_project.size) if target_rect.has_no_area(): diff --git a/src/UI/Canvas/PixelGrid.gd b/src/UI/Canvas/PixelGrid.gd index 1c929b6c9..b5ba863ba 100644 --- a/src/UI/Canvas/PixelGrid.gd +++ b/src/UI/Canvas/PixelGrid.gd @@ -9,7 +9,7 @@ func _draw() -> void: if zoom_percentage < Global.pixel_grid_show_at_zoom: return - var target_rect: Rect2 = Global.current_project.get_tile_mode_rect() + var target_rect: Rect2 = Global.current_project.tiles.get_bounding_rect() if target_rect.has_no_area(): return diff --git a/src/UI/Canvas/TileMode.gd b/src/UI/Canvas/TileMode.gd index d17bb6312..b949ec9ed 100644 --- a/src/UI/Canvas/TileMode.gd +++ b/src/UI/Canvas/TileMode.gd @@ -1,17 +1,17 @@ extends Node2D +var tiles: Tiles +var draw_center := false + func _draw() -> void: - var size: Vector2 = Global.current_project.size - var positions: Array = get_tile_positions(size) + var positions := get_tile_positions() var tilemode_opacity := Global.tilemode_opacity - var position_tmp := position - var scale_tmp := scale if Global.mirror_view: - position_tmp.x = position_tmp.x + Global.current_project.size.x - scale_tmp.x = -1 - draw_set_transform(position_tmp, rotation, scale_tmp) + var position_tmp := Vector2(Global.current_project.size.x, 0) + var scale_tmp := Vector2(-1, 1) + draw_set_transform(position_tmp, 0, scale_tmp) var modulate_color := Color( tilemode_opacity, tilemode_opacity, tilemode_opacity, tilemode_opacity @@ -20,31 +20,33 @@ func _draw() -> void: for pos in positions: draw_texture(current_frame_texture, pos, modulate_color) - draw_set_transform(position, rotation, scale) + draw_set_transform(Vector2.ZERO, 0, Vector2.ONE) -func get_tile_positions(size): - match Global.current_project.tile_mode: - Global.TileMode.BOTH: - return [ - Vector2(0, size.y), # Down - Vector2(-size.x, size.y), # Down left - Vector2(-size.x, 0), # Left - -size, # Up left - Vector2(0, -size.y), # Up - Vector2(size.x, -size.y), # Up right - Vector2(size.x, 0), # Right - size # Down right - ] - Global.TileMode.X_AXIS: - return [ - Vector2(size.x, 0), # Right - Vector2(-size.x, 0), # Left - ] - Global.TileMode.Y_AXIS: - return [ - Vector2(0, size.y), # Down - Vector2(0, -size.y), # Up - ] - _: - return [] +func get_tile_positions() -> Array: + var defaulted_tiles := tiles + if defaulted_tiles == null: + defaulted_tiles = Global.current_project.tiles + + var x_basis: Vector2 = defaulted_tiles.x_basis + var y_basis: Vector2 = defaulted_tiles.y_basis + var tile_mode: int = defaulted_tiles.mode + + var x_range := ( + range(-1, 2) + if tile_mode in [Tiles.MODE.X_AXIS, Tiles.MODE.BOTH] + else range(0, 1) + ) + var y_range := ( + range(-1, 2) + if tile_mode in [Tiles.MODE.Y_AXIS, Tiles.MODE.BOTH] + else range(0, 1) + ) + var positions := [] + for r in y_range: + for c in x_range: + if not draw_center and r == 0 and c == 0: + continue + var position: Vector2 = r * y_basis + c * x_basis + positions.append(position) + return positions diff --git a/src/UI/Dialogs/TileModeOffsetsDialog.gd b/src/UI/Dialogs/TileModeOffsetsDialog.gd new file mode 100644 index 000000000..196e80bdb --- /dev/null +++ b/src/UI/Dialogs/TileModeOffsetsDialog.gd @@ -0,0 +1,72 @@ +extends ConfirmationDialog + +onready var x_basis_x_spinbox: SpinBox = $VBoxContainer/OptionsContainer/XBasisX +onready var x_basis_y_spinbox: SpinBox = $VBoxContainer/OptionsContainer/XBasisY +onready var y_basis_x_spinbox: SpinBox = $VBoxContainer/OptionsContainer/YBasisX +onready var y_basis_y_spinbox: SpinBox = $VBoxContainer/OptionsContainer/YBasisY +onready var preview_rect: Control = $VBoxContainer/AspectRatioContainer/Preview +onready var tile_mode: Node2D = $VBoxContainer/AspectRatioContainer/Preview/TileMode + + +func _on_TileModeOffsetsDialog_about_to_show() -> void: + tile_mode.draw_center = true + tile_mode.tiles = Tiles.new(Global.current_project.size) + tile_mode.tiles.mode = Tiles.MODE.BOTH + tile_mode.tiles.x_basis = Global.current_project.tiles.x_basis + tile_mode.tiles.y_basis = Global.current_project.tiles.y_basis + x_basis_x_spinbox.value = tile_mode.tiles.x_basis.x + x_basis_y_spinbox.value = tile_mode.tiles.x_basis.y + y_basis_x_spinbox.value = tile_mode.tiles.y_basis.x + y_basis_y_spinbox.value = tile_mode.tiles.y_basis.y + update_preview() + + +func _on_TileModeOffsetsDialog_confirmed() -> void: + Global.current_project.tiles.x_basis = tile_mode.tiles.x_basis + Global.current_project.tiles.y_basis = tile_mode.tiles.y_basis + Global.canvas.tile_mode.update() + Global.transparent_checker.update_rect() + + +func _on_XBasisX_value_changed(value: int) -> void: + tile_mode.tiles.x_basis.x = value + update_preview() + + +func _on_XBasisY_value_changed(value: int) -> void: + tile_mode.tiles.x_basis.y = value + update_preview() + + +func _on_YBasisX_value_changed(value: int) -> void: + tile_mode.tiles.y_basis.x = value + update_preview() + + +func _on_YBasisY_value_changed(value: int) -> void: + tile_mode.tiles.y_basis.y = value + update_preview() + + +func update_preview() -> void: + var bounding_rect: Rect2 = tile_mode.tiles.get_bounding_rect() + var offset := -bounding_rect.position + var axis_scale := preview_rect.rect_size / bounding_rect.size + var min_scale: Vector2 = preview_rect.rect_size / (tile_mode.tiles.tile_size * 3.0) + var scale: float = [axis_scale.x, axis_scale.y, min_scale.x, min_scale.y].min() + var t := Transform2D.IDENTITY.translated(offset).scaled(Vector2(scale, scale)) + var transformed_bounding_rect: Rect2 = t.xform(bounding_rect) + var centering_offset := (preview_rect.rect_size - transformed_bounding_rect.size) / 2.0 + t = t.translated(centering_offset / scale) + tile_mode.transform = t + tile_mode.update() + preview_rect.get_node("TransparentChecker").rect_size = preview_rect.rect_size + + +func _on_TileModeOffsetsDialog_popup_hide() -> void: + Global.dialog_open(false) + + +func _on_TileModeOffsetsDialog_item_rect_changed(): + if tile_mode: + update_preview() diff --git a/src/UI/Dialogs/TileModeOffsetsDialog.tscn b/src/UI/Dialogs/TileModeOffsetsDialog.tscn new file mode 100644 index 000000000..a7d2ab64a --- /dev/null +++ b/src/UI/Dialogs/TileModeOffsetsDialog.tscn @@ -0,0 +1,129 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://src/UI/TransparentChecker.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/UI/Canvas/TileMode.gd" type="Script" id=2] +[ext_resource path="res://src/UI/Dialogs/TileModeOffsetsDialog.gd" type="Script" id=3] + +[sub_resource type="CanvasItemMaterial" id=1] +blend_mode = 4 + +[node name="TileModeOffsetsDialog" type="ConfirmationDialog"] +margin_right = 216.0 +margin_bottom = 374.0 +rect_min_size = Vector2( 172, 60.2 ) +window_title = "Tile Mode Offsets" +resizable = true +script = ExtResource( 3 ) + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +margin_left = 8.0 +margin_top = 8.0 +margin_right = 208.0 +margin_bottom = 338.0 + +[node name="TileModeOffsets" type="Label" parent="VBoxContainer"] +margin_right = 200.0 +margin_bottom = 14.0 +text = "Tile Mode Offsets" + +[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"] +margin_top = 18.0 +margin_right = 200.0 +margin_bottom = 126.0 +custom_constants/vseparation = 4 +custom_constants/hseparation = 2 +columns = 2 + +[node name="XBasisXLabel" type="Label" parent="VBoxContainer/OptionsContainer"] +margin_top = 5.0 +margin_right = 61.0 +margin_bottom = 19.0 +text = "X-basis x:" + +[node name="XBasisX" type="SpinBox" parent="VBoxContainer/OptionsContainer"] +margin_left = 63.0 +margin_right = 137.0 +margin_bottom = 24.0 +mouse_default_cursor_shape = 2 +min_value = -16384.0 +max_value = 16384.0 +suffix = "px" + +[node name="XBasisYLabel" type="Label" parent="VBoxContainer/OptionsContainer"] +margin_top = 33.0 +margin_right = 61.0 +margin_bottom = 47.0 +text = "X-basis y:" + +[node name="XBasisY" type="SpinBox" parent="VBoxContainer/OptionsContainer"] +margin_left = 63.0 +margin_top = 28.0 +margin_right = 137.0 +margin_bottom = 52.0 +mouse_default_cursor_shape = 2 +min_value = -16384.0 +max_value = 16384.0 +suffix = "px" + +[node name="YBasisXLabel" type="Label" parent="VBoxContainer/OptionsContainer"] +margin_top = 61.0 +margin_right = 61.0 +margin_bottom = 75.0 +text = "Y-basis x:" + +[node name="YBasisX" type="SpinBox" parent="VBoxContainer/OptionsContainer"] +margin_left = 63.0 +margin_top = 56.0 +margin_right = 137.0 +margin_bottom = 80.0 +mouse_default_cursor_shape = 2 +min_value = -16384.0 +max_value = 16384.0 +suffix = "px" + +[node name="YBasisYLabel" type="Label" parent="VBoxContainer/OptionsContainer"] +margin_top = 89.0 +margin_right = 61.0 +margin_bottom = 103.0 +text = "Y-basis y:" + +[node name="YBasisY" type="SpinBox" parent="VBoxContainer/OptionsContainer"] +margin_left = 63.0 +margin_top = 84.0 +margin_right = 137.0 +margin_bottom = 108.0 +mouse_default_cursor_shape = 2 +min_value = -16384.0 +max_value = 16384.0 +suffix = "px" + +[node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"] +margin_top = 130.0 +margin_right = 200.0 +margin_bottom = 330.0 +size_flags_vertical = 3 + +[node name="Preview" type="Control" parent="VBoxContainer/AspectRatioContainer"] +margin_right = 200.0 +margin_bottom = 200.0 +rect_min_size = Vector2( 200, 200 ) + +[node name="TileMode" type="Node2D" parent="VBoxContainer/AspectRatioContainer/Preview"] +material = SubResource( 1 ) +script = ExtResource( 2 ) + +[node name="TransparentChecker" parent="VBoxContainer/AspectRatioContainer/Preview" instance=ExtResource( 1 )] +show_behind_parent = true +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = 0.0 +margin_bottom = 0.0 + +[connection signal="about_to_show" from="." to="." method="_on_TileModeOffsetsDialog_about_to_show"] +[connection signal="confirmed" from="." to="." method="_on_TileModeOffsetsDialog_confirmed"] +[connection signal="item_rect_changed" from="." to="." method="_on_TileModeOffsetsDialog_item_rect_changed"] +[connection signal="popup_hide" from="." to="." method="_on_TileModeOffsetsDialog_popup_hide"] +[connection signal="value_changed" from="VBoxContainer/OptionsContainer/XBasisX" to="." method="_on_XBasisX_value_changed"] +[connection signal="value_changed" from="VBoxContainer/OptionsContainer/XBasisY" to="." method="_on_XBasisY_value_changed"] +[connection signal="value_changed" from="VBoxContainer/OptionsContainer/YBasisX" to="." method="_on_YBasisX_value_changed"] +[connection signal="value_changed" from="VBoxContainer/OptionsContainer/YBasisY" to="." method="_on_YBasisY_value_changed"] diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 125cc1d7c..97811cba2 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -105,6 +105,7 @@ func _setup_view_menu() -> void: # Order as in Global.ViewMenu enum var view_menu_items := [ "Tile Mode", + "Tile Mode Offsets", "Greyscale View", "Mirror View", "Show Grid", @@ -117,6 +118,8 @@ func _setup_view_menu() -> void: for item in view_menu_items: if item == "Tile Mode": _setup_tile_mode_submenu(item) + elif item == "Tile Mode Offsets": + view_menu.add_item(item, i) else: view_menu.add_check_item(item, i) i += 1 @@ -150,11 +153,11 @@ func _setup_view_menu() -> void: func _setup_tile_mode_submenu(item: String) -> void: tile_mode_submenu.set_name("tile_mode_submenu") - tile_mode_submenu.add_radio_check_item("None", Global.TileMode.NONE) - tile_mode_submenu.set_item_checked(Global.TileMode.NONE, true) - tile_mode_submenu.add_radio_check_item("Tiled In Both Axis", Global.TileMode.BOTH) - tile_mode_submenu.add_radio_check_item("Tiled In X Axis", Global.TileMode.X_AXIS) - tile_mode_submenu.add_radio_check_item("Tiled In Y Axis", Global.TileMode.Y_AXIS) + tile_mode_submenu.add_radio_check_item("None", Tiles.MODE.NONE) + tile_mode_submenu.set_item_checked(Tiles.MODE.NONE, true) + tile_mode_submenu.add_radio_check_item("Tiled In Both Axis", Tiles.MODE.BOTH) + tile_mode_submenu.add_radio_check_item("Tiled In X Axis", Tiles.MODE.X_AXIS) + tile_mode_submenu.add_radio_check_item("Tiled In Y Axis", Tiles.MODE.Y_AXIS) tile_mode_submenu.hide_on_checkable_item_selection = false tile_mode_submenu.connect("id_pressed", self, "_tile_mode_submenu_id_pressed") @@ -419,6 +422,8 @@ func edit_menu_id_pressed(id: int) -> void: func view_menu_id_pressed(id: int) -> void: match id: + Global.ViewMenu.TILE_MODE_OFFSETS: + _show_tile_mode_offsets_popup() Global.ViewMenu.GREYSCALE_VIEW: _toggle_greyscale_view() Global.ViewMenu.MIRROR_VIEW: @@ -437,10 +442,15 @@ func view_menu_id_pressed(id: int) -> void: Global.canvas.update() +func _show_tile_mode_offsets_popup() -> void: + Global.control.get_node("Dialogs/TileModeOffsetsDialog").popup_centered() + Global.dialog_open(true) + + func _tile_mode_submenu_id_pressed(id: int) -> void: - Global.current_project.tile_mode = id - Global.transparent_checker.fit_rect(Global.current_project.get_tile_mode_rect()) - for i in Global.TileMode.values(): + Global.current_project.tiles.mode = id + Global.transparent_checker.fit_rect(Global.current_project.tiles.get_bounding_rect()) + for i in Tiles.MODE.values(): tile_mode_submenu.set_item_checked(i, i == id) Global.canvas.tile_mode.update() Global.canvas.pixel_grid.update() diff --git a/src/UI/TransparentChecker.gd b/src/UI/TransparentChecker.gd index a8b57b5b6..e5f5508c0 100644 --- a/src/UI/TransparentChecker.gd +++ b/src/UI/TransparentChecker.gd @@ -8,7 +8,7 @@ func _ready() -> void: func update_rect() -> void: rect_size = Global.current_project.size if self == Global.transparent_checker: - fit_rect(Global.current_project.get_tile_mode_rect()) + fit_rect(Global.current_project.tiles.get_bounding_rect()) Global.second_viewport.get_node("Viewport/TransparentChecker").update_rect() Global.small_preview_viewport.get_node("Viewport/TransparentChecker").update_rect() material.set_shader_param("size", Global.checker_size)