1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-14 17:53:09 +00:00

Compare commits

...

23 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
Piotr Kostrzewski 5bfe44a202
Create pixelorama pl.nsi
Polish translation of the Pixelorama installer and context menu.
2024-01-11 15:49:36 +01:00
31 changed files with 786 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.
<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
This update has been brought to you by the contributions of:
Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind))

View file

@ -3,16 +3,23 @@ Name=Pixelorama
GenericName=2D sprite editor
GenericName[el]=Επεξεργαστής δισδιάστατων εικόνων
GenericName[fr]=Éditeur de sprites 2D
GenericName[ru]=2Д редактор спрайтов
GenericName[pt_BR]=Editor de sprites 2D
GenericName[uk]=2Д редактор спрайтів
GenericName[zh_CN]=2D
Comment=Create and edit static or animated 2D sprites
Comment[el]=Δημιουργήστε και επεξεργαστείτε στατικές ή κινούμενες δισδιάστατες εικόνες
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[uk]=Створюйте та редагуйте статичні та анімовані 2Д спрайти
Comment[zh_CN]= 2D
Exec=pixelorama
Icon=pixelorama
Terminal=false
Type=Application
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;

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

264
installer/pixelorama pl.nsi Normal file
View file

@ -0,0 +1,264 @@
; Pixelorama Installer NSIS Script
; Copyright Xenofon Konitsas (huskee) 2021
; Licensed under the MIT License
; Helper variables so that we don't change 20 instances of the version for every update
!define APPNAME "Pixelorama"
!define APPVERSION "v0.11.3"
!define COMPANYNAME "Orama Interactive"
; Include the Modern UI library
!include "MUI2.nsh"
!include "x64.nsh"
; Basic Installer Info
Name "${APPNAME} ${APPVERSION}"
OutFile "${APPNAME}_${APPVERSION}_setup.exe"
Unicode True
; Default installation folder
InstallDir "$APPDATA\${COMPANYNAME}\${APPNAME}"
; Get installation folder from registry if available
InstallDirRegKey HKCU "Software\${COMPANYNAME}\${APPNAME}" "InstallDir"
; Request application privileges for Vista and later
RequestExecutionLevel admin
; Interface Settings
!define MUI_ICON "assets\pixel-install.ico"
!define MUI_UNICON "assets\pixel-uninstall.ico"
!define MUI_WELCOMEFINISHPAGE_BITMAP "assets\wizard.bmp"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "assets\wizard.bmp"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_RIGHT
!define MUI_HEADERIMAGE_BITMAP "assets\header.bmp"
!define MUI_HEADERIMAGE_UNBITMAP "assets\header.bmp"
!define MUI_ABORTWARNING
!define MUI_COMPONENTSPAGE_SMALLDESC
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
!define MUI_FINISHPAGE_RUN "$INSTDIR\pixelorama.exe"
; Language selection settings
!define MUI_LANGDLL_ALLLANGUAGES
## Remember the installer language
!define MUI_LANGDLL_REGISTRY_ROOT HKCU
!define MUI_LANGDLL_REGISTRY_KEY "Software\${COMPANYNAME}\${APPNAME}"
!define MUI_LANGDLL_REGISTRY_VALUENAME "Język instalatora"
; Installer pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "Licencja"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
; Multilingual support
!insertmacro MUI_LANGUAGE "Polski"
;@INSERT_TRANSLATIONS@
; Assign language strings to installer/uninstaller section names
LangString SecInstall ${LANG_ENGLISH} "Zainstaluj ${APPNAME}"
LangString SecStartmenu ${LANG_ENGLISH} "Utwórz skróty w menu Start (opcjonalnie)"
LangString SecDesktop ${LANG_ENGLISH} "Utwórz skrót na pulpicie (opcjonalnie)"
LangString un.SecUninstall ${LANG_ENGLISH} "Odinstaluj ${APPNAME} ${APPVERSION}"
LangString un.SecConfig ${LANG_ENGLISH} "Usuń pliki konfiguracyjne (opcjonalnie)"
; Installer sections
Section "$(SecInstall)" SecInstall ; Main install section
SectionIn RO ; Non optional section
; Set the installation folder as the output directory
SetOutPath "$INSTDIR"
; Copy all files to install directory
${If} ${RunningX64}
File "..\build\windows-64bit\pixelorama.exe"
File "..\build\windows-64bit\pixelorama.pck"
${Else}
File "..\build\windows-32bit\pixelorama.exe"
File "..\build\windows-32bit\pixelorama.pck"
${EndIf}
File "..\assets\graphics\icons\pxo.ico"
SetOutPath "$INSTDIR\pixelorama_data"
File /nonfatal /r "..\build\pixelorama_data\*"
; Store installation folder in the registry
WriteRegStr HKCU "Software\${COMPANYNAME}\${APPNAME}" "InstallDir" $INSTDIR
; Create uninstaller
WriteUninstaller "$INSTDIR\uninstall.exe"
; Create Add/Remove Programs entry
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"DisplayName" "${APPNAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"UninstallString" "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"DisplayIcon" "$INSTDIR\pixelorama.exe,0"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"Publisher" "${COMPANYNAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"HelpLink" "https://orama-interactive.github.io/Pixelorama-Docs"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"DisplayVersion" "${APPVERSION}"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" \
"NoRepair" 1
; Associate .pxo files with Pixelorama
WriteRegStr HKCR ".pxo" "" "Pixelorama project"
WriteRegStr HKCR ".pxo" "ContentType" "image/pixelorama"
WriteRegStr HKCR ".pxo" "PerceivedType" "document"
WriteRegStr HKCR "Pixelorama project" "" "Pixelorama project"
WriteRegStr HKCR "Pixelorama project\shell" "" "open"
WriteRegStr HKCR "Pixelorama project\DefaultIcon" "" "$INSTDIR\pxo.ico"
WriteRegStr HKCR "Pixelorama project\shell\open\command" "" '$INSTDIR\${APPNAME}.exe "%1"'
WriteRegStr HKCR "Pixelorama project\shell\edit" "" "Edytuj projekt w ${APPNAME}"
WriteRegStr HKCR "Pixelorama project\shell\edit\command" "" '$INSTDIR\${APPNAME}.exe "%1"'
SectionEnd
Section /o "$(SecStartmenu)" SecStartmenu ; Create Start Menu shortcuts
; Create folder in Start Menu\Programs and create shortcuts for app and uninstaller
CreateDirectory "$SMPROGRAMS\${COMPANYNAME}"
CreateShortCut "$SMPROGRAMS\${COMPANYNAME}\${APPNAME} ${APPVERSION}.lnk" "$INSTDIR\Pixelorama.exe"
CreateShortCut "$SMPROGRAMS\${COMPANYNAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe"
SectionEnd
Section /o "$(SecDesktop)" SecDesktop ; Create Desktop shortcut
; Create shortcut for app on desktop
CreateShortCut "$DESKTOP\${APPNAME} ${APPVERSION}.lnk" "$INSTDIR\Pixelorama.exe"
SectionEnd
; Installer functions
Function .onInit
!insertmacro MUI_LANGDLL_DISPLAY
FunctionEnd
; Uninstaller sections
Section "un.$(un.SecUninstall)" un.SecUninstall ; Main uninstall section
SectionIn RO
; Delete all files and folders created by the installer
Delete "$INSTDIR\uninstall.exe"
Delete "$INSTDIR\Pixelorama.exe"
Delete "$INSTDIR\Pixelorama.pck"
Delete "$INSTDIR\pxo.ico"
RMDir /r "$INSTDIR\pixelorama_data"
RMDir "$INSTDIR"
; Delete shortcuts
RMDir /r "$SMPROGRAMS\${COMPANYNAME}"
Delete "$DESKTOP\${APPNAME} ${APPVERSION}.lnk"
; Delete the install folder
SetOutPath "$APPDATA"
RMDir /r "${COMPANYNAME}"
; If empty, delete the application's registry key
DeleteRegKey /ifempty HKCU "Software\${COMPANYNAME}\${APPNAME}"
; Delete the Add/Remove Programs entry
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
; Delete the .pxo file association
DeleteRegKey HKCR "Pixelorama project"
DeleteRegKey HKCR ".pxo"
SectionEnd
Section "un.$(un.SecConfig)" un.SecConfig ; Configuration removal section
; Delete the application's settings file
Delete "$APPDATA\Godot\app_userdata\${APPNAME}\cache.ini"
SectionEnd
; Uninstaller functions
Function un.onInit
!insertmacro MUI_UNGETLANGUAGE
FunctionEnd
; Section description language strings for multilingual support
LangString DESC_SecInstall ${LANG_ENGLISH} "Instalowanie ${APPNAME} ${APPVERSION}."
LangString DESC_SecStartmenu ${LANG_ENGLISH} "Tworzenie skrótów w menu Start ${APPNAME}."
LangString DESC_SecDesktop ${LANG_ENGLISH} "Tworzenie skrótu na pulpicie dla ${APPNAME}."
LangString DESC_un.SecUninstall ${LANG_ENGLISH} "Odinstalowywanie ${APPNAME} ${APPVERSION} i usuwanie wszystkich skrótów."
LangString DESC_un.SecConfig ${LANG_ENGLISH} "Usuwanie plików konfiguracyjnych ${APPNAME}."
; Assign language strings to installer/uninstaller descriptions
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecInstall} $(DESC_SecInstall)
!insertmacro MUI_DESCRIPTION_TEXT ${SecStartmenu} $(DESC_SecStartmenu)
!insertmacro MUI_DESCRIPTION_TEXT ${SecDesktop} $(DESC_SecDesktop)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
!insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${un.SecUninstall} $(DESC_un.SecUninstall)
!insertmacro MUI_DESCRIPTION_TEXT ${un.SecConfig} $(DESC_un.SecConfig)
!insertmacro MUI_UNFUNCTION_DESCRIPTION_END

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)
## Found in Preferences. The distance to the guide or grig below which cursor snapping activates.
var snapping_distance := 32.0
## Found in Preferences. The grid type defined by [enum GridTypes] enum.
var grid_type := GridTypes.CARTESIAN:
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()
## Contains dictionaries of individual grids.
var grids: Array[Grid] = []
## Found in Preferences. The minimum zoom after which pixel grid gets drawn if enabled.
var pixel_grid_show_at_zoom := 1500.0: # percentage
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")
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:
# Load settings from the config file
config_cache.load(CONFIG_PATH)
@ -713,6 +722,8 @@ func _init() -> void:
func _ready() -> void:
# Initialize Grid
Grid.new() # gets auto added to grids array
_initialize_keychain()
default_width = config_cache.get_value("preferences", "default_width", default_width)
default_height = config_cache.get_value("preferences", "default_height", default_height)
@ -729,13 +740,27 @@ func _ready() -> void:
if get(pref) == null:
continue
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():
Global.use_native_file_dialogs = true
await get_tree().process_frame
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:
Keychain.config_file = config_cache
Keychain.actions = {
@ -1066,7 +1091,9 @@ func get_available_font_names() -> PackedStringArray:
if font_name in font_names:
continue
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:
continue
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:
# Attempt to load the images from the file paths.
var loaded_images: Array = []
for filen in fpaths:
for file in fpaths:
var image := Image.new()
var err := image.load(filen)
var err := image.load(file)
if err == OK:
image.convert(Image.FORMAT_RGBA8)
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:
current_palette = palettes.get(palette_name)
current_palette = palettes.get(palette_name, null)
_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)
@ -224,6 +225,7 @@ func current_palete_delete(permanent := true) -> void:
select_palette(palettes.keys()[0])
else:
current_palette = null
select_palette("")
func current_palette_add_color(mouse_button: int, start_index := 0) -> void:

