1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-15 10:13:07 +00:00

Compare commits

..

22 commits

Author SHA1 Message Date
Piotr Kostrzewski bb3fd7f6c8
Merge 5bfe44a202 into 7f4c7a6bf1 2024-11-29 17:54:57 +02:00
Variable 7f4c7a6bf1
Grid patch (#1142)
* fix second grid not *shown* removed when first grid has default values.

* Make next added grid twice the previous size, and with a different color

* Formatting
2024-11-28 22:02:13 +02:00
HuanWuCode 41ea287df4
Update Import.gd (#1121) 2024-11-27 17:01:00 +02:00
Emmanouil Papadeas a3e372c5d8 [skip ci] Update CHANGELOG.md 2024-11-26 14:01:45 +02:00
Variable 6224d06428
Allow multiple Grids (#1122)
* Allow upto 10 grids

* Fixed more stuff

* fixed a bug

* formatting

* removed some left over stuff

* linting

* formatting and a bugfix
2024-11-25 15:57:13 +02:00
Vovkiv 6459151549
[skip ci] [linux] Enhancements for desktop file. (#1140)
Co-authored-by: volkov <volkovissocool@gmail.com>
2024-11-24 14:38:55 +02:00
Variable fe6efb0f1d
fixed recorder label not updating when project is changed (#1139) 2024-11-24 14:37:02 +02:00
Emmanouil Papadeas 8b1367494d Ensure that the swatches get deleted when the user removes all palettes 2024-11-23 17:54:28 +02:00
Emmanouil Papadeas 01b55aca07 Fix crash when using indexed mode without a palette 2024-11-23 14:17:41 +02:00
Emmanouil Papadeas 5f53a3eb7b Fix crash when Pixelorama starts without a palette 2024-11-23 14:17:27 +02:00
Emmanouil Papadeas 658477ed4b Sort system font names by alphabetical order 2024-11-23 01:21:22 +02:00
Emmanouil Papadeas 3fb8484ac5 Use Control + mouse wheel to increase the size of the text tool 2024-11-23 01:00:49 +02:00
Emmanouil Papadeas 0484b1012f Fix Delete button and fill selection mode of the bucket tool not working with indexed mode 2024-11-23 00:58:34 +02:00
Emmanouil Papadeas b87a8e2ab8 Fix cel copying not working with indexed mode 2024-11-22 21:00:38 +02:00
Emmanouil Papadeas e6c4a72158 Fix crash when using indexed mode and the palette has empty swatches between colors 2024-11-22 20:47:38 +02:00
Emmanouil Papadeas 1dcb696c35 Use texelFetch instead of texture for indexed mode shaders
Fixes various weird issues when palettes have empty slots, and removes unnecessary calculations.
2024-11-22 20:47:05 +02:00
Emmanouil Papadeas d580523c6e Revert "Slightly optimize IndexedToRGB.gdshader"
This reverts commit 7cf87ac142.
2024-11-22 18:29:27 +02:00
Emmanouil Papadeas 11da07b9ac Hide the color mode submenu when selecting an item 2024-11-22 18:02:36 +02:00
Emmanouil Papadeas 7cf87ac142 Slightly optimize IndexedToRGB.gdshader
Multiply the index by 255.0 only once, instead of dividing and multiplying it again
2024-11-22 18:01:29 +02:00
Emmanouil Papadeas bd7d3b19cc Add a crop_image boolean parameter to Palette.convert_to_image()
Fixes some issues with the Palettize effect where the output would be different if the palette size changed and empty swatches were added, even if the colors themselves stayed the same.
2024-11-22 17:56:39 +02:00
Emmanouil Papadeas 996a234d0d Call Palettes.current_palette_set_color() immediately when changing the color of a swatch 2024-11-22 15:26:30 +02:00
Emmanouil Papadeas 77f6bcf07b Fix Palette.convert_to_image() storing wrong colors in the image
Similar fix to #1108.
2024-11-22 15:07:16 +02:00
30 changed files with 522 additions and 204 deletions

View file

@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). All the dates are in YYYY-MM-DD format. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). All the dates are in YYYY-MM-DD format.
<br><br> <br><br>
## [v1.1] - Unreleased
This update has been brought to you by the contributions of:
Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind))
Built using Godot 4.3
### Added
- Indexed mode has finally been implemented! [#1136](https://github.com/Orama-Interactive/Pixelorama/pull/1136)
- Added a new text tool. Destructive only for now, meaning that once the text is confirmed, it cannot be changed later. [#1134](https://github.com/Orama-Interactive/Pixelorama/pull/1134)
- Implemented support for multiple grids. [#1122](https://github.com/Orama-Interactive/Pixelorama/pull/1122)
### Changed
- System font names are now sorted by alphabetical order.
### Fixed
- Fixed crash when Pixelorama starts without a palette.
- Undo/redo now works again when the cursor is hovering over the timeline.
- Palette swatches now get deleted when the user removes all palettes
- Fixed the Palettize effect and palette exporting to images storing slightly wrong color values. [77f6bcf](https://github.com/Orama-Interactive/Pixelorama/commit/77f6bcf07bd80bc042e478bb883d05900cebe436)
- Fixed some issues with the Palettize effect where the output would be different if the palette size changed and empty swatches were added, even if the colors themselves stayed the same. Initially fixed by [bd7d3b1](https://github.com/Orama-Interactive/Pixelorama/commit/bd7d3b19cc98804e9b99754153c4d553d2048ee3), but [1dcb696](https://github.com/Orama-Interactive/Pixelorama/commit/1dcb696c35121f8208bde699f87bb75deff99d13) is the proper fix.
- Fixed recorder label not updating when project is changed. [#1139](https://github.com/Orama-Interactive/Pixelorama/pull/1139)
## [v1.0.5] - 2024-11-18 ## [v1.0.5] - 2024-11-18
This update has been brought to you by the contributions of: This update has been brought to you by the contributions of:
Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind)) Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind))

View file

@ -3,16 +3,23 @@ Name=Pixelorama
GenericName=2D sprite editor GenericName=2D sprite editor
GenericName[el]=Επεξεργαστής δισδιάστατων εικόνων GenericName[el]=Επεξεργαστής δισδιάστατων εικόνων
GenericName[fr]=Éditeur de sprites 2D GenericName[fr]=Éditeur de sprites 2D
GenericName[ru]=2Д редактор спрайтов
GenericName[pt_BR]=Editor de sprites 2D GenericName[pt_BR]=Editor de sprites 2D
GenericName[uk]=2Д редактор спрайтів
GenericName[zh_CN]=2D GenericName[zh_CN]=2D
Comment=Create and edit static or animated 2D sprites Comment=Create and edit static or animated 2D sprites
Comment[el]=Δημιουργήστε και επεξεργαστείτε στατικές ή κινούμενες δισδιάστατες εικόνες Comment[el]=Δημιουργήστε και επεξεργαστείτε στατικές ή κινούμενες δισδιάστατες εικόνες
Comment[fr]=Créez et modifiez des sprites 2D statiques ou animées Comment[fr]=Créez et modifiez des sprites 2D statiques ou animées
Comment[ru]=Создавайте и редактируйте статичные и анимированные 2Д спрайты
Comment[pt_BR]=Crie e edite sprites 2D estáticos ou animados Comment[pt_BR]=Crie e edite sprites 2D estáticos ou animados
Comment[uk]=Створюйте та редагуйте статичні та анімовані 2Д спрайти
Comment[zh_CN]= 2D Comment[zh_CN]= 2D
Exec=pixelorama Exec=pixelorama
Icon=pixelorama Icon=pixelorama
Terminal=false Terminal=false
Type=Application Type=Application
Categories=Graphics;2DGraphics;RasterGraphics; Categories=Graphics;2DGraphics;RasterGraphics;
Keywords=pixel;retro;animation;art;image;2d;sprite;graphics;drawing;editor;
Keywords[ru]=pixel;retro;animation;art;image;2d;sprite;graphics;drawing;editor;пиксель;ретро;анимация;арт;изображение;2д;спрайт;графика;рисование;редактор;
Keywords[uk]=pixel;retro;animation;art;image;2d;sprite;graphics;drawing;editor;піксель;ретро;анімація;арт;зображення;2д;спрайт;графіка;малювання;редактор;
MimeType=image/pxo;image/png;image/bmp;image/vnd.radiance;image/jpeg;image/svg+xml;image/x-tga;image/webp; MimeType=image/pxo;image/png;image/bmp;image/vnd.radiance;image/jpeg;image/svg+xml;image/x-tga;image/webp;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 B

After

Width:  |  Height:  |  Size: 136 B

View file

@ -337,55 +337,8 @@ var default_height := 64 ## Found in Preferences. The default height of startup
var default_fill_color := Color(0, 0, 0, 0) var default_fill_color := Color(0, 0, 0, 0)
## Found in Preferences. The distance to the guide or grig below which cursor snapping activates. ## Found in Preferences. The distance to the guide or grig below which cursor snapping activates.
var snapping_distance := 32.0 var snapping_distance := 32.0
## Found in Preferences. The grid type defined by [enum GridTypes] enum. ## Contains dictionaries of individual grids.
var grid_type := GridTypes.CARTESIAN: var grids: Array[Grid] = []
set(value):
if value == grid_type:
return
grid_type = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The size of rectangular grid.
var grid_size := Vector2i(2, 2):
set(value):
if value == grid_size:
return
grid_size = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The size of isometric grid.
var isometric_grid_size := Vector2i(16, 8):
set(value):
if value == isometric_grid_size:
return
isometric_grid_size = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The grid offset from top-left corner of the canvas.
var grid_offset := Vector2i.ZERO:
set(value):
if value == grid_offset:
return
grid_offset = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. If [code]true[/code], The grid draws over the area extended by
## tile-mode as well.
var grid_draw_over_tile_mode := false:
set(value):
if value == grid_draw_over_tile_mode:
return
grid_draw_over_tile_mode = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The color of grid.
var grid_color := Color.BLACK:
set(value):
if value == grid_color:
return
grid_color = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The minimum zoom after which pixel grid gets drawn if enabled. ## Found in Preferences. The minimum zoom after which pixel grid gets drawn if enabled.
var pixel_grid_show_at_zoom := 1500.0: # percentage var pixel_grid_show_at_zoom := 1500.0: # percentage
set(value): set(value):
@ -677,6 +630,62 @@ var cel_button_scene: PackedScene = load("res://src/UI/Timeline/CelButton.tscn")
@onready var error_dialog: AcceptDialog = control.find_child("ErrorDialog") @onready var error_dialog: AcceptDialog = control.find_child("ErrorDialog")
class Grid:
var grid_type := GridTypes.CARTESIAN:
set(value):
if value == grid_type:
return
grid_type = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The size of rectangular grid.
var grid_size := Vector2i(2, 2):
set(value):
if value == grid_size:
return
grid_size = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The size of isometric grid.
var isometric_grid_size := Vector2i(16, 8):
set(value):
if value == isometric_grid_size:
return
isometric_grid_size = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The grid offset from top-left corner of the canvas.
var grid_offset := Vector2i.ZERO:
set(value):
if value == grid_offset:
return
grid_offset = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. If [code]true[/code], The grid draws over the area extended by
## tile-mode as well.
var grid_draw_over_tile_mode := false:
set(value):
if value == grid_draw_over_tile_mode:
return
grid_draw_over_tile_mode = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The color of grid.
var grid_color := Color.BLACK:
set(value):
if value == grid_color:
return
grid_color = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
func _init(properties := {}) -> void:
Global.grids.append(self)
for prop in properties.keys():
set(prop, properties[prop])
func _init() -> void: func _init() -> void:
# Load settings from the config file # Load settings from the config file
config_cache.load(CONFIG_PATH) config_cache.load(CONFIG_PATH)
@ -713,6 +722,8 @@ func _init() -> void:
func _ready() -> void: func _ready() -> void:
# Initialize Grid
Grid.new() # gets auto added to grids array
_initialize_keychain() _initialize_keychain()
default_width = config_cache.get_value("preferences", "default_width", default_width) default_width = config_cache.get_value("preferences", "default_width", default_width)
default_height = config_cache.get_value("preferences", "default_height", default_height) default_height = config_cache.get_value("preferences", "default_height", default_height)
@ -729,13 +740,27 @@ func _ready() -> void:
if get(pref) == null: if get(pref) == null:
continue continue
var value = config_cache.get_value("preferences", pref) var value = config_cache.get_value("preferences", pref)
set(pref, value) if pref == "grids":
if value:
update_grids(value)
else:
set(pref, value)
if OS.is_sandboxed(): if OS.is_sandboxed():
Global.use_native_file_dialogs = true Global.use_native_file_dialogs = true
await get_tree().process_frame await get_tree().process_frame
project_switched.emit() project_switched.emit()
func update_grids(grids_data: Dictionary):
# Remove old grids
grids.clear()
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
# ADD new ones
for grid_idx in grids_data.size():
Grid.new(grids_data[grid_idx]) # gets auto added to grids array
func _initialize_keychain() -> void: func _initialize_keychain() -> void:
Keychain.config_file = config_cache Keychain.config_file = config_cache
Keychain.actions = { Keychain.actions = {
@ -1066,7 +1091,9 @@ func get_available_font_names() -> PackedStringArray:
if font_name in font_names: if font_name in font_names:
continue continue
font_names.append(font_name) font_names.append(font_name)
for system_font_name in OS.get_system_fonts(): var system_fonts := OS.get_system_fonts()
system_fonts.sort()
for system_font_name in system_fonts:
if system_font_name in font_names: if system_font_name in font_names:
continue continue
font_names.append(system_font_name) font_names.append(system_font_name)

View file

@ -90,9 +90,9 @@ func get_brush_files_from_directory(directory: String): # -> Array
func add_randomised_brush(fpaths: Array, tooltip_name: String) -> void: func add_randomised_brush(fpaths: Array, tooltip_name: String) -> void:
# Attempt to load the images from the file paths. # Attempt to load the images from the file paths.
var loaded_images: Array = [] var loaded_images: Array = []
for filen in fpaths: for file in fpaths:
var image := Image.new() var image := Image.new()
var err := image.load(filen) var err := image.load(file)
if err == OK: if err == OK:
image.convert(Image.FORMAT_RGBA8) image.convert(Image.FORMAT_RGBA8)
loaded_images.append(image) loaded_images.append(image)

View file

@ -36,9 +36,10 @@ func does_palette_exist(palette_name: String) -> bool:
func select_palette(palette_name: String) -> void: func select_palette(palette_name: String) -> void:
current_palette = palettes.get(palette_name) current_palette = palettes.get(palette_name, null)
_clear_selected_colors() _clear_selected_colors()
Global.config_cache.set_value("data", "last_palette", current_palette.name) if is_instance_valid(current_palette):
Global.config_cache.set_value("data", "last_palette", current_palette.name)
palette_selected.emit(palette_name) palette_selected.emit(palette_name)
@ -224,6 +225,7 @@ func current_palete_delete(permanent := true) -> void:
select_palette(palettes.keys()[0]) select_palette(palettes.keys()[0])
else: else:
current_palette = null current_palette = null
select_palette("")
func current_palette_add_color(mouse_button: int, start_index := 0) -> void: func current_palette_add_color(mouse_button: int, start_index := 0) -> void:

View file

@ -8,7 +8,7 @@ var image: ImageExtended:
set = image_changed set = image_changed
func _init(_image: ImageExtended, _opacity := 1.0) -> void: func _init(_image := ImageExtended.new(), _opacity := 1.0) -> void:
image_texture = ImageTexture.new() image_texture = ImageTexture.new()
image = _image # Set image and call setter image = _image # Set image and call setter
opacity = _opacity opacity = _opacity
@ -20,7 +20,7 @@ func image_changed(value: ImageExtended) -> void:
image_texture.set_image(image) image_texture.set_image(image)
func get_content(): func get_content() -> ImageExtended:
return image return image
@ -34,17 +34,19 @@ func set_content(content, texture: ImageTexture = null) -> void:
image_texture.update(image) image_texture.update(image)
func create_empty_content(): func create_empty_content() -> ImageExtended:
var empty_image := Image.create( var empty := Image.create(image.get_width(), image.get_height(), false, image.get_format())
image.get_size().x, image.get_size().y, false, Image.FORMAT_RGBA8 var new_image := ImageExtended.new()
) new_image.copy_from_custom(empty, image.is_indexed)
return empty_image return new_image
func copy_content(): func copy_content() -> ImageExtended:
var copy_image := Image.create_from_data( var tmp_image := Image.create_from_data(
image.get_width(), image.get_height(), false, Image.FORMAT_RGBA8, image.get_data() image.get_width(), image.get_height(), false, image.get_format(), image.get_data()
) )
var copy_image := ImageExtended.new()
copy_image.copy_from_custom(tmp_image, image.is_indexed)
return copy_image return copy_image

View file

@ -74,17 +74,20 @@ func select_palette(_name: String, convert_to_rgb := true) -> void:
## Updates [member palette] to contain the colors of [member current_palette]. ## Updates [member palette] to contain the colors of [member current_palette].
func update_palette() -> void: func update_palette() -> void:
if palette.size() != current_palette.colors.size(): if not is_instance_valid(current_palette):
palette.resize(current_palette.colors.size()) return
if palette.size() != current_palette.colors_max:
palette.resize(current_palette.colors_max)
palette.fill(TRANSPARENT)
for i in current_palette.colors: for i in current_palette.colors:
palette[i] = current_palette.colors[i].color palette[i] = current_palette.colors[i].color
## Displays the actual RGBA values of each pixel in the image from indexed mode. ## Displays the actual RGBA values of each pixel in the image from indexed mode.
func convert_indexed_to_rgb() -> void: func convert_indexed_to_rgb() -> void:
if not is_indexed: if not is_indexed or not is_instance_valid(current_palette):
return return
var palette_image := Palettes.current_palette.convert_to_image() var palette_image := current_palette.convert_to_image(false)
var palette_texture := ImageTexture.create_from_image(palette_image) var palette_texture := ImageTexture.create_from_image(palette_image)
var shader_image_effect := ShaderImageEffect.new() var shader_image_effect := ShaderImageEffect.new()
var indices_texture := ImageTexture.create_from_image(indices_image) var indices_texture := ImageTexture.create_from_image(indices_image)
@ -96,9 +99,9 @@ func convert_indexed_to_rgb() -> void:
## Automatically maps each color of the image's pixel to the closest color of the palette, ## Automatically maps each color of the image's pixel to the closest color of the palette,
## by finding the palette color's index and storing it in [member indices_image]. ## by finding the palette color's index and storing it in [member indices_image].
func convert_rgb_to_indexed() -> void: func convert_rgb_to_indexed() -> void:
if not is_indexed: if not is_indexed or not is_instance_valid(current_palette):
return return
var palette_image := Palettes.current_palette.convert_to_image() var palette_image := current_palette.convert_to_image(false)
var palette_texture := ImageTexture.create_from_image(palette_image) var palette_texture := ImageTexture.create_from_image(palette_image)
var params := { var params := {
"palette_texture": palette_texture, "rgb_texture": ImageTexture.create_from_image(self) "palette_texture": palette_texture, "rgb_texture": ImageTexture.create_from_image(self)

View file

@ -25,7 +25,7 @@ var colors_max := 0
class PaletteColor: class PaletteColor:
var color := Color.TRANSPARENT var color := Color(0, 0, 0, 0)
var index := -1 var index := -1
func _init(init_color := Color.BLACK, init_index := -1) -> void: func _init(init_color := Color.BLACK, init_index := -1) -> void:
@ -358,9 +358,11 @@ static func strip_unvalid_characters(string_to_strip: String) -> String:
return regex.sub(string_to_strip, "", true) return regex.sub(string_to_strip, "", true)
func convert_to_image() -> Image: func convert_to_image(crop_image := true) -> Image:
var image := Image.create(colors_max, 1, false, Image.FORMAT_RGBA8) var image := Image.create(colors_max, 1, false, Image.FORMAT_RGBA8)
for i in colors_max: for i in colors_max:
if colors.has(i): if colors.has(i):
image.set_pixel(i, 0, colors[i].color) image.set_pixel(i, 0, Color(colors[i].color.to_html()))
if crop_image:
image.copy_from(image.get_region(image.get_used_rect()))
return image return image

View file

@ -23,10 +23,6 @@ func _ready() -> void:
func set_palette(new_palette: Palette) -> void: func set_palette(new_palette: Palette) -> void:
# Only display valid palette objects
if not new_palette:
return
current_palette = new_palette current_palette = new_palette
grid_window_origin = Vector2.ZERO grid_window_origin = Vector2.ZERO
@ -87,6 +83,8 @@ func scroll_palette(origin: Vector2i) -> void:
## Called when the color changes, either the left or the right, determined by [param mouse_button]. ## Called when the color changes, either the left or the right, determined by [param mouse_button].
## If current palette has [param target_color] as a [Color], then select it. ## If current palette has [param target_color] as a [Color], then select it.
func find_and_select_color(target_color: Color, mouse_button: int) -> void: func find_and_select_color(target_color: Color, mouse_button: int) -> void:
if not is_instance_valid(current_palette):
return
var old_index := Palettes.current_palette_get_selected_color_index(mouse_button) var old_index := Palettes.current_palette_get_selected_color_index(mouse_button)
for color_ind in swatches.size(): for color_ind in swatches.size():
if ( if (
@ -115,6 +113,8 @@ func find_and_select_color(target_color: Color, mouse_button: int) -> void:
## Displays a left/right highlight over a swatch ## Displays a left/right highlight over a swatch
func select_swatch(mouse_button: int, palette_index: int, old_palette_index: int) -> void: func select_swatch(mouse_button: int, palette_index: int, old_palette_index: int) -> void:
if not is_instance_valid(current_palette):
return
var index := convert_palette_index_to_grid_index(palette_index) var index := convert_palette_index_to_grid_index(palette_index)
var old_index := convert_palette_index_to_grid_index(old_palette_index) var old_index := convert_palette_index_to_grid_index(old_palette_index)
if index >= 0 and index < swatches.size(): if index >= 0 and index < swatches.size():
@ -159,16 +159,17 @@ func convert_palette_index_to_grid_index(palette_index: int) -> int:
func resize_grid(new_rect_size: Vector2) -> void: func resize_grid(new_rect_size: Vector2) -> void:
if not is_instance_valid(current_palette):
return
var grid_x: int = ( var grid_x: int = (
new_rect_size.x / (swatch_size.x + get("theme_override_constants/h_separation")) new_rect_size.x / (swatch_size.x + get("theme_override_constants/h_separation"))
) )
var grid_y: int = ( var grid_y: int = (
new_rect_size.y / (swatch_size.y + get("theme_override_constants/v_separation")) new_rect_size.y / (swatch_size.y + get("theme_override_constants/v_separation"))
) )
grid_size.x = mini(grid_x, current_palette.width) if is_instance_valid(current_palette):
grid_size.y = mini(grid_y, current_palette.height) grid_size.x = mini(grid_x, current_palette.width)
grid_size.y = mini(grid_y, current_palette.height)
else:
grid_size = Vector2i.ZERO
setup_swatches() setup_swatches()
draw_palette() draw_palette()

View file

@ -89,16 +89,16 @@ func select_palette(palette_name: String) -> void:
var palette_id = palettes_path_id.get(palette_name) var palette_id = palettes_path_id.get(palette_name)
if palette_id != null: if palette_id != null:
palette_select.selected = palette_id palette_select.selected = palette_id
palette_grid.set_palette(Palettes.current_palette) palette_grid.set_palette(Palettes.current_palette)
palette_scroll.resize_grid() palette_scroll.resize_grid()
palette_scroll.set_sliders(Palettes.current_palette, palette_grid.grid_window_origin) palette_scroll.set_sliders(Palettes.current_palette, palette_grid.grid_window_origin)
var left_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_LEFT) var left_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_LEFT)
var right_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_RIGHT) var right_selected := Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_RIGHT)
palette_grid.select_swatch(MOUSE_BUTTON_LEFT, left_selected, left_selected) palette_grid.select_swatch(MOUSE_BUTTON_LEFT, left_selected, left_selected)
palette_grid.select_swatch(MOUSE_BUTTON_RIGHT, right_selected, right_selected) palette_grid.select_swatch(MOUSE_BUTTON_RIGHT, right_selected, right_selected)
toggle_add_delete_buttons() toggle_add_delete_buttons()
## Select and display current palette ## Select and display current palette
@ -115,6 +115,8 @@ func redraw_current_palette() -> void:
func toggle_add_delete_buttons() -> void: func toggle_add_delete_buttons() -> void:
if not is_instance_valid(Palettes.current_palette):
return
add_color_button.disabled = Palettes.current_palette.is_full() add_color_button.disabled = Palettes.current_palette.is_full()
if add_color_button.disabled: if add_color_button.disabled:
add_color_button.mouse_default_cursor_shape = CURSOR_FORBIDDEN add_color_button.mouse_default_cursor_shape = CURSOR_FORBIDDEN
@ -252,6 +254,7 @@ func _on_ColorPicker_color_changed(color: Color) -> void:
== Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_RIGHT) == Palettes.current_palette_get_selected_color_index(MOUSE_BUTTON_RIGHT)
): ):
Tools.assign_color(color, MOUSE_BUTTON_RIGHT) Tools.assign_color(color, MOUSE_BUTTON_RIGHT)
Palettes.current_palette_set_color(edited_swatch_index, edited_swatch_color)
## Saves edited swatch to palette file when color selection dialog is closed ## Saves edited swatch to palette file when color selection dialog is closed

View file

@ -4,9 +4,9 @@ var scroll := Vector2i.ZERO
var drag_started := false var drag_started := false
var drag_start_position := Vector2i.ZERO var drag_start_position := Vector2i.ZERO
@onready var h_slider := %HScrollBar @onready var h_slider := %HScrollBar as HScrollBar
@onready var v_slider := %VScrollBar @onready var v_slider := %VScrollBar as VScrollBar
@onready var palette_grid := %PaletteGrid @onready var palette_grid := %PaletteGrid as PaletteGrid
func _input(event: InputEvent) -> void: func _input(event: InputEvent) -> void:
@ -17,16 +17,21 @@ func _input(event: InputEvent) -> void:
func set_sliders(palette: Palette, origin: Vector2i) -> void: func set_sliders(palette: Palette, origin: Vector2i) -> void:
if not is_instance_valid(palette): if is_instance_valid(palette):
return h_slider.value = origin.x
h_slider.value = origin.x h_slider.max_value = palette.width
h_slider.max_value = palette.width h_slider.page = palette_grid.grid_size.x
h_slider.page = palette_grid.grid_size.x v_slider.value = origin.y
v_slider.max_value = palette.height
v_slider.page = palette_grid.grid_size.y
else:
h_slider.value = 0
h_slider.max_value = 0
h_slider.page = 0
v_slider.value = 0
v_slider.max_value = 0
v_slider.page = 0
h_slider.visible = false if h_slider.max_value <= palette_grid.grid_size.x else true h_slider.visible = false if h_slider.max_value <= palette_grid.grid_size.x else true
v_slider.value = origin.y
v_slider.max_value = palette.height
v_slider.page = palette_grid.grid_size.y
v_slider.visible = false if v_slider.max_value <= palette_grid.grid_size.y else true v_slider.visible = false if v_slider.max_value <= palette_grid.grid_size.y else true
@ -58,7 +63,7 @@ func _on_PaletteGrid_gui_input(event: InputEvent) -> void:
drag_started = true drag_started = true
# Keeps position where the dragging started # Keeps position where the dragging started
drag_start_position = ( drag_start_position = (
event.position + Vector2i(h_slider.value, v_slider.value) * palette_grid.swatch_size event.position + Vector2(h_slider.value, v_slider.value) * palette_grid.swatch_size
) )
if event is InputEventMouseMotion and drag_started: if event is InputEventMouseMotion and drag_started:

View file

@ -0,0 +1,200 @@
extends GridContainer
# We should use pre defined initial grid colors instead of random colors
const INITIAL_GRID_COLORS := [
Color.BLACK,
Color.WHITE,
Color.YELLOW,
Color.GREEN,
Color.BLUE,
Color.GRAY,
Color.ORANGE,
Color.PINK,
Color.SIENNA,
Color.CORAL,
]
var grid_preferences: Array[GridPreference] = [
GridPreference.new("grid_type", "GridType", "selected", Global.GridTypes.CARTESIAN),
GridPreference.new("grid_size", "GridSizeValue", "value", Vector2i(2, 2)),
GridPreference.new("isometric_grid_size", "IsometricGridSizeValue", "value", Vector2i(16, 8)),
GridPreference.new("grid_offset", "GridOffsetValue", "value", Vector2i.ZERO),
GridPreference.new("grid_draw_over_tile_mode", "GridDrawOverTileMode", "button_pressed", false),
GridPreference.new("grid_color", "GridColor", "color", Color.BLACK),
]
var grid_selected: int = 0:
set(key):
grid_selected = key
for child: BaseButton in grids_select_container.get_children():
if child.get_index() == grid_selected:
child.self_modulate = Color.WHITE
else:
child.self_modulate = Color.DIM_GRAY
var grids: Dictionary = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
if grids.has(key):
update_pref_ui(grids[key])
@onready var grids_select_container: HFlowContainer = $GridsSelectContainer
class GridPreference:
var prop_name: String
var node_path: String
var value_type: String
var default_value
func _init(
_prop_name: String,
_node_path: String,
_value_type: String,
_default_value = null,
_require_restart := false
) -> void:
prop_name = _prop_name
node_path = _node_path
value_type = _value_type
if _default_value != null:
default_value = _default_value
func _ready() -> void:
var grids = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
Global.config_cache.set_value("preferences", "grids", grids)
$GridsCount.value = grids.size()
if grids.size() == 1:
add_remove_select_button(0)
for pref in grid_preferences:
if not has_node(pref.node_path):
continue
var node := get_node(pref.node_path)
var restore_default_button := RestoreDefaultButton.new()
restore_default_button.pressed.connect(
_on_grid_pref_value_changed.bind(pref.default_value, pref, restore_default_button)
)
restore_default_button.setting_name = pref.prop_name
restore_default_button.value_type = pref.value_type
restore_default_button.default_value = pref.default_value
restore_default_button.node = node
var node_position := node.get_index()
node.get_parent().add_child(restore_default_button)
node.get_parent().move_child(restore_default_button, node_position)
match pref.value_type:
"button_pressed":
node.toggled.connect(_on_grid_pref_value_changed.bind(pref, restore_default_button))
"value":
node.value_changed.connect(
_on_grid_pref_value_changed.bind(pref, restore_default_button)
)
"color":
node.get_picker().presets_visible = false
node.color_changed.connect(
_on_grid_pref_value_changed.bind(pref, restore_default_button)
)
"selected":
node.item_selected.connect(
_on_grid_pref_value_changed.bind(pref, restore_default_button)
)
grid_selected = 0
func _on_grid_pref_value_changed(value, pref: GridPreference, button: RestoreDefaultButton) -> void:
var grids: Dictionary = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
if grids.has(grid_selected): # Failsafe (Always true)
var grid_info: Dictionary = grids[grid_selected]
var prop := pref.prop_name
grid_info[prop] = value
grids[grid_selected] = grid_info
Global.update_grids(grids)
var default_value = pref.default_value
var disable: bool = Global.grids[grid_selected].get(prop) == default_value
if typeof(value) == TYPE_COLOR:
disable = value.is_equal_approx(default_value)
disable_restore_default_button(button, disable)
Global.config_cache.set_value("preferences", "grids", grids)
func _on_grids_count_value_changed(value: float) -> void:
var new_grids: Dictionary = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
var last_grid_idx = int(value - 1)
if last_grid_idx >= grids_select_container.get_child_count():
# Add missing grids
for key in range(grids_select_container.get_child_count(), value):
if not new_grids.has(key):
var new_grid := create_default_properties()
if new_grids.has(key - 1): # Failsafe
var last_grid = new_grids[key - 1]
# This small bit of code is there to make ui look a little neater
# Reasons:
# - Usually user intends to make the next grid twice the size.
# - Having all grids being same size initially may cause confusion for some
# users when they try to change color of a middle grid not seeing it's changing
# (due to being covered by grids above it).
if (
new_grid.has("grid_size")
and new_grid.has("isometric_grid_size")
and new_grid.has("grid_color")
):
new_grid["grid_size"] = last_grid["grid_size"] * 2
new_grid["isometric_grid_size"] = last_grid["isometric_grid_size"] * 2
if key < INITIAL_GRID_COLORS.size():
new_grid["grid_color"] = INITIAL_GRID_COLORS[key]
new_grids[key] = new_grid
add_remove_select_button(key)
else:
# Remove extra grids
for key: int in range(value, new_grids.size()):
new_grids.erase(key)
add_remove_select_button(key, true)
grid_selected = min(grid_selected, last_grid_idx)
Global.update_grids(new_grids)
Global.config_cache.set_value("preferences", "grids", new_grids)
func create_default_properties() -> Dictionary:
var grid_info = {}
for pref in grid_preferences:
grid_info[pref.prop_name] = pref.default_value
return grid_info
func disable_restore_default_button(button: RestoreDefaultButton, disable: bool) -> void:
button.disabled = disable
if disable:
button.mouse_default_cursor_shape = Control.CURSOR_ARROW
button.tooltip_text = ""
else:
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
button.tooltip_text = "Restore default value"
func add_remove_select_button(grid_idx: int, remove := false):
if not remove:
var select_button = Button.new()
select_button.text = str(grid_idx)
grids_select_container.add_child(select_button)
select_button.pressed.connect(func(): grid_selected = grid_idx)
else:
if grid_idx < grids_select_container.get_child_count():
grids_select_container.get_child(grid_idx).queue_free()
func update_pref_ui(grid_data: Dictionary):
for pref in grid_preferences:
var key = pref.prop_name
if grid_data.has(key):
var node := get_node(pref.node_path)
node.set(pref.value_type, grid_data[key])
if pref.value_type == "color":
# the signal doesn't seem to be emitted automatically
node.color_changed.emit(grid_data[key])

View file

@ -94,21 +94,6 @@ var preferences: Array[Preference] = [
Preference.new("smooth_zoom", "Canvas/ZoomOptions/SmoothZoom", "button_pressed", true), Preference.new("smooth_zoom", "Canvas/ZoomOptions/SmoothZoom", "button_pressed", true),
Preference.new("integer_zoom", "Canvas/ZoomOptions/IntegerZoom", "button_pressed", false), Preference.new("integer_zoom", "Canvas/ZoomOptions/IntegerZoom", "button_pressed", false),
Preference.new("snapping_distance", "Canvas/SnappingOptions/DistanceValue", "value", 32.0), Preference.new("snapping_distance", "Canvas/SnappingOptions/DistanceValue", "value", 32.0),
Preference.new(
"grid_type", "Canvas/GridOptions/GridType", "selected", Global.GridTypes.CARTESIAN
),
Preference.new("grid_size", "Canvas/GridOptions/GridSizeValue", "value", Vector2i(2, 2)),
Preference.new(
"isometric_grid_size", "Canvas/GridOptions/IsometricGridSizeValue", "value", Vector2i(16, 8)
),
Preference.new("grid_offset", "Canvas/GridOptions/GridOffsetValue", "value", Vector2i.ZERO),
Preference.new(
"grid_draw_over_tile_mode",
"Canvas/GridOptions/GridDrawOverTileMode",
"button_pressed",
false
),
Preference.new("grid_color", "Canvas/GridOptions/GridColor", "color", Color.BLACK),
Preference.new( Preference.new(
"pixel_grid_show_at_zoom", "Canvas/PixelGridOptions/ShowAtZoom", "value", 1500.0 "pixel_grid_show_at_zoom", "Canvas/PixelGridOptions/ShowAtZoom", "value", 1500.0
), ),

View file

@ -1,8 +1,10 @@
[gd_scene load_steps=9 format=3 uid="uid://b3hkjj3s6pe4x"] [gd_scene load_steps=11 format=3 uid="uid://b3hkjj3s6pe4x"]
[ext_resource type="Script" path="res://src/Preferences/PreferencesDialog.gd" id="1"] [ext_resource type="Script" path="res://src/Preferences/PreferencesDialog.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://bq7ibhm0txl5p" path="res://addons/keychain/ShortcutEdit.tscn" id="3"] [ext_resource type="PackedScene" uid="uid://bq7ibhm0txl5p" path="res://addons/keychain/ShortcutEdit.tscn" id="3"]
[ext_resource type="Script" path="res://src/Preferences/ThemesPreferences.gd" id="3_nvl8k"] [ext_resource type="Script" path="res://src/Preferences/ThemesPreferences.gd" id="3_nvl8k"]
[ext_resource type="Script" path="res://src/Preferences/GridPreferences.gd" id="4_76iff"]
[ext_resource type="PackedScene" uid="uid://yjhp0ssng2mp" path="res://src/UI/Nodes/ValueSlider.tscn" id="5_rlmsh"]
[ext_resource type="PackedScene" path="res://src/UI/Nodes/ValueSliderV2.tscn" id="7"] [ext_resource type="PackedScene" path="res://src/UI/Nodes/ValueSliderV2.tscn" id="7"]
[ext_resource type="Script" path="res://src/Preferences/ExtensionsPreferences.gd" id="7_8ume5"] [ext_resource type="Script" path="res://src/Preferences/ExtensionsPreferences.gd" id="7_8ume5"]
[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="8"] [ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="8"]
@ -482,6 +484,30 @@ layout_mode = 2
theme_override_constants/h_separation = 4 theme_override_constants/h_separation = 4
theme_override_constants/v_separation = 4 theme_override_constants/v_separation = 4
columns = 3 columns = 3
script = ExtResource("4_76iff")
[node name="GridsCountLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
text = "Grids Visible:"
[node name="Spacer" type="Control" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
[node name="GridsCount" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions" instance=ExtResource("5_rlmsh")]
layout_mode = 2
min_value = 1.0
max_value = 10.0
value = 1.0
[node name="GridsSelectLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
text = "Editing Grid:"
[node name="Spacer2" type="Control" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
[node name="GridsSelectContainer" type="HFlowContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
[node name="GridTypeLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"] [node name="GridTypeLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2 layout_mode = 2
@ -1478,6 +1504,7 @@ dialog_text = "Are you sure you want to reset the selected options? There will b
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Language/System Language" to="." method="_on_language_pressed" binds= [1]] [connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Language/System Language" to="." method="_on_language_pressed" binds= [1]]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/ShrinkContainer/ShrinkApplyButton" to="." method="_on_shrink_apply_button_pressed"] [connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/ShrinkContainer/ShrinkApplyButton" to="." method="_on_shrink_apply_button_pressed"]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/FontSizeContainer/FontSizeApplyButton" to="." method="_on_font_size_apply_button_pressed"] [connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/FontSizeContainer/FontSizeApplyButton" to="." method="_on_font_size_apply_button_pressed"]
[connection signal="value_changed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions/GridsCount" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions" method="_on_grids_count_value_changed"]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/ExtensionsHeader/Explore" to="Store" method="_on_explore_pressed"] [connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/ExtensionsHeader/Explore" to="Store" method="_on_explore_pressed"]
[connection signal="empty_clicked" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_empty_clicked"] [connection signal="empty_clicked" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_empty_clicked"]
[connection signal="item_selected" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_item_selected"] [connection signal="item_selected" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_item_selected"]

View file

@ -14,7 +14,7 @@ vec4 swap_color(vec4 color) {
int n_of_colors = textureSize(palette_texture, 0).x; int n_of_colors = textureSize(palette_texture, 0).x;
int color_index = find_index(color, n_of_colors, palette_texture); int color_index = find_index(color, n_of_colors, palette_texture);
return texture(palette_texture, vec2(float(color_index) / float(n_of_colors), 0.0)); return texelFetch(palette_texture, ivec2(color_index, 0), 0);
} }
void fragment() { void fragment() {

View file

@ -2,8 +2,7 @@ int find_index(vec4 color, int n_of_colors, sampler2D palette_texture) {
int color_index = 0; int color_index = 0;
float smaller_distance = distance(color, texture(palette_texture, vec2(0.0))); float smaller_distance = distance(color, texture(palette_texture, vec2(0.0)));
for (int i = 0; i <= n_of_colors; i++) { for (int i = 0; i <= n_of_colors; i++) {
vec2 uv = vec2(float(i) / float(n_of_colors), 0.0); vec4 palette_color = texelFetch(palette_texture, ivec2(i, 0), 0);
vec4 palette_color = texture(palette_texture, uv);
float dist = distance(color, palette_color); float dist = distance(color, palette_color);
if (dist < smaller_distance) { if (dist < smaller_distance) {
smaller_distance = dist; smaller_distance = dist;

View file

@ -7,16 +7,16 @@ uniform sampler2D palette_texture : filter_nearest;
uniform sampler2D indices_texture : filter_nearest; uniform sampler2D indices_texture : filter_nearest;
void fragment() { void fragment() {
float index = texture(indices_texture, UV).r; float index = texture(indices_texture, UV).r * 255.0;
if (index <= EPSILON) { // If index is zero, make it transparent if (index <= EPSILON) { // If index is zero, make it transparent
COLOR = vec4(0.0); COLOR = vec4(0.0);
} }
else { else {
float n_of_colors = float(textureSize(palette_texture, 0).x); float n_of_colors = float(textureSize(palette_texture, 0).x);
index -= 1.0 / 255.0; index -= 1.0;
float index_normalized = ((index * 255.0)) / n_of_colors; float index_normalized = index / n_of_colors;
if (index_normalized + EPSILON < 1.0) { if (index < n_of_colors) {
COLOR = texture(palette_texture, vec2(index_normalized + EPSILON, 0.0)); COLOR = texelFetch(palette_texture, ivec2(int(index), 0), 0);
} }
else { else {
// If index is bigger than the size of the palette, make it transparent. // If index is bigger than the size of the palette, make it transparent.

View file

@ -8,7 +8,7 @@ uniform sampler2D palette_texture : filter_nearest;
void fragment() { void fragment() {
vec4 color = texture(rgb_texture, UV); vec4 color = texture(rgb_texture, UV);
if (color.a <= 0.01) { if (color.a <= 0.0001) {
COLOR.r = 0.0; COLOR.r = 0.0;
} }
else { else {

View file

@ -159,16 +159,17 @@ func draw_move(pos: Vector2i) -> void:
else: else:
pos.x = _start_pos.x pos.x = _start_pos.x
if Input.is_action_pressed("transform_snap_grid"): if Input.is_action_pressed("transform_snap_grid"):
_offset = _offset.snapped(Global.grid_size) _offset = _offset.snapped(Global.grids[0].grid_size)
var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position
selection_node.big_bounding_rectangle.position = prev_pos.snapped(Global.grid_size) selection_node.big_bounding_rectangle.position = prev_pos.snapped(Global.grids[0].grid_size)
selection_node.marching_ants_outline.offset += Vector2( selection_node.marching_ants_outline.offset += Vector2(
selection_node.big_bounding_rectangle.position - prev_pos selection_node.big_bounding_rectangle.position - prev_pos
) )
pos = pos.snapped(Global.grid_size) pos = pos.snapped(Global.grids[0].grid_size)
var grid_offset := Global.grid_offset var grid_offset := Global.grids[0].grid_offset
grid_offset = Vector2i( grid_offset = Vector2i(
fmod(grid_offset.x, Global.grid_size.x), fmod(grid_offset.y, Global.grid_size.y) fmod(grid_offset.x, Global.grids[0].grid_size.x),
fmod(grid_offset.y, Global.grids[0].grid_size.y)
) )
pos += grid_offset pos += grid_offset

View file

@ -129,19 +129,20 @@ func draw_preview() -> void:
func snap_position(pos: Vector2) -> Vector2: func snap_position(pos: Vector2) -> Vector2:
var snapping_distance := Global.snapping_distance / Global.camera.zoom.x var snapping_distance := Global.snapping_distance / Global.camera.zoom.x
if Global.snap_to_rectangular_grid_boundary: if Global.snap_to_rectangular_grid_boundary:
var grid_pos := pos.snapped(Global.grid_size) var grid_pos := pos.snapped(Global.grids[0].grid_size)
grid_pos += Vector2(Global.grid_offset) grid_pos += Vector2(Global.grids[0].grid_offset)
# keeping grid_pos as is would have been fine but this adds extra accuracy as to # keeping grid_pos as is would have been fine but this adds extra accuracy as to
# which snap point (from the list below) is closest to mouse and occupy THAT point # which snap point (from the list below) is closest to mouse and occupy THAT point
var t_l := grid_pos + Vector2(-Global.grid_size.x, -Global.grid_size.y) # t_l is for "top left" and so on
var t_c := grid_pos + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on var t_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var t_r := grid_pos + Vector2(Global.grid_size.x, -Global.grid_size.y) var t_c := grid_pos + Vector2(0, -Global.grids[0].grid_size.y)
var m_l := grid_pos + Vector2(-Global.grid_size.x, 0) var t_r := grid_pos + Vector2(Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var m_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, 0)
var m_c := grid_pos var m_c := grid_pos
var m_r := grid_pos + Vector2(Global.grid_size.x, 0) var m_r := grid_pos + Vector2(Global.grids[0].grid_size.x, 0)
var b_l := grid_pos + Vector2(-Global.grid_size.x, Global.grid_size.y) var b_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, Global.grids[0].grid_size.y)
var b_c := grid_pos + Vector2(0, Global.grid_size.y) var b_c := grid_pos + Vector2(0, Global.grids[0].grid_size.y)
var b_r := grid_pos + Vector2(Global.grid_size) var b_r := grid_pos + Vector2(Global.grids[0].grid_size)
var vec_arr: PackedVector2Array = [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r] var vec_arr: PackedVector2Array = [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r]
for vec in vec_arr: for vec in vec_arr:
if vec.distance_to(pos) < grid_pos.distance_to(pos): if vec.distance_to(pos) < grid_pos.distance_to(pos):
@ -152,19 +153,22 @@ func snap_position(pos: Vector2) -> Vector2:
pos = grid_point.floor() pos = grid_point.floor()
if Global.snap_to_rectangular_grid_center: if Global.snap_to_rectangular_grid_center:
var grid_center := pos.snapped(Global.grid_size) + Vector2(Global.grid_size / 2) var grid_center := (
grid_center += Vector2(Global.grid_offset) pos.snapped(Global.grids[0].grid_size) + Vector2(Global.grids[0].grid_size / 2)
)
grid_center += Vector2(Global.grids[0].grid_offset)
# keeping grid_center as is would have been fine but this adds extra accuracy as to # keeping grid_center as is would have been fine but this adds extra accuracy as to
# which snap point (from the list below) is closest to mouse and occupy THAT point # which snap point (from the list below) is closest to mouse and occupy THAT point
var t_l := grid_center + Vector2(-Global.grid_size.x, -Global.grid_size.y) # t_l is for "top left" and so on
var t_c := grid_center + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on var t_l := grid_center + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var t_r := grid_center + Vector2(Global.grid_size.x, -Global.grid_size.y) var t_c := grid_center + Vector2(0, -Global.grids[0].grid_size.y)
var m_l := grid_center + Vector2(-Global.grid_size.x, 0) var t_r := grid_center + Vector2(Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var m_l := grid_center + Vector2(-Global.grids[0].grid_size.x, 0)
var m_c := grid_center var m_c := grid_center
var m_r := grid_center + Vector2(Global.grid_size.x, 0) var m_r := grid_center + Vector2(Global.grids[0].grid_size.x, 0)
var b_l := grid_center + Vector2(-Global.grid_size.x, Global.grid_size.y) var b_l := grid_center + Vector2(-Global.grids[0].grid_size.x, Global.grids[0].grid_size.y)
var b_c := grid_center + Vector2(0, Global.grid_size.y) var b_c := grid_center + Vector2(0, Global.grids[0].grid_size.y)
var b_r := grid_center + Vector2(Global.grid_size) var b_r := grid_center + Vector2(Global.grids[0].grid_size)
var vec_arr := [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r] var vec_arr := [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r]
for vec in vec_arr: for vec in vec_arr:
if vec.distance_to(pos) < grid_center.distance_to(pos): if vec.distance_to(pos) < grid_center.distance_to(pos):

View file

@ -269,9 +269,11 @@ func fill_in_selection() -> void:
var selection_map_copy := project.selection_map.return_cropped_copy(project.size) var selection_map_copy := project.selection_map.return_cropped_copy(project.size)
for image in images: for image in images:
image.blit_rect_mask(filler, selection_map_copy, rect, rect.position) image.blit_rect_mask(filler, selection_map_copy, rect, rect.position)
image.convert_rgb_to_indexed()
else: else:
for image in images: for image in images:
image.fill(tool_slot.color) image.fill(tool_slot.color)
image.convert_rgb_to_indexed()
else: else:
# End early if we are filling with an empty pattern # End early if we are filling with an empty pattern
var pattern_image: Image = _pattern.image var pattern_image: Image = _pattern.image

View file

@ -16,17 +16,17 @@ func _input(event: InputEvent) -> void:
return return
if event.is_action_pressed("transform_snap_grid"): if event.is_action_pressed("transform_snap_grid"):
_snap_to_grid = true _snap_to_grid = true
_offset = _offset.snapped(Global.grid_size) _offset = _offset.snapped(Global.grids[0].grid_size)
if Global.current_project.has_selection and selection_node.is_moving_content: if Global.current_project.has_selection and selection_node.is_moving_content:
var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position
selection_node.big_bounding_rectangle.position = Vector2i( selection_node.big_bounding_rectangle.position = Vector2i(
prev_pos.snapped(Global.grid_size) prev_pos.snapped(Global.grids[0].grid_size)
) )
# The first time transform_snap_grid is enabled then _snap_position() is not called # The first time transform_snap_grid is enabled then _snap_position() is not called
# and the selection had wrong offset, so do selection offsetting here # and the selection had wrong offset, so do selection offsetting here
var grid_offset := Vector2i( var grid_offset := Vector2i(
fmod(Global.grid_offset.x, Global.grid_size.x), fmod(Global.grids[0].grid_offset.x, Global.grids[0].grid_size.x),
fmod(Global.grid_offset.y, Global.grid_size.y) fmod(Global.grids[0].grid_offset.y, Global.grids[0].grid_size.y)
) )
selection_node.big_bounding_rectangle.position += grid_offset selection_node.big_bounding_rectangle.position += grid_offset
selection_node.marching_ants_outline.offset += Vector2( selection_node.marching_ants_outline.offset += Vector2(
@ -110,16 +110,18 @@ func _snap_position(pos: Vector2) -> Vector2:
else: else:
pos.x = _start_pos.x pos.x = _start_pos.x
if _snap_to_grid: # Snap to grid if _snap_to_grid: # Snap to grid
pos = pos.snapped(Global.grid_size) pos = pos.snapped(Global.grids[0].grid_size)
# The part below only corrects the offset for situations when there is no selection # The part below only corrects the offset for situations when there is no selection
# Offsets when there is selection is controlled in _input() function # Offsets when there is selection is controlled in _input() function
if !Global.current_project.has_selection: if !Global.current_project.has_selection:
var move_offset := Vector2.ZERO var move_offset := Vector2.ZERO
move_offset.x = ( move_offset.x = (
_start_pos.x - (_start_pos.x / Global.grid_size.x) * Global.grid_size.x _start_pos.x
- (_start_pos.x / Global.grids[0].grid_size.x) * Global.grids[0].grid_size.x
) )
move_offset.y = ( move_offset.y = (
_start_pos.y - (_start_pos.y / Global.grid_size.y) * Global.grid_size.y _start_pos.y
- (_start_pos.y / Global.grids[0].grid_size.y) * Global.grids[0].grid_size.y
) )
pos += move_offset pos += move_offset

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=6 format=3 uid="uid://ct4o5i1jeul3k"] [gd_scene load_steps=6 format=3 uid="uid://bdregpkflev7u"]
[ext_resource type="PackedScene" uid="uid://ctfgfelg0sho8" path="res://src/Tools/BaseTool.tscn" id="1_1q6ub"] [ext_resource type="PackedScene" uid="uid://ctfgfelg0sho8" path="res://src/Tools/BaseTool.tscn" id="1_1q6ub"]
[ext_resource type="Script" path="res://src/Tools/UtilityTools/Text.gd" id="2_ql5g6"] [ext_resource type="Script" path="res://src/Tools/UtilityTools/Text.gd" id="2_ql5g6"]
@ -63,6 +63,8 @@ stretch_margin_bottom = 3
script = ExtResource("3_tidsq") script = ExtResource("3_tidsq")
prefix = "Size:" prefix = "Size:"
suffix = "px" suffix = "px"
global_increment_action = "brush_size_increment"
global_decrement_action = "brush_size_decrement"
[node name="GridContainer" type="GridContainer" parent="." index="4"] [node name="GridContainer" type="GridContainer" parent="." index="4"]
layout_mode = 2 layout_mode = 2

View file

@ -1,5 +1,8 @@
extends Node2D extends Node2D
var unique_rect_lines := PackedVector2Array()
var unique_iso_lines := PackedVector2Array()
func _ready() -> void: func _ready() -> void:
Global.project_switched.connect(queue_redraw) Global.project_switched.connect(queue_redraw)
@ -10,54 +13,60 @@ func _draw() -> void:
return return
var target_rect: Rect2i var target_rect: Rect2i
if Global.grid_draw_over_tile_mode: unique_rect_lines.clear()
target_rect = Global.current_project.tiles.get_bounding_rect() unique_iso_lines.clear()
else: for grid_idx in range(Global.grids.size() - 1, -1, -1):
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size) if Global.grids[grid_idx].grid_draw_over_tile_mode:
if not target_rect.has_area(): target_rect = Global.current_project.tiles.get_bounding_rect()
return else:
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size)
if not target_rect.has_area():
return
var grid_type := Global.grid_type var grid_type := Global.grids[grid_idx].grid_type
if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL: if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL:
_draw_cartesian_grid(target_rect) _draw_cartesian_grid(grid_idx, target_rect)
if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL: if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL:
_draw_isometric_grid(target_rect) _draw_isometric_grid(grid_idx, target_rect)
func _draw_cartesian_grid(target_rect: Rect2i) -> void: func _draw_cartesian_grid(grid_index: int, target_rect: Rect2i) -> void:
var grid = Global.grids[grid_index]
var grid_multiline_points := PackedVector2Array() var grid_multiline_points := PackedVector2Array()
var x: float = ( var x: float = (
target_rect.position.x target_rect.position.x
+ fposmod(Global.grid_offset.x - target_rect.position.x, Global.grid_size.x) + fposmod(grid.grid_offset.x - target_rect.position.x, grid.grid_size.x)
) )
while x <= target_rect.end.x: while x <= target_rect.end.x:
grid_multiline_points.push_back(Vector2(x, target_rect.position.y)) if not Vector2(x, target_rect.position.y) in unique_rect_lines:
grid_multiline_points.push_back(Vector2(x, target_rect.end.y)) grid_multiline_points.push_back(Vector2(x, target_rect.position.y))
x += Global.grid_size.x grid_multiline_points.push_back(Vector2(x, target_rect.end.y))
x += grid.grid_size.x
var y: float = ( var y: float = (
target_rect.position.y target_rect.position.y
+ fposmod(Global.grid_offset.y - target_rect.position.y, Global.grid_size.y) + fposmod(grid.grid_offset.y - target_rect.position.y, grid.grid_size.y)
) )
while y <= target_rect.end.y: while y <= target_rect.end.y:
grid_multiline_points.push_back(Vector2(target_rect.position.x, y)) if not Vector2(target_rect.position.x, y) in unique_rect_lines:
grid_multiline_points.push_back(Vector2(target_rect.end.x, y)) grid_multiline_points.push_back(Vector2(target_rect.position.x, y))
y += Global.grid_size.y grid_multiline_points.push_back(Vector2(target_rect.end.x, y))
y += grid.grid_size.y
unique_rect_lines.append_array(grid_multiline_points)
if not grid_multiline_points.is_empty(): if not grid_multiline_points.is_empty():
draw_multiline(grid_multiline_points, Global.grid_color) draw_multiline(grid_multiline_points, grid.grid_color)
func _draw_isometric_grid(target_rect: Rect2i) -> void: func _draw_isometric_grid(grid_index: int, target_rect: Rect2i) -> void:
var grid = Global.grids[grid_index]
var grid_multiline_points := PackedVector2Array() var grid_multiline_points := PackedVector2Array()
var cell_size: Vector2 = Global.isometric_grid_size var cell_size: Vector2 = grid.isometric_grid_size
var max_cell_count: Vector2 = Vector2(target_rect.size) / cell_size var max_cell_count: Vector2 = Vector2(target_rect.size) / cell_size
var origin_offset: Vector2 = Vector2(Global.grid_offset - target_rect.position).posmodv( var origin_offset: Vector2 = Vector2(grid.grid_offset - target_rect.position).posmodv(cell_size)
cell_size
)
# lines ↗↗↗ (from bottom-left to top-right) # lines ↗↗↗ (from bottom-left to top-right)
var per_cell_offset: Vector2 = cell_size * Vector2(1, -1) var per_cell_offset: Vector2 = cell_size * Vector2(1, -1)
@ -70,8 +79,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y) var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y)
var cells_to_rect_bounds: float = minf(max_cell_count.x, y / cell_size.y) var cells_to_rect_bounds: float = minf(max_cell_count.x, y / cell_size.y)
var end := start + cells_to_rect_bounds * per_cell_offset var end := start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start) if not start in unique_iso_lines:
grid_multiline_points.push_back(end) grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
y += cell_size.y y += cell_size.y
# lines ↗↗↗ starting from the rect's bottom side (left to right): # lines ↗↗↗ starting from the rect's bottom side (left to right):
@ -80,8 +90,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(x, target_rect.size.y) var start: Vector2 = Vector2(target_rect.position) + Vector2(x, target_rect.size.y)
var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x) var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x)
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start) if not start in unique_iso_lines:
grid_multiline_points.push_back(end) grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
x += cell_size.x x += cell_size.x
# lines ↘↘↘ (from top-left to bottom-right) # lines ↘↘↘ (from top-left to bottom-right)
@ -93,8 +104,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y) var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y)
var cells_to_rect_bounds: float = minf(max_cell_count.x, max_cell_count.y - y / cell_size.y) var cells_to_rect_bounds: float = minf(max_cell_count.x, max_cell_count.y - y / cell_size.y)
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start) if not start in unique_iso_lines:
grid_multiline_points.push_back(end) grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
y += cell_size.y y += cell_size.y
# lines ↘↘↘ starting from the rect's top side (left to right): # lines ↘↘↘ starting from the rect's top side (left to right):
@ -103,9 +115,11 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(x, 0) var start: Vector2 = Vector2(target_rect.position) + Vector2(x, 0)
var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x) var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x)
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start) if not start in unique_iso_lines:
grid_multiline_points.push_back(end) grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
x += cell_size.x x += cell_size.x
grid_multiline_points.append_array(grid_multiline_points)
if not grid_multiline_points.is_empty(): if not grid_multiline_points.is_empty():
draw_multiline(grid_multiline_points, Global.grid_color) draw_multiline(grid_multiline_points, grid.grid_color)

View file

@ -214,7 +214,7 @@ func _move_with_arrow_keys(event: InputEvent) -> void:
if _is_action_direction(event) and arrow_key_move: if _is_action_direction(event) and arrow_key_move:
var step := Vector2.ONE var step := Vector2.ONE
if Input.is_key_pressed(KEY_CTRL): if Input.is_key_pressed(KEY_CTRL):
step = Global.grid_size step = Global.grids[0].grid_size
var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var move := input.rotated(snappedf(Global.camera.rotation, PI / 2)) var move := input.rotated(snappedf(Global.camera.rotation, PI / 2))
# These checks are needed to fix a bug where the selection got stuck # These checks are needed to fix a bug where the selection got stuck
@ -808,9 +808,11 @@ func delete(selected_cels := true) -> void:
image.blit_rect_mask( image.blit_rect_mask(
blank, selection_map_copy, big_bounding_rectangle, big_bounding_rectangle.position blank, selection_map_copy, big_bounding_rectangle, big_bounding_rectangle.position
) )
image.convert_rgb_to_indexed()
else: else:
for image in images: for image in images:
image.fill(0) image.fill(0)
image.convert_rgb_to_indexed()
commit_undo("Draw", undo_data_tmp) commit_undo("Draw", undo_data_tmp)

View file

@ -55,6 +55,7 @@ class Recorder:
dir.make_dir_recursive(save_directory) dir.make_dir_recursive(save_directory)
project.removed.connect(recorder_panel.finalize_recording.bind(project)) project.removed.connect(recorder_panel.finalize_recording.bind(project))
project.undo_redo.version_changed.connect(capture_frame) project.undo_redo.version_changed.connect(capture_frame)
recorder_panel.captured_label.text = ""
func _notification(what: int) -> void: func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE: if what == NOTIFICATION_PREDELETE:
@ -100,6 +101,9 @@ func _on_project_switched() -> void:
initialize_recording() initialize_recording()
start_button.set_pressed_no_signal(true) start_button.set_pressed_no_signal(true)
Global.change_button_texturerect(start_button.get_child(0), "stop.png") Global.change_button_texturerect(start_button.get_child(0), "stop.png")
captured_label.text = str(
"Saved: ", recorded_projects[Global.current_project].frames_captured
)
else: else:
finalize_recording() finalize_recording()
start_button.set_pressed_no_signal(false) start_button.set_pressed_no_signal(false)

View file

@ -40,15 +40,18 @@ mouse_default_cursor_shape = 2
toggle_mode = true toggle_mode = true
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/Start"] [node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/Start"]
layout_mode = 0 layout_mode = 1
anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -10.0 offset_left = -11.0
offset_top = -10.5 offset_top = -11.0
offset_right = 10.0 offset_right = 11.0
offset_bottom = 10.5 offset_bottom = 11.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1") texture = ExtResource("1")
expand_mode = 1 expand_mode = 1
stretch_mode = 5 stretch_mode = 5
@ -74,7 +77,7 @@ offset_bottom = 10.5
texture = ExtResource("3") texture = ExtResource("3")
stretch_mode = 5 stretch_mode = 5
[node name="OpenFolder" type="Button" parent="ScrollContainer/CenterContainer/GridContainer"] [node name="OpenFolder" type="Button" parent="ScrollContainer/CenterContainer/GridContainer" groups=["UIButtons"]]
custom_minimum_size = Vector2(32, 32) custom_minimum_size = Vector2(32, 32)
layout_mode = 2 layout_mode = 2
tooltip_text = "Open Folder" tooltip_text = "Open Folder"

View file

@ -421,7 +421,6 @@ func _setup_color_mode_submenu(item: String) -> void:
color_mode_submenu.add_radio_check_item("RGBA", ColorModes.RGBA) color_mode_submenu.add_radio_check_item("RGBA", ColorModes.RGBA)
color_mode_submenu.set_item_checked(ColorModes.RGBA, true) color_mode_submenu.set_item_checked(ColorModes.RGBA, true)
color_mode_submenu.add_radio_check_item("Indexed", ColorModes.INDEXED) color_mode_submenu.add_radio_check_item("Indexed", ColorModes.INDEXED)
color_mode_submenu.hide_on_checkable_item_selection = false
color_mode_submenu.id_pressed.connect(_color_mode_submenu_id_pressed) color_mode_submenu.id_pressed.connect(_color_mode_submenu_id_pressed)
image_menu.add_child(color_mode_submenu) image_menu.add_child(color_mode_submenu)