1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-19 01:29:49 +00:00

Add options for custom tile mode offest (#707)

* Add preferences for tile mode basis vectors

Each tile is offset according to the x and y basis. For example, a tile (1,1) would be at basis_x + basis_y

* Update tools for custom tile modes

Show the indicator in the correct position, and only draw on the nearest tile.

* Fix style issues

* Move tile functionality to own class to prevent bloating Project

* Fix error in Tiles bounding box logic

* Make tile mode offsets project settings

Since the desired tile mode offsets depends on the tile being drawn, the tile mode offsets should be a project specific setting which is persisted in the project file.

* Update TileMode object immediately after closing dialog

* Don't draw center tile by default in TileMode

* Move tile mode offsets to view menu

* Move tile mode offsets dialog out of ImageEffects
This commit is contained in:
Grant Moyer 2022-06-20 05:07:20 -04:00 committed by GitHub
parent ec15a3f0b4
commit aadad462b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 399 additions and 90 deletions

View file

@ -123,6 +123,11 @@ _global_script_classes=[ {
"class": "SymmetryGuide", "class": "SymmetryGuide",
"language": "GDScript", "language": "GDScript",
"path": "res://src/UI/Canvas/Rulers/SymmetryGuide.gd" "path": "res://src/UI/Canvas/Rulers/SymmetryGuide.gd"
}, {
"base": "Reference",
"class": "Tiles",
"language": "GDScript",
"path": "res://src/Classes/Tiles.gd"
} ] } ]
_global_script_class_icons={ _global_script_class_icons={
"AnimationTag": "", "AnimationTag": "",
@ -147,7 +152,8 @@ _global_script_class_icons={
"SelectionTool": "", "SelectionTool": "",
"ShaderImageEffect": "", "ShaderImageEffect": "",
"ShortcutProfile": "", "ShortcutProfile": "",
"SymmetryGuide": "" "SymmetryGuide": "",
"Tiles": ""
} }
[application] [application]

View file

@ -4,7 +4,6 @@ signal project_changed
enum GridTypes { CARTESIAN, ISOMETRIC, ALL } enum GridTypes { CARTESIAN, ISOMETRIC, ALL }
enum PressureSensitivity { NONE, ALPHA, SIZE, ALPHA_AND_SIZE } enum PressureSensitivity { NONE, ALPHA, SIZE, ALPHA_AND_SIZE }
enum TileMode { NONE, BOTH, X_AXIS, Y_AXIS }
enum IconColorFrom { THEME, CUSTOM } enum IconColorFrom { THEME, CUSTOM }
enum ButtonSize { SMALL, BIG } 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 EditMenu { UNDO, REDO, COPY, CUT, PASTE, DELETE, NEW_BRUSH, PREFERENCES }
enum ViewMenu { enum ViewMenu {
TILE_MODE, TILE_MODE,
TILE_MODE_OFFSETS,
GREYSCALE_VIEW, GREYSCALE_VIEW,
MIRROR_VIEW, MIRROR_VIEW,
SHOW_GRID, SHOW_GRID,

View file

@ -5,8 +5,7 @@ extends Reference
var name := "" setget _name_changed var name := "" setget _name_changed
var size: Vector2 setget _size_changed var size: Vector2 setget _size_changed
var undo_redo := UndoRedo.new() var undo_redo := UndoRedo.new()
var tile_mode: int = Global.TileMode.NONE var tiles: Tiles
var tile_mode_rects := [] # Cached to avoid recalculation
var undos := 0 # The number of times we added undo properties var undos := 0 # The number of times we added undo properties
var fill_color := Color(0) var fill_color := Color(0)
var has_changed := false setget _has_changed_changed var has_changed := false setget _has_changed_changed
@ -55,8 +54,8 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) ->
frames = _frames frames = _frames
name = _name name = _name
size = _size size = _size
tiles = Tiles.new(size)
selection_bitmap.create(size) selection_bitmap.create(size)
_update_tile_mode_rects()
Global.tabs.add_tab(name) Global.tabs.add_tab(name)
OpenSave.current_save_paths.append("") 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)) 6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))
) )
for j in Global.TileMode.values(): for j in Tiles.MODE.values():
Global.top_menu_container.tile_mode_submenu.set_item_checked(j, j == tile_mode) Global.top_menu_container.tile_mode_submenu.set_item_checked(j, j == tiles.mode)
# Change selection effect & bounding rectangle # Change selection effect & bounding rectangle
Global.canvas.selection.marching_ants_outline.offset = selection_offset Global.canvas.selection.marching_ants_outline.offset = selection_offset
@ -353,6 +352,10 @@ func serialize() -> Dictionary:
"name": name, "name": name,
"size_x": size.x, "size_x": size.x,
"size_y": size.y, "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)], "save_path": OpenSave.current_save_paths[Global.projects.find(self)],
"layers": layer_data, "layers": layer_data,
"tags": tag_data, "tags": tag_data,
@ -375,8 +378,14 @@ func deserialize(dict: Dictionary) -> void:
if dict.has("size_x") and dict.has("size_y"): if dict.has("size_x") and dict.has("size_y"):
size.x = dict.size_x size.x = dict.size_x
size.y = dict.size_y size.y = dict.size_y
_update_tile_mode_rects() tiles.tile_size = size
selection_bitmap = resize_bitmap(selection_bitmap, 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"): if dict.has("save_path"):
OpenSave.current_save_paths[Global.projects.find(self)] = dict.save_path OpenSave.current_save_paths[Global.projects.find(self)] = dict.save_path
if dict.has("frames"): if dict.has("frames"):
@ -452,8 +461,16 @@ func _name_changed(value: String) -> void:
func _size_changed(value: Vector2) -> 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 size = value
_update_tile_mode_rects()
func _frames_changed(value: Array) -> void: 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) 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: func is_empty() -> bool:
return ( return (
frames.size() == 1 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: if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y:
return false return false
if tiles.mode != Tiles.MODE.NONE and tiles.get_nearest_tile(pixel).position != Vector2.ZERO:
return false
if has_selection: if has_selection:
if selection_position.x < 0: if selection_position.x < 0:
pixel.x -= selection_position.x pixel.x -= selection_position.x

90
src/Classes/Tiles.gd Normal file
View file

@ -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

View file

@ -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://assets/themes/dark/theme.tres" type="Theme" id=1]
[ext_resource path="res://src/Main.gd" type="Script" id=2] [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/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/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/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/SplashDialog.tscn" type="PackedScene" id=27]
[ext_resource path="res://src/UI/Dialogs/CreateNewImage.tscn" type="PackedScene" id=28] [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] [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="WindowOpacityDialog" parent="Dialogs" instance=ExtResource( 10 )]
[node name="TileModeOffsetsDialog" parent="Dialogs" instance=ExtResource( 14 )]
[node name="Extensions" type="Control" parent="."] [node name="Extensions" type="Control" parent="."]
__meta__ = { __meta__ = {
"_edit_use_anchors_": false "_edit_use_anchors_": false

View file

@ -76,7 +76,7 @@ func _get_draw_rect() -> Rect2:
if Global.current_project.has_selection: if Global.current_project.has_selection:
return Global.current_project.get_selection_rectangle() return Global.current_project.get_selection_rectangle()
else: else:
return Global.current_project.tile_mode_rects[Global.TileMode.NONE] return Rect2(Vector2.ZERO, Global.current_project.size)
func _get_draw_image() -> Image: func _get_draw_image() -> Image:

View file

@ -145,7 +145,7 @@ func draw_start(position: Vector2) -> void:
Global.canvas.selection.transform_content_confirm() Global.canvas.selection.transform_content_confirm()
if ( if (
!Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn() !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 return
if ( if (
@ -444,8 +444,7 @@ func _get_undo_data() -> Dictionary:
func _pick_color(position: Vector2) -> void: func _pick_color(position: Vector2) -> void:
var project: Project = Global.current_project var project: Project = Global.current_project
if project.tile_mode and project.get_tile_mode_rect().has_point(position): position = project.tiles.get_canon_position(position)
position = position.posmodv(project.size)
if position.x < 0 or position.y < 0: if position.x < 0 or position.y < 0:
return return

View file

@ -53,8 +53,7 @@ func draw_end(position: Vector2) -> void:
func _pick_color(position: Vector2) -> void: func _pick_color(position: Vector2) -> void:
var project: Project = Global.current_project var project: Project = Global.current_project
if project.tile_mode and project.get_tile_mode_rect().has_point(position): position = project.tiles.get_canon_position(position)
position = position.posmodv(project.size)
if position.x < 0 or position.y < 0: if position.x < 0 or position.y < 0:
return return

View file

@ -25,7 +25,6 @@ var _line_polylines := []
# Memorize some stuff when doing brush strokes # Memorize some stuff when doing brush strokes
var _stroke_project: Project var _stroke_project: Project
var _stroke_images := [] # Array of Images var _stroke_images := [] # Array of Images
var _tile_mode_rect: Rect2
var _is_mask_size_zero := true var _is_mask_size_zero := true
var _circle_tool_shortcut: PoolVector2Array var _circle_tool_shortcut: PoolVector2Array
@ -223,8 +222,6 @@ func _prepare_tool() -> void:
_stroke_project = Global.current_project _stroke_project = Global.current_project
# Memorize the frame/layer we are drawing on rather than fetching it on every pixel # Memorize the frame/layer we are drawing on rather than fetching it on every pixel
_stroke_images = _get_selected_draw_images() _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 # This may prevent a few tests when setting pixels
_is_mask_size_zero = _mask.size() == 0 _is_mask_size_zero = _mask.size() == 0
match _brush.type: match _brush.type:
@ -354,9 +351,7 @@ func _draw_tool_circle_from_map(position: Vector2) -> PoolVector2Array:
func draw_tool_brush(position: Vector2) -> void: func draw_tool_brush(position: Vector2) -> void:
var project: Project = Global.current_project var project: Project = Global.current_project
position = project.tiles.get_canon_position(position)
if project.tile_mode and project.get_tile_mode_rect().has_point(position):
position = position.posmodv(project.size)
var size := _brush_image.get_size() var size := _brush_image.get_size()
var dst := position - (size / 2).floor() 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: func draw_indicator() -> void:
draw_indicator_at(_cursor, Vector2.ZERO, Color.blue) draw_indicator_at(_cursor, Vector2.ZERO, Color.blue)
if ( if Global.current_project.tiles.mode and Global.current_project.tiles.has_point(_cursor):
Global.current_project.tile_mode var position := _line_start if _draw_line else _cursor
and Global.current_project.get_tile_mode_rect().has_point(_cursor) var nearest_tile := Global.current_project.tiles.get_nearest_tile(position)
): if nearest_tile.position != Vector2.ZERO:
var tile := _line_start if _draw_line else _cursor var offset := nearest_tile.position
if not Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(tile):
var offset := tile - tile.posmodv(Global.current_project.size)
draw_indicator_at(_cursor, offset, Color.green) 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: func _set_pixel_no_cache(position: Vector2, ignore_mirroring := false) -> void:
if _stroke_project.tile_mode and _tile_mode_rect.has_point(position): position = _stroke_project.tiles.get_canon_position(position)
position = position.posmodv(_stroke_project.size)
if !_stroke_project.can_pixel_get_drawn(position): if !_stroke_project.can_pixel_get_drawn(position):
return return
@ -652,8 +644,7 @@ func _get_undo_data() -> Dictionary:
func _pick_color(position: Vector2) -> void: func _pick_color(position: Vector2) -> void:
var project: Project = Global.current_project var project: Project = Global.current_project
if project.tile_mode and project.get_tile_mode_rect().has_point(position): position = project.tiles.get_canon_position(position)
position = position.posmodv(project.size)
if position.x < 0 or position.y < 0: if position.x < 0 or position.y < 0:
return return

View file

@ -52,7 +52,7 @@ func _draw() -> void:
refresh_onion() refresh_onion()
currently_visible_frame.size = Global.current_project.size currently_visible_frame.size = Global.current_project.size
current_frame_drawer.update() 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() tile_mode.update()
draw_set_transform(position, rotation, scale) draw_set_transform(position, rotation, scale)

View file

@ -7,7 +7,7 @@ func _draw() -> void:
var target_rect: Rect2 var target_rect: Rect2
if Global.grid_draw_over_tile_mode: 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: else:
target_rect = Rect2(Vector2.ZERO, Global.current_project.size) target_rect = Rect2(Vector2.ZERO, Global.current_project.size)
if target_rect.has_no_area(): if target_rect.has_no_area():

View file

@ -9,7 +9,7 @@ func _draw() -> void:
if zoom_percentage < Global.pixel_grid_show_at_zoom: if zoom_percentage < Global.pixel_grid_show_at_zoom:
return 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(): if target_rect.has_no_area():
return return

View file

@ -1,17 +1,17 @@
extends Node2D extends Node2D
var tiles: Tiles
var draw_center := false
func _draw() -> void: func _draw() -> void:
var size: Vector2 = Global.current_project.size var positions := get_tile_positions()
var positions: Array = get_tile_positions(size)
var tilemode_opacity := Global.tilemode_opacity var tilemode_opacity := Global.tilemode_opacity
var position_tmp := position
var scale_tmp := scale
if Global.mirror_view: if Global.mirror_view:
position_tmp.x = position_tmp.x + Global.current_project.size.x var position_tmp := Vector2(Global.current_project.size.x, 0)
scale_tmp.x = -1 var scale_tmp := Vector2(-1, 1)
draw_set_transform(position_tmp, rotation, scale_tmp) draw_set_transform(position_tmp, 0, scale_tmp)
var modulate_color := Color( var modulate_color := Color(
tilemode_opacity, tilemode_opacity, tilemode_opacity, tilemode_opacity tilemode_opacity, tilemode_opacity, tilemode_opacity, tilemode_opacity
@ -20,31 +20,33 @@ func _draw() -> void:
for pos in positions: for pos in positions:
draw_texture(current_frame_texture, pos, modulate_color) 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): func get_tile_positions() -> Array:
match Global.current_project.tile_mode: var defaulted_tiles := tiles
Global.TileMode.BOTH: if defaulted_tiles == null:
return [ defaulted_tiles = Global.current_project.tiles
Vector2(0, size.y), # Down
Vector2(-size.x, size.y), # Down left var x_basis: Vector2 = defaulted_tiles.x_basis
Vector2(-size.x, 0), # Left var y_basis: Vector2 = defaulted_tiles.y_basis
-size, # Up left var tile_mode: int = defaulted_tiles.mode
Vector2(0, -size.y), # Up
Vector2(size.x, -size.y), # Up right var x_range := (
Vector2(size.x, 0), # Right range(-1, 2)
size # Down right if tile_mode in [Tiles.MODE.X_AXIS, Tiles.MODE.BOTH]
] else range(0, 1)
Global.TileMode.X_AXIS: )
return [ var y_range := (
Vector2(size.x, 0), # Right range(-1, 2)
Vector2(-size.x, 0), # Left if tile_mode in [Tiles.MODE.Y_AXIS, Tiles.MODE.BOTH]
] else range(0, 1)
Global.TileMode.Y_AXIS: )
return [ var positions := []
Vector2(0, size.y), # Down for r in y_range:
Vector2(0, -size.y), # Up for c in x_range:
] if not draw_center and r == 0 and c == 0:
_: continue
return [] var position: Vector2 = r * y_basis + c * x_basis
positions.append(position)
return positions

View file

@ -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()

View file

@ -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"]

View file

@ -105,6 +105,7 @@ func _setup_view_menu() -> void:
# Order as in Global.ViewMenu enum # Order as in Global.ViewMenu enum
var view_menu_items := [ var view_menu_items := [
"Tile Mode", "Tile Mode",
"Tile Mode Offsets",
"Greyscale View", "Greyscale View",
"Mirror View", "Mirror View",
"Show Grid", "Show Grid",
@ -117,6 +118,8 @@ func _setup_view_menu() -> void:
for item in view_menu_items: for item in view_menu_items:
if item == "Tile Mode": if item == "Tile Mode":
_setup_tile_mode_submenu(item) _setup_tile_mode_submenu(item)
elif item == "Tile Mode Offsets":
view_menu.add_item(item, i)
else: else:
view_menu.add_check_item(item, i) view_menu.add_check_item(item, i)
i += 1 i += 1
@ -150,11 +153,11 @@ func _setup_view_menu() -> void:
func _setup_tile_mode_submenu(item: String) -> void: func _setup_tile_mode_submenu(item: String) -> void:
tile_mode_submenu.set_name("tile_mode_submenu") tile_mode_submenu.set_name("tile_mode_submenu")
tile_mode_submenu.add_radio_check_item("None", Global.TileMode.NONE) tile_mode_submenu.add_radio_check_item("None", Tiles.MODE.NONE)
tile_mode_submenu.set_item_checked(Global.TileMode.NONE, true) tile_mode_submenu.set_item_checked(Tiles.MODE.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 Both Axis", Tiles.MODE.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 X Axis", Tiles.MODE.X_AXIS)
tile_mode_submenu.add_radio_check_item("Tiled In Y Axis", Global.TileMode.Y_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.hide_on_checkable_item_selection = false
tile_mode_submenu.connect("id_pressed", self, "_tile_mode_submenu_id_pressed") 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: func view_menu_id_pressed(id: int) -> void:
match id: match id:
Global.ViewMenu.TILE_MODE_OFFSETS:
_show_tile_mode_offsets_popup()
Global.ViewMenu.GREYSCALE_VIEW: Global.ViewMenu.GREYSCALE_VIEW:
_toggle_greyscale_view() _toggle_greyscale_view()
Global.ViewMenu.MIRROR_VIEW: Global.ViewMenu.MIRROR_VIEW:
@ -437,10 +442,15 @@ func view_menu_id_pressed(id: int) -> void:
Global.canvas.update() 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: func _tile_mode_submenu_id_pressed(id: int) -> void:
Global.current_project.tile_mode = id Global.current_project.tiles.mode = id
Global.transparent_checker.fit_rect(Global.current_project.get_tile_mode_rect()) Global.transparent_checker.fit_rect(Global.current_project.tiles.get_bounding_rect())
for i in Global.TileMode.values(): for i in Tiles.MODE.values():
tile_mode_submenu.set_item_checked(i, i == id) tile_mode_submenu.set_item_checked(i, i == id)
Global.canvas.tile_mode.update() Global.canvas.tile_mode.update()
Global.canvas.pixel_grid.update() Global.canvas.pixel_grid.update()

View file

@ -8,7 +8,7 @@ func _ready() -> void:
func update_rect() -> void: func update_rect() -> void:
rect_size = Global.current_project.size rect_size = Global.current_project.size
if self == Global.transparent_checker: 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.second_viewport.get_node("Viewport/TransparentChecker").update_rect()
Global.small_preview_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) material.set_shader_param("size", Global.checker_size)