View file

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

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].
func update_palette() -> void:
if palette.size() != current_palette.colors.size():
palette.resize(current_palette.colors.size())
if not is_instance_valid(current_palette):
return
if palette.size() != current_palette.colors_max:
palette.resize(current_palette.colors_max)
palette.fill(TRANSPARENT)
for i in current_palette.colors:
palette[i] = current_palette.colors[i].color
## Displays the actual RGBA values of each pixel in the image from indexed mode.
func convert_indexed_to_rgb() -> void:
if not is_indexed:
if not is_indexed or not is_instance_valid(current_palette):
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 shader_image_effect := ShaderImageEffect.new()
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,
## by finding the palette color's index and storing it in [member indices_image].
func convert_rgb_to_indexed() -> void:
if not is_indexed:
if not is_indexed or not is_instance_valid(current_palette):
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 params := {
"palette_texture": palette_texture, "rgb_texture": ImageTexture.create_from_image(self)

View file

@ -25,7 +25,7 @@ var colors_max := 0
class PaletteColor:
var color := Color.TRANSPARENT
var color := Color(0, 0, 0, 0)
var index := -1
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)
func convert_to_image() -> Image:
func convert_to_image(crop_image := true) -> Image:
var image := Image.create(colors_max, 1, false, Image.FORMAT_RGBA8)
for i in colors_max:
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

View file

@ -23,10 +23,6 @@ func _ready() -> void:
func set_palette(new_palette: Palette) -> void:
# Only display valid palette objects
if not new_palette:
return
current_palette = new_palette
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].
## 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:
if not is_instance_valid(current_palette):
return
var old_index := Palettes.current_palette_get_selected_color_index(mouse_button)
for color_ind in swatches.size():
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
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 old_index := convert_palette_index_to_grid_index(old_palette_index)
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:
if not is_instance_valid(current_palette):
return
var grid_x: int = (
new_rect_size.x / (swatch_size.x + get("theme_override_constants/h_separation"))
)
var grid_y: int = (
new_rect_size.y / (swatch_size.y + get("theme_override_constants/v_separation"))
)
grid_size.x = mini(grid_x, current_palette.width)
grid_size.y = mini(grid_y, current_palette.height)
if is_instance_valid(current_palette):
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()
draw_palette()

View file

@ -89,16 +89,16 @@ func select_palette(palette_name: String) -> void:
var palette_id = palettes_path_id.get(palette_name)
if palette_id != null:
palette_select.selected = palette_id
palette_grid.set_palette(Palettes.current_palette)
palette_scroll.resize_grid()
palette_scroll.set_sliders(Palettes.current_palette, palette_grid.grid_window_origin)
palette_grid.set_palette(Palettes.current_palette)
palette_scroll.resize_grid()
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 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_RIGHT, right_selected, right_selected)
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)
palette_grid.select_swatch(MOUSE_BUTTON_LEFT, left_selected, left_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
@ -115,6 +115,8 @@ func redraw_current_palette() -> 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()
if add_color_button.disabled:
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)
):
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

View file

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

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("integer_zoom", "Canvas/ZoomOptions/IntegerZoom", "button_pressed", false),
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(
"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="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/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="Script" path="res://src/Preferences/ExtensionsPreferences.gd" id="7_8ume5"]
[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/v_separation = 4
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"]
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/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="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="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"]

View file

@ -14,7 +14,7 @@ vec4 swap_color(vec4 color) {
int n_of_colors = textureSize(palette_texture, 0).x;
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() {

View file

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

View file

@ -7,16 +7,16 @@ uniform sampler2D palette_texture : filter_nearest;
uniform sampler2D indices_texture : filter_nearest;
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
COLOR = vec4(0.0);
}
else {
float n_of_colors = float(textureSize(palette_texture, 0).x);
index -= 1.0 / 255.0;
float index_normalized = ((index * 255.0)) / n_of_colors;
if (index_normalized + EPSILON < 1.0) {
COLOR = texture(palette_texture, vec2(index_normalized + EPSILON, 0.0));
index -= 1.0;
float index_normalized = index / n_of_colors;
if (index < n_of_colors) {
COLOR = texelFetch(palette_texture, ivec2(int(index), 0), 0);
}
else {
// 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() {
vec4 color = texture(rgb_texture, UV);
if (color.a <= 0.01) {
if (color.a <= 0.0001) {
COLOR.r = 0.0;
}
else {

View file

@ -159,16 +159,17 @@ func draw_move(pos: Vector2i) -> void:
else:
pos.x = _start_pos.x
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
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.big_bounding_rectangle.position - prev_pos
)
pos = pos.snapped(Global.grid_size)
var grid_offset := Global.grid_offset
pos = pos.snapped(Global.grids[0].grid_size)
var grid_offset := Global.grids[0].grid_offset
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

View file

@ -129,19 +129,20 @@ func draw_preview() -> void:
func snap_position(pos: Vector2) -> Vector2:
var snapping_distance := Global.snapping_distance / Global.camera.zoom.x
if Global.snap_to_rectangular_grid_boundary:
var grid_pos := pos.snapped(Global.grid_size)
grid_pos += Vector2(Global.grid_offset)
var grid_pos := pos.snapped(Global.grids[0].grid_size)
grid_pos += Vector2(Global.grids[0].grid_offset)
# 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
var t_l := grid_pos + Vector2(-Global.grid_size.x, -Global.grid_size.y)
var t_c := grid_pos + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on
var t_r := grid_pos + Vector2(Global.grid_size.x, -Global.grid_size.y)
var m_l := grid_pos + Vector2(-Global.grid_size.x, 0)
# t_l is for "top left" and so on
var t_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var t_c := grid_pos + Vector2(0, -Global.grids[0].grid_size.y)
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_r := grid_pos + Vector2(Global.grid_size.x, 0)
var b_l := grid_pos + Vector2(-Global.grid_size.x, Global.grid_size.y)
var b_c := grid_pos + Vector2(0, Global.grid_size.y)
var b_r := grid_pos + Vector2(Global.grid_size)
var m_r := grid_pos + Vector2(Global.grids[0].grid_size.x, 0)
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.grids[0].grid_size.y)
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]
for vec in vec_arr:
if vec.distance_to(pos) < grid_pos.distance_to(pos):
@ -152,19 +153,22 @@ func snap_position(pos: Vector2) -> Vector2:
pos = grid_point.floor()
if Global.snap_to_rectangular_grid_center:
var grid_center := pos.snapped(Global.grid_size) + Vector2(Global.grid_size / 2)
grid_center += Vector2(Global.grid_offset)
var grid_center := (
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
# 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)
var t_c := grid_center + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on
var t_r := grid_center + Vector2(Global.grid_size.x, -Global.grid_size.y)
var m_l := grid_center + Vector2(-Global.grid_size.x, 0)
# t_l is for "top left" and so on
var t_l := grid_center + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var t_c := grid_center + Vector2(0, -Global.grids[0].grid_size.y)
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_r := grid_center + Vector2(Global.grid_size.x, 0)
var b_l := grid_center + Vector2(-Global.grid_size.x, Global.grid_size.y)
var b_c := grid_center + Vector2(0, Global.grid_size.y)
var b_r := grid_center + Vector2(Global.grid_size)
var m_r := grid_center + Vector2(Global.grids[0].grid_size.x, 0)
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.grids[0].grid_size.y)
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]
for vec in vec_arr:
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)
for image in images:
image.blit_rect_mask(filler, selection_map_copy, rect, rect.position)
image.convert_rgb_to_indexed()
else:
for image in images:
image.fill(tool_slot.color)
image.convert_rgb_to_indexed()
else:
# End early if we are filling with an empty pattern
var pattern_image: Image = _pattern.image

View file

@ -16,17 +16,17 @@ func _input(event: InputEvent) -> void:
return
if event.is_action_pressed("transform_snap_grid"):
_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:
var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position
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
# and the selection had wrong offset, so do selection offsetting here
var grid_offset := Vector2i(
fmod(Global.grid_offset.x, Global.grid_size.x),
fmod(Global.grid_offset.y, Global.grid_size.y)
fmod(Global.grids[0].grid_offset.x, Global.grids[0].grid_size.x),
fmod(Global.grids[0].grid_offset.y, Global.grids[0].grid_size.y)
)
selection_node.big_bounding_rectangle.position += grid_offset
selection_node.marching_ants_outline.offset += Vector2(
@ -110,16 +110,18 @@ func _snap_position(pos: Vector2) -> Vector2:
else:
pos.x = _start_pos.x
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
# Offsets when there is selection is controlled in _input() function
if !Global.current_project.has_selection:
var move_offset := Vector2.ZERO
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 = (
_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

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="Script" path="res://src/Tools/UtilityTools/Text.gd" id="2_ql5g6"]
@ -63,6 +63,8 @@ stretch_margin_bottom = 3
script = ExtResource("3_tidsq")
prefix = "Size:"
suffix = "px"
global_increment_action = "brush_size_increment"
global_decrement_action = "brush_size_decrement"
[node name="GridContainer" type="GridContainer" parent="." index="4"]
layout_mode = 2

View file

@ -1,5 +1,8 @@
extends Node2D
var unique_rect_lines := PackedVector2Array()
var unique_iso_lines := PackedVector2Array()
func _ready() -> void:
Global.project_switched.connect(queue_redraw)
@ -10,54 +13,60 @@ func _draw() -> void:
return
var target_rect: Rect2i
if Global.grid_draw_over_tile_mode:
target_rect = Global.current_project.tiles.get_bounding_rect()
else:
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size)
if not target_rect.has_area():
return
unique_rect_lines.clear()
unique_iso_lines.clear()
for grid_idx in range(Global.grids.size() - 1, -1, -1):
if Global.grids[grid_idx].grid_draw_over_tile_mode:
target_rect = Global.current_project.tiles.get_bounding_rect()
else:
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size)
if not target_rect.has_area():
return
var grid_type := Global.grid_type
if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL:
_draw_cartesian_grid(target_rect)
var grid_type := Global.grids[grid_idx].grid_type
if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL:
_draw_cartesian_grid(grid_idx, target_rect)
if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL:
_draw_isometric_grid(target_rect)
if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL:
_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 x: float = (
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:
grid_multiline_points.push_back(Vector2(x, target_rect.position.y))
grid_multiline_points.push_back(Vector2(x, target_rect.end.y))
x += Global.grid_size.x
if not Vector2(x, target_rect.position.y) in unique_rect_lines:
grid_multiline_points.push_back(Vector2(x, target_rect.position.y))
grid_multiline_points.push_back(Vector2(x, target_rect.end.y))
x += grid.grid_size.x
var y: float = (
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:
grid_multiline_points.push_back(Vector2(target_rect.position.x, y))
grid_multiline_points.push_back(Vector2(target_rect.end.x, y))
y += Global.grid_size.y
if not Vector2(target_rect.position.x, y) in unique_rect_lines:
grid_multiline_points.push_back(Vector2(target_rect.position.x, 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():
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 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 origin_offset: Vector2 = Vector2(Global.grid_offset - target_rect.position).posmodv(
cell_size
)
var origin_offset: Vector2 = Vector2(grid.grid_offset - target_rect.position).posmodv(cell_size)
# lines ↗↗↗ (from bottom-left to top-right)
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 cells_to_rect_bounds: float = minf(max_cell_count.x, y / cell_size.y)
var end := start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
y += cell_size.y
# 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 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
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
x += cell_size.x
# 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 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
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
y += cell_size.y
# 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 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
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
x += cell_size.x
grid_multiline_points.append_array(grid_multiline_points)
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:
var step := Vector2.ONE
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 move := input.rotated(snappedf(Global.camera.rotation, PI / 2))
# 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(
blank, selection_map_copy, big_bounding_rectangle, big_bounding_rectangle.position
)
image.convert_rgb_to_indexed()
else:
for image in images:
image.fill(0)
image.convert_rgb_to_indexed()
commit_undo("Draw", undo_data_tmp)

View file

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

View file

@ -40,15 +40,18 @@ mouse_default_cursor_shape = 2
toggle_mode = true
[node name="TextureRect" type="TextureRect" parent="ScrollContainer/CenterContainer/GridContainer/Start"]
layout_mode = 0
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -10.0
offset_top = -10.5
offset_right = 10.0
offset_bottom = 10.5
offset_left = -11.0
offset_top = -11.0
offset_right = 11.0
offset_bottom = 11.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("1")
expand_mode = 1
stretch_mode = 5
@ -74,7 +77,7 @@ offset_bottom = 10.5
texture = ExtResource("3")
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)
layout_mode = 2
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.set_item_checked(ColorModes.RGBA, true)
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)
image_menu.add_child(color_mode_submenu)