mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 09:09:47 +00:00
Compare commits
22 commits
c3cb695b36
...
619456f4cc
Author | SHA1 | Date | |
---|---|---|---|
619456f4cc | |||
8bd31112be | |||
d980eec683 | |||
39c85c3079 | |||
8ceeba76c0 | |||
93eab6929b | |||
1d9b9fda1e | |||
482dbecd13 | |||
cf8dacf0f5 | |||
048058bd35 | |||
605bff7324 | |||
206773c4e7 | |||
b5d5c44c4b | |||
8e55b91a39 | |||
0fad406967 | |||
4b12f764b5 | |||
02d1900dc2 | |||
18e9e2ec56 | |||
6100bdc8df | |||
5ec316a50f | |||
a7a76ff9f0 | |||
5bfe44a202 |
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -6,13 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## [v1.1] - Unreleased
|
||||
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)), Spencer Beckwith ([@spencerjbeckwith](https://github.com/spencerjbeckwith))
|
||||
|
||||
Built using Godot 4.3
|
||||
|
||||
### Added
|
||||
- Tilemap layers have arrived! Tilemap layers allow artists to create tiles, and easily preview and dynamically modify them within Pixelorama. [#1146](https://github.com/Orama-Interactive/Pixelorama/pull/1146)
|
||||
- Indexed mode has finally been implemented! [#1136](https://github.com/Orama-Interactive/Pixelorama/pull/1136)
|
||||
- Audio layers have been added, allowing artists to easily synchronize their animations with audio. [#1149](https://github.com/Orama-Interactive/Pixelorama/pull/1149)
|
||||
- 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)
|
||||
- A color curves image and layer effect has been added.
|
||||
- It is now possible to load custom Godot shaders as image and layer effects.
|
||||
- Implemented support for multiple grids. [#1122](https://github.com/Orama-Interactive/Pixelorama/pull/1122)
|
||||
|
||||
### Changed
|
||||
|
@ -21,7 +25,11 @@ Built using Godot 4.3
|
|||
### 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
|
||||
- The first frame is no longer exported twice when using ping-pong loop.
|
||||
- Fixed pencil/eraser/shading previews turning white for a brief moment when changing image brushes, and when switchin between tools.
|
||||
- Dialogs that are children of other dialogs now always appear on top, to avoid issues where they could hide behind their parents and causing confusion that made Pixelorama seem unresponsive.
|
||||
- Palette swatches now get deleted when the user removes all palettes.
|
||||
- The CLI's output option now works with filepaths instead of just filenames. [#1145](https://github.com/Orama-Interactive/Pixelorama/pull/1145)
|
||||
- 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)
|
||||
|
@ -50,7 +58,7 @@ Built using Godot 4.3
|
|||
- Fixed layer effect slider values being rounded to the nearest integer.
|
||||
- Fixed memory leak where the project remained referenced by a drawing tool, even when its tab was closed.
|
||||
- Fixed memory leak where the first project remained forever references in memory by the Recorder panel.
|
||||
- Slightly optimize circle brushes by only calling the ellipse algorithms once while drawing
|
||||
- Slightly optimize circle brushes by only calling the ellipse algorithms once while drawing.
|
||||
|
||||
### Removed
|
||||
- The Recorder panel has been removed from the Web version. It wasn't functional anyway in a way that was useful, and it's unsure if we can find a way to make it work.
|
||||
|
|
|
@ -1157,6 +1157,27 @@ msgstr ""
|
|||
msgid "Tint effect factor:"
|
||||
msgstr ""
|
||||
|
||||
#. An image effect that adjusts the colors of the image by using curves.
|
||||
msgid "Color Curves"
|
||||
msgstr ""
|
||||
|
||||
#. Refers to a color channel, such as the red, green, blue or alpha channels.
|
||||
msgid "Channel:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Red"
|
||||
msgstr ""
|
||||
|
||||
msgid "Green"
|
||||
msgstr ""
|
||||
|
||||
#. Refers to the value (as in HSV) of the colors of an image.
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
msgid "Presets"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1773,6 +1794,10 @@ msgstr ""
|
|||
msgid "If enabled, the application window can become transparent. This affects performance, so keep it off if you don't need it."
|
||||
msgstr ""
|
||||
|
||||
#. An option found in the preferences, under the Performance section.
|
||||
msgid "Use dummy audio driver"
|
||||
msgstr ""
|
||||
|
||||
#. Found in the Preferences, under Drivers. Specifies the renderer/video driver being used.
|
||||
msgid "Renderer:"
|
||||
msgstr ""
|
||||
|
@ -2203,6 +2228,10 @@ msgstr ""
|
|||
msgid "Unlink Cels"
|
||||
msgstr ""
|
||||
|
||||
#. An option found in the right click menu of an audio cel. If selected, the audio of the audio layer will start playing from this frame.
|
||||
msgid "Play audio here"
|
||||
msgstr ""
|
||||
|
||||
msgid "Properties"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2243,6 +2272,9 @@ msgstr ""
|
|||
msgid "Tilemap"
|
||||
msgstr ""
|
||||
|
||||
msgid "Audio"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layers"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2275,6 +2307,11 @@ msgstr ""
|
|||
msgid "Add Tilemap Layer"
|
||||
msgstr ""
|
||||
|
||||
#. One of the options of the create new layer button.
|
||||
#: src/UI/Timeline/AnimationTimeline.tscn
|
||||
msgid "Add Audio Layer"
|
||||
msgstr ""
|
||||
|
||||
#: src/UI/Timeline/AnimationTimeline.tscn
|
||||
msgid "Remove current layer"
|
||||
msgstr ""
|
||||
|
@ -2405,6 +2442,17 @@ msgstr ""
|
|||
msgid "Expand/collapse group"
|
||||
msgstr ""
|
||||
|
||||
#. Refers to the audio file of an audio layer.
|
||||
msgid "Audio file:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load file"
|
||||
msgstr ""
|
||||
|
||||
#. An option in the audio layer properties, allows users to play the audio starting from a specific frame.
|
||||
msgid "Play at frame:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Palette"
|
||||
msgstr ""
|
||||
|
||||
|
|
BIN
assets/graphics/misc/musical_note.png
Normal file
BIN
assets/graphics/misc/musical_note.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 192 B |
34
assets/graphics/misc/musical_note.png.import
Normal file
34
assets/graphics/misc/musical_note.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dfjd72smxp6ma"
|
||||
path="res://.godot/imported/musical_note.png-f1be7cc6341733e6ffe2fa5b650b80c2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/musical_note.png"
|
||||
dest_files=["res://.godot/imported/musical_note.png-f1be7cc6341733e6ffe2fa5b650b80c2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/graphics/tileset/place_tiles_disabled.png
Normal file
BIN
assets/graphics/tileset/place_tiles_disabled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 204 B |
34
assets/graphics/tileset/place_tiles_disabled.png.import
Normal file
34
assets/graphics/tileset/place_tiles_disabled.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bqr3n3tm8b6w2"
|
||||
path="res://.godot/imported/place_tiles_disabled.png-f43e25b0863e3eedf3c6fc7ef902127f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/tileset/place_tiles_disabled.png"
|
||||
dest_files=["res://.godot/imported/place_tiles_disabled.png-f43e25b0863e3eedf3c6fc7ef902127f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/graphics/tileset/place_tiles_enabled.png
Normal file
BIN
assets/graphics/tileset/place_tiles_enabled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 185 B |
34
assets/graphics/tileset/place_tiles_enabled.png.import
Normal file
34
assets/graphics/tileset/place_tiles_enabled.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dga8nirhhgyc4"
|
||||
path="res://.godot/imported/place_tiles_enabled.png-845e4dd5c3bbd38cc7bf5ee82ed05667.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/tileset/place_tiles_enabled.png"
|
||||
dest_files=["res://.godot/imported/place_tiles_enabled.png-845e4dd5c3bbd38cc7bf5ee82ed05667.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
264
installer/pixelorama pl.nsi
Normal file
264
installer/pixelorama pl.nsi
Normal 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
|
||||
|
||||
|
|
@ -27,10 +27,6 @@ config/windows_native_icon="res://assets/graphics/icons/icon.ico"
|
|||
config/ExtensionsAPI_Version=5
|
||||
config/Pxo_Version=4
|
||||
|
||||
[audio]
|
||||
|
||||
driver/driver="Dummy"
|
||||
|
||||
[autoload]
|
||||
|
||||
Global="*res://src/Autoload/Global.gd"
|
||||
|
@ -689,6 +685,10 @@ adjust_brightness_contrast={
|
|||
"deadzone": 0.5,
|
||||
"events": []
|
||||
}
|
||||
color_curves={
|
||||
"deadzone": 0.5,
|
||||
"events": []
|
||||
}
|
||||
gradient={
|
||||
"deadzone": 0.5,
|
||||
"events": []
|
||||
|
|
|
@ -282,7 +282,7 @@ func process_animation(project := Global.current_project) -> void:
|
|||
for cel in frame.cels:
|
||||
var image := Image.new()
|
||||
image.copy_from(cel.get_image())
|
||||
var duration := frame.duration * (1.0 / project.fps)
|
||||
var duration := frame.get_duration_in_seconds(project.fps)
|
||||
processed_images.append(
|
||||
ProcessedImage.new(image, project.frames.find(frame), duration)
|
||||
)
|
||||
|
@ -298,7 +298,7 @@ func process_animation(project := Global.current_project) -> void:
|
|||
image.copy_from(crop)
|
||||
if trim_images:
|
||||
image = image.get_region(image.get_used_rect())
|
||||
var duration := frame.duration * (1.0 / project.fps)
|
||||
var duration := frame.get_duration_in_seconds(project.fps)
|
||||
processed_images.append(ProcessedImage.new(image, project.frames.find(frame), duration))
|
||||
|
||||
|
||||
|
@ -427,7 +427,7 @@ func export_processed_images(
|
|||
|
||||
if is_single_file_format(project):
|
||||
if is_using_ffmpeg(project.file_format):
|
||||
var video_exported := export_video(export_paths)
|
||||
var video_exported := export_video(export_paths, project)
|
||||
if not video_exported:
|
||||
Global.popup_error(
|
||||
tr("Video failed to export. Ensure that FFMPEG is installed correctly.")
|
||||
|
@ -505,8 +505,9 @@ func export_processed_images(
|
|||
|
||||
|
||||
## Uses FFMPEG to export a video
|
||||
func export_video(export_paths: PackedStringArray) -> bool:
|
||||
func export_video(export_paths: PackedStringArray, project: Project) -> bool:
|
||||
DirAccess.make_dir_absolute(TEMP_PATH)
|
||||
var video_duration := 0
|
||||
var temp_path_real := ProjectSettings.globalize_path(TEMP_PATH)
|
||||
var input_file_path := temp_path_real.path_join("input.txt")
|
||||
var input_file := FileAccess.open(input_file_path, FileAccess.WRITE)
|
||||
|
@ -516,25 +517,80 @@ func export_video(export_paths: PackedStringArray) -> bool:
|
|||
processed_images[i].image.save_png(temp_file_path)
|
||||
input_file.store_line("file '" + temp_file_name + "'")
|
||||
input_file.store_line("duration %s" % processed_images[i].duration)
|
||||
video_duration += processed_images[i].duration
|
||||
input_file.close()
|
||||
|
||||
# ffmpeg -y -f concat -i input.txt output_path
|
||||
var ffmpeg_execute: PackedStringArray = [
|
||||
"-y", "-f", "concat", "-i", input_file_path, export_paths[0]
|
||||
]
|
||||
var output := []
|
||||
var success := OS.execute(Global.ffmpeg_path, ffmpeg_execute, output, true)
|
||||
print(output)
|
||||
var temp_dir := DirAccess.open(TEMP_PATH)
|
||||
for file in temp_dir.get_files():
|
||||
temp_dir.remove(file)
|
||||
DirAccess.remove_absolute(TEMP_PATH)
|
||||
var success := OS.execute(Global.ffmpeg_path, ffmpeg_execute, [], true)
|
||||
if success < 0 or success > 1:
|
||||
var fail_text := """Video failed to export. Make sure you have FFMPEG installed
|
||||
and have set the correct path in the preferences."""
|
||||
Global.popup_error(tr(fail_text))
|
||||
_clear_temp_folder()
|
||||
return false
|
||||
# Find audio layers
|
||||
var ffmpeg_combine_audio: PackedStringArray = ["-y"]
|
||||
var audio_layer_count := 0
|
||||
var max_audio_duration := 0
|
||||
var adelay_string := ""
|
||||
for layer in project.get_all_audio_layers():
|
||||
if layer.audio is AudioStreamMP3:
|
||||
var temp_file_name := str(audio_layer_count + 1).pad_zeros(number_of_digits) + ".mp3"
|
||||
var temp_file_path := temp_path_real.path_join(temp_file_name)
|
||||
var temp_audio_file := FileAccess.open(temp_file_path, FileAccess.WRITE)
|
||||
temp_audio_file.store_buffer(layer.audio.data)
|
||||
ffmpeg_combine_audio.append("-i")
|
||||
ffmpeg_combine_audio.append(temp_file_path)
|
||||
var delay := floori(layer.playback_position * 1000)
|
||||
# [n]adelay=delay_in_ms:all=1[na]
|
||||
adelay_string += (
|
||||
"[%s]adelay=%s:all=1[%sa];" % [audio_layer_count, delay, audio_layer_count]
|
||||
)
|
||||
audio_layer_count += 1
|
||||
if layer.get_audio_length() >= max_audio_duration:
|
||||
max_audio_duration = layer.get_audio_length()
|
||||
if audio_layer_count > 0:
|
||||
# If we have audio layers, merge them all into one file.
|
||||
for i in audio_layer_count:
|
||||
adelay_string += "[%sa]" % i
|
||||
var amix_inputs_string := "amix=inputs=%s[a]" % audio_layer_count
|
||||
var final_filter_string := adelay_string + amix_inputs_string
|
||||
var audio_file_path := temp_path_real.path_join("audio.mp3")
|
||||
ffmpeg_combine_audio.append_array(
|
||||
PackedStringArray(
|
||||
["-filter_complex", final_filter_string, "-map", '"[a]"', audio_file_path]
|
||||
)
|
||||
)
|
||||
# ffmpeg -i input1 -i input2 ... -i inputn -filter_complex amix=inputs=n output_path
|
||||
var combined_audio_success := OS.execute(Global.ffmpeg_path, ffmpeg_combine_audio, [], true)
|
||||
if combined_audio_success == 0 or combined_audio_success == 1:
|
||||
var copied_video := temp_path_real.path_join("video." + export_paths[0].get_extension())
|
||||
# Then mix the audio file with the video.
|
||||
DirAccess.copy_absolute(export_paths[0], copied_video)
|
||||
# ffmpeg -y -i video_file -i input_audio -c:v copy -map 0:v:0 -map 1:a:0 video_file
|
||||
var ffmpeg_final_video: PackedStringArray = [
|
||||
"-y", "-i", copied_video, "-i", audio_file_path
|
||||
]
|
||||
if max_audio_duration > video_duration:
|
||||
ffmpeg_final_video.append("-shortest")
|
||||
ffmpeg_final_video.append_array(
|
||||
["-c:v", "copy", "-map", "0:v:0", "-map", "1:a:0", export_paths[0]]
|
||||
)
|
||||
OS.execute(Global.ffmpeg_path, ffmpeg_final_video, [], true)
|
||||
_clear_temp_folder()
|
||||
return true
|
||||
|
||||
|
||||
func _clear_temp_folder() -> void:
|
||||
var temp_dir := DirAccess.open(TEMP_PATH)
|
||||
for file in temp_dir.get_files():
|
||||
temp_dir.remove(file)
|
||||
DirAccess.remove_absolute(TEMP_PATH)
|
||||
|
||||
|
||||
func export_animated(args: Dictionary) -> void:
|
||||
var project: Project = args["project"]
|
||||
var exporter: AImgIOBaseExporter = args["exporter"]
|
||||
|
|
|
@ -631,7 +631,7 @@ class ProjectAPI:
|
|||
|
||||
## Returns the current cel.
|
||||
## Cel type can be checked using function [method get_class_name] inside the cel
|
||||
## type can be GroupCel, PixelCel, Cel3D, or BaseCel.
|
||||
## type can be GroupCel, PixelCel, Cel3D, CelTileMap, AudioCel or BaseCel.
|
||||
func get_current_cel() -> BaseCel:
|
||||
return current_project.get_current_cel()
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ signal cel_switched ## Emitted whenever you select a different cel.
|
|||
signal project_data_changed(project: Project) ## Emitted when project data is modified.
|
||||
signal font_loaded ## Emitted when a new font has been loaded, or an old one gets unloaded.
|
||||
|
||||
enum LayerTypes { PIXEL, GROUP, THREE_D, TILEMAP }
|
||||
enum LayerTypes { PIXEL, GROUP, THREE_D, TILEMAP, AUDIO }
|
||||
enum GridTypes { CARTESIAN, ISOMETRIC, ALL }
|
||||
## ## Used to tell whether a color is being taken from the current theme,
|
||||
## or if it is a custom color.
|
||||
|
@ -64,13 +64,14 @@ enum EffectsMenu {
|
|||
DESATURATION,
|
||||
HSV,
|
||||
BRIGHTNESS_SATURATION,
|
||||
COLOR_CURVES,
|
||||
PALETTIZE,
|
||||
PIXELIZE,
|
||||
POSTERIZE,
|
||||
GAUSSIAN_BLUR,
|
||||
GRADIENT,
|
||||
GRADIENT_MAP,
|
||||
SHADER
|
||||
LOADED_EFFECTS
|
||||
}
|
||||
## Enumeration of items present in the Select Menu.
|
||||
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE, MODIFY }
|
||||
|
@ -490,6 +491,11 @@ var window_transparency := false:
|
|||
return
|
||||
window_transparency = value
|
||||
_save_to_override_file()
|
||||
var dummy_audio_driver := false:
|
||||
set(value):
|
||||
if value != dummy_audio_driver:
|
||||
dummy_audio_driver = value
|
||||
_save_to_override_file()
|
||||
|
||||
## Found in Preferences. The time (in minutes) after which backup is created (if enabled).
|
||||
var autosave_interval := 1.0:
|
||||
|
@ -726,6 +732,7 @@ func _init() -> void:
|
|||
window_transparency = ProjectSettings.get_setting(
|
||||
"display/window/per_pixel_transparency/allowed"
|
||||
)
|
||||
dummy_audio_driver = ProjectSettings.get_setting("audio/driver/driver") == "Dummy"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
@ -803,6 +810,7 @@ func _initialize_keychain() -> void:
|
|||
&"drop_shadow": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"adjust_hsv": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"adjust_brightness_contrast": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"color_curves": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"gaussian_blur": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"gradient": Keychain.InputAction.new("", "Effects menu", true),
|
||||
&"gradient_map": Keychain.InputAction.new("", "Effects menu", true),
|
||||
|
@ -1187,3 +1195,6 @@ func _save_to_override_file() -> void:
|
|||
file.store_line("[display]\n")
|
||||
file.store_line("window/subwindows/embed_subwindows=%s" % single_window_mode)
|
||||
file.store_line("window/per_pixel_transparency/allowed=%s" % window_transparency)
|
||||
if dummy_audio_driver:
|
||||
file.store_line("[audio]\n")
|
||||
file.store_line('driver/driver="Dummy"')
|
||||
|
|
|
@ -3,6 +3,9 @@ extends Node
|
|||
|
||||
signal project_saved
|
||||
signal reference_image_imported
|
||||
signal shader_copied(file_path: String)
|
||||
|
||||
const SHADERS_DIRECTORY := "user://shaders"
|
||||
|
||||
var preview_dialog_tscn := preload("res://src/UI/Dialogs/ImportPreviewDialog.tscn")
|
||||
var preview_dialogs := [] ## Array of preview dialogs
|
||||
|
@ -39,12 +42,15 @@ func handle_loading_file(file: String) -> void:
|
|||
elif file_ext in ["pck", "zip"]: # Godot resource pack file
|
||||
Global.control.get_node("Extensions").install_extension(file)
|
||||
|
||||
elif file_ext == "shader" or file_ext == "gdshader": # Godot shader file
|
||||
elif file_ext == "gdshader": # Godot shader file
|
||||
var shader := load(file)
|
||||
if not shader is Shader:
|
||||
return
|
||||
var file_name: String = file.get_file().get_basename()
|
||||
Global.control.find_child("ShaderEffect").change_shader(shader, file_name)
|
||||
var new_path := SHADERS_DIRECTORY.path_join(file.get_file())
|
||||
DirAccess.copy_absolute(file, new_path)
|
||||
shader_copied.emit(new_path)
|
||||
elif file_ext == "mp3": # Audio file
|
||||
open_audio_file(file)
|
||||
|
||||
else: # Image files
|
||||
# Attempt to load as APNG.
|
||||
|
@ -185,8 +191,8 @@ func handle_loading_video(file: String) -> bool:
|
|||
project_size.x = temp_image.get_width()
|
||||
if temp_image.get_height() > project_size.y:
|
||||
project_size.y = temp_image.get_height()
|
||||
DirAccess.remove_absolute(Export.TEMP_PATH)
|
||||
if images_to_import.size() == 0 or project_size == Vector2i.ZERO:
|
||||
DirAccess.remove_absolute(Export.TEMP_PATH)
|
||||
return false # We didn't find any images, return
|
||||
# If we found images, create a new project out of them
|
||||
var new_project := Project.new([], file.get_basename().get_file(), project_size)
|
||||
|
@ -196,6 +202,14 @@ func handle_loading_video(file: String) -> bool:
|
|||
Global.projects.append(new_project)
|
||||
Global.tabs.current_tab = Global.tabs.get_tab_count() - 1
|
||||
Global.canvas.camera_zoom()
|
||||
var output_audio_file := temp_path_real.path_join("audio.mp3")
|
||||
# ffmpeg -y -i input_file -vn audio.mp3
|
||||
var ffmpeg_execute_audio: PackedStringArray = ["-y", "-i", file, "-vn", output_audio_file]
|
||||
OS.execute(Global.ffmpeg_path, ffmpeg_execute_audio, [], true)
|
||||
if FileAccess.file_exists(output_audio_file):
|
||||
open_audio_file(output_audio_file)
|
||||
temp_dir.remove("audio.mp3")
|
||||
DirAccess.remove_absolute(Export.TEMP_PATH)
|
||||
return true
|
||||
|
||||
|
||||
|
@ -438,6 +452,14 @@ func save_pxo_file(
|
|||
zip_packer.start_file(tileset_path.path_join(str(j)))
|
||||
zip_packer.write_file(tile.image.get_data())
|
||||
zip_packer.close_file()
|
||||
var audio_layers := project.get_all_audio_layers()
|
||||
for i in audio_layers.size():
|
||||
var layer := audio_layers[i]
|
||||
var audio_path := "audio/%s" % i
|
||||
if layer.audio is AudioStreamMP3:
|
||||
zip_packer.start_file(audio_path)
|
||||
zip_packer.write_file(layer.audio.data)
|
||||
zip_packer.close_file()
|
||||
zip_packer.close()
|
||||
|
||||
if temp_path != path:
|
||||
|
@ -902,6 +924,23 @@ func set_new_imported_tab(project: Project, path: String) -> void:
|
|||
Global.tabs.delete_tab(prev_project_pos)
|
||||
|
||||
|
||||
func open_audio_file(path: String) -> void:
|
||||
var audio_stream: AudioStream
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
audio_stream = AudioStreamMP3.new()
|
||||
audio_stream.data = file.get_buffer(file.get_length())
|
||||
if not is_instance_valid(audio_stream):
|
||||
return
|
||||
var project := Global.current_project
|
||||
for layer in project.layers:
|
||||
if layer is AudioLayer and not is_instance_valid(layer.audio):
|
||||
layer.audio = audio_stream
|
||||
return
|
||||
var new_layer := AudioLayer.new(project, path.get_basename().get_file())
|
||||
new_layer.audio = audio_stream
|
||||
Global.animation_timeline.add_layer(new_layer, project)
|
||||
|
||||
|
||||
func update_autosave() -> void:
|
||||
if not is_instance_valid(autosave_timer):
|
||||
return
|
||||
|
|
|
@ -800,7 +800,10 @@ func _cel_switched() -> void:
|
|||
var layer: BaseLayer = Global.current_project.layers[Global.current_project.current_layer]
|
||||
var layer_type := layer.get_layer_type()
|
||||
# Do not make any changes when its the same type of layer, or a group layer
|
||||
if layer_type == _curr_layer_type or layer_type == Global.LayerTypes.GROUP:
|
||||
if (
|
||||
layer_type == _curr_layer_type
|
||||
or layer_type in [Global.LayerTypes.GROUP, Global.LayerTypes.AUDIO]
|
||||
):
|
||||
return
|
||||
_show_relevant_tools(layer_type)
|
||||
|
||||
|
|
18
src/Classes/Cels/AudioCel.gd
Normal file
18
src/Classes/Cels/AudioCel.gd
Normal file
|
@ -0,0 +1,18 @@
|
|||
class_name AudioCel
|
||||
extends BaseCel
|
||||
## A class for the properties of cels in AudioLayers.
|
||||
## The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel).
|
||||
|
||||
|
||||
func _init(_opacity := 1.0) -> void:
|
||||
opacity = _opacity
|
||||
image_texture = ImageTexture.new()
|
||||
|
||||
|
||||
func get_image() -> Image:
|
||||
var image := Global.current_project.new_empty_image()
|
||||
return image
|
||||
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "AudioCel"
|
|
@ -25,7 +25,13 @@ func get_content() -> ImageExtended:
|
|||
|
||||
|
||||
func set_content(content, texture: ImageTexture = null) -> void:
|
||||
image = content
|
||||
var proper_content: ImageExtended
|
||||
if content is not ImageExtended:
|
||||
proper_content = ImageExtended.new()
|
||||
proper_content.copy_from_custom(content, image.is_indexed)
|
||||
else:
|
||||
proper_content = content
|
||||
image = proper_content
|
||||
if is_instance_valid(texture) and is_instance_valid(texture.get_image()):
|
||||
image_texture = texture
|
||||
if image_texture.get_image().get_size() != image.get_size():
|
||||
|
|
|
@ -11,3 +11,26 @@ var user_data := "" ## User defined data, set in the frame properties.
|
|||
func _init(_cels: Array[BaseCel] = [], _duration := 1.0) -> void:
|
||||
cels = _cels
|
||||
duration = _duration
|
||||
|
||||
|
||||
func get_duration_in_seconds(fps: float) -> float:
|
||||
return duration * (1.0 / fps)
|
||||
|
||||
|
||||
func position_in_seconds(project: Project, start_from := 0) -> float:
|
||||
var pos := 0.0
|
||||
var index := project.frames.find(self)
|
||||
if index > start_from:
|
||||
for i in range(start_from, index):
|
||||
if i >= 0:
|
||||
var frame := project.frames[i]
|
||||
pos += frame.get_duration_in_seconds(project.fps)
|
||||
else:
|
||||
pos += 1.0 / project.fps
|
||||
else:
|
||||
if start_from >= project.frames.size():
|
||||
return -1.0
|
||||
for i in range(start_from, index, -1):
|
||||
var frame := project.frames[i]
|
||||
pos -= frame.get_duration_in_seconds(project.fps)
|
||||
return pos
|
||||
|
|
|
@ -144,8 +144,10 @@ func set_nodes() -> void:
|
|||
selection_checkbox = $VBoxContainer/OptionsContainer/SelectionCheckBox
|
||||
affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton
|
||||
animate_panel = $"%AnimatePanel"
|
||||
animate_panel.image_effect_node = self
|
||||
live_checkbox.button_pressed = live_preview
|
||||
if is_instance_valid(animate_panel):
|
||||
animate_panel.image_effect_node = self
|
||||
if is_instance_valid(live_checkbox):
|
||||
live_checkbox.button_pressed = live_preview
|
||||
|
||||
|
||||
func display_animate_dialog() -> void:
|
||||
|
|
74
src/Classes/Layers/AudioLayer.gd
Normal file
74
src/Classes/Layers/AudioLayer.gd
Normal file
|
@ -0,0 +1,74 @@
|
|||
class_name AudioLayer
|
||||
extends BaseLayer
|
||||
## A unique type of layer which acts as an audio track for the timeline.
|
||||
## Each audio layer has one audio stream, and its starting position can be
|
||||
## in any point during the animation.
|
||||
|
||||
signal audio_changed
|
||||
signal playback_frame_changed
|
||||
|
||||
var audio: AudioStream: ## The audio stream of the layer.
|
||||
set(value):
|
||||
audio = value
|
||||
audio_changed.emit()
|
||||
var playback_position := 0.0: ## The time in seconds where the audio stream starts playing.
|
||||
get():
|
||||
if playback_frame >= 0:
|
||||
var frame := project.frames[playback_frame]
|
||||
return frame.position_in_seconds(project)
|
||||
var pos := 0.0
|
||||
for i in absi(playback_frame):
|
||||
pos -= 1.0 / project.fps
|
||||
return pos
|
||||
var playback_frame := 0: ## The frame where the audio stream starts playing.
|
||||
set(value):
|
||||
playback_frame = value
|
||||
playback_frame_changed.emit()
|
||||
|
||||
|
||||
func _init(_project: Project, _name := "") -> void:
|
||||
project = _project
|
||||
name = _name
|
||||
|
||||
|
||||
## Returns the length of the audio stream.
|
||||
func get_audio_length() -> float:
|
||||
if is_instance_valid(audio):
|
||||
return audio.get_length()
|
||||
else:
|
||||
return -1.0
|
||||
|
||||
|
||||
## Returns the class name of the audio stream. E.g. "AudioStreamMP3".
|
||||
func get_audio_type() -> String:
|
||||
if not is_instance_valid(audio):
|
||||
return ""
|
||||
return audio.get_class()
|
||||
|
||||
|
||||
# Overridden Methods:
|
||||
func serialize() -> Dictionary:
|
||||
var data := {
|
||||
"name": name,
|
||||
"type": get_layer_type(),
|
||||
"playback_frame": playback_frame,
|
||||
"audio_type": get_audio_type()
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
func deserialize(dict: Dictionary) -> void:
|
||||
super.deserialize(dict)
|
||||
playback_frame = dict.get("playback_frame", playback_frame)
|
||||
|
||||
|
||||
func get_layer_type() -> int:
|
||||
return Global.LayerTypes.AUDIO
|
||||
|
||||
|
||||
func new_empty_cel() -> AudioCel:
|
||||
return AudioCel.new()
|
||||
|
||||
|
||||
func set_name_to_default(number: int) -> void:
|
||||
name = tr("Audio") + " %s" % number
|
|
@ -232,7 +232,7 @@ func display_effects(cel: BaseCel, image_override: Image = null) -> Image:
|
|||
return image
|
||||
var image_size := image.get_size()
|
||||
for effect in effects:
|
||||
if not effect.enabled:
|
||||
if not effect.enabled or not is_instance_valid(effect.shader):
|
||||
continue
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
shader_image_effect.generate_image(image, effect.shader, effect.params, image_size)
|
||||
|
|
|
@ -8,6 +8,7 @@ signal serialized(dict: Dictionary)
|
|||
signal about_to_deserialize(dict: Dictionary)
|
||||
signal resized
|
||||
signal timeline_updated
|
||||
signal fps_changed
|
||||
|
||||
const INDEXED_MODE := Image.FORMAT_MAX + 1
|
||||
|
||||
|
@ -65,7 +66,10 @@ var brushes: Array[Image] = []
|
|||
var reference_images: Array[ReferenceImage] = []
|
||||
var reference_index: int = -1 # The currently selected index ReferenceImage
|
||||
var vanishing_points := [] ## Array of Vanishing Points
|
||||
var fps := 6.0
|
||||
var fps := 6.0:
|
||||
set(value):
|
||||
fps = value
|
||||
fps_changed.emit()
|
||||
var user_data := "" ## User defined data, set in the project properties.
|
||||
|
||||
var x_symmetry_point: float
|
||||
|
@ -356,6 +360,7 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces
|
|||
tileset.deserialize(saved_tileset)
|
||||
tilesets.append(tileset)
|
||||
if dict.has("frames") and dict.has("layers"):
|
||||
var audio_layers := 0
|
||||
for saved_layer in dict.layers:
|
||||
match int(saved_layer.get("type", Global.LayerTypes.PIXEL)):
|
||||
Global.LayerTypes.PIXEL:
|
||||
|
@ -366,6 +371,18 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces
|
|||
layers.append(Layer3D.new(self))
|
||||
Global.LayerTypes.TILEMAP:
|
||||
layers.append(LayerTileMap.new(self, null))
|
||||
Global.LayerTypes.AUDIO:
|
||||
var layer := AudioLayer.new(self)
|
||||
var audio_path := "audio/%s" % audio_layers
|
||||
if zip_reader.file_exists(audio_path):
|
||||
var audio_data := zip_reader.read_file(audio_path)
|
||||
var stream: AudioStream
|
||||
if saved_layer.get("audio_type", "") == "AudioStreamMP3":
|
||||
stream = AudioStreamMP3.new()
|
||||
stream.data = audio_data
|
||||
layer.audio = stream
|
||||
layers.append(layer)
|
||||
audio_layers += 1
|
||||
|
||||
var frame_i := 0
|
||||
for frame in dict.frames:
|
||||
|
@ -390,6 +407,8 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces
|
|||
var tileset := tilesets[tileset_index]
|
||||
var new_cel := CelTileMap.new(tileset, image)
|
||||
cels.append(new_cel)
|
||||
Global.LayerTypes.AUDIO:
|
||||
cels.append(AudioCel.new())
|
||||
cel["pxo_version"] = pxo_version
|
||||
cels[cel_i].deserialize(cel)
|
||||
_deserialize_metadata(cels[cel_i], cel)
|
||||
|
@ -640,10 +659,10 @@ func find_first_drawable_cel(frame := frames[current_frame]) -> BaseCel:
|
|||
var result: BaseCel
|
||||
var cel := frame.cels[0]
|
||||
var i := 0
|
||||
while cel is GroupCel and i < layers.size():
|
||||
while (cel is GroupCel or cel is AudioCel) and i < layers.size():
|
||||
cel = frame.cels[i]
|
||||
i += 1
|
||||
if not cel is GroupCel:
|
||||
if cel is not GroupCel and cel is not AudioCel:
|
||||
result = cel
|
||||
return result
|
||||
|
||||
|
@ -658,6 +677,18 @@ func get_all_pixel_cels() -> Array[PixelCel]:
|
|||
return cels
|
||||
|
||||
|
||||
func get_all_audio_layers(only_valid_streams := true) -> Array[AudioLayer]:
|
||||
var audio_layers: Array[AudioLayer]
|
||||
for layer in layers:
|
||||
if layer is AudioLayer:
|
||||
if only_valid_streams:
|
||||
if is_instance_valid(layer.audio):
|
||||
audio_layers.append(layer)
|
||||
else:
|
||||
audio_layers.append(layer)
|
||||
return audio_layers
|
||||
|
||||
|
||||
## Reads data from [param cels] and appends them to [param data],
|
||||
## to be used for the undo/redo system.
|
||||
## It adds data such as the images of [PixelCel]s,
|
||||
|
|
|
@ -252,6 +252,17 @@ static func create_ui_for_shader_uniforms(
|
|||
func(_gradient, _cc): value_changed.call(gradient_edit.texture, u_name)
|
||||
)
|
||||
hbox.add_child(gradient_edit)
|
||||
elif u_name.begins_with("curve_"):
|
||||
var curve_edit := CurveEdit.new()
|
||||
curve_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
if params.has(u_name) and params[u_name] is CurveTexture:
|
||||
curve_edit.curve = params[u_name].curve
|
||||
else:
|
||||
curve_edit.set_default_curve()
|
||||
curve_edit.value_changed.connect(
|
||||
func(curve: Curve): value_changed.call(CurveEdit.to_texture(curve), u_name)
|
||||
)
|
||||
hbox.add_child(curve_edit)
|
||||
else: ## Simple texture
|
||||
var file_dialog := FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
|
|
|
@ -64,6 +64,7 @@ func draw_palette() -> void:
|
|||
var grid_index := i + grid_size.x * j
|
||||
var index := convert_grid_index_to_palette_index(grid_index)
|
||||
var swatch := swatches[grid_index]
|
||||
swatch.color_index = index
|
||||
swatch.show_left_highlight = Palettes.left_selected_color == index
|
||||
swatch.show_right_highlight = Palettes.right_selected_color == index
|
||||
var color = current_palette.get_color(index)
|
||||
|
|
|
@ -8,6 +8,7 @@ signal dropped(source_index: int, new_index: int)
|
|||
const DEFAULT_COLOR := Color(0.0, 0.0, 0.0, 0.0)
|
||||
|
||||
var index := -1
|
||||
var color_index := -1
|
||||
var show_left_highlight := false
|
||||
var show_right_highlight := false
|
||||
var empty := true:
|
||||
|
@ -48,6 +49,23 @@ func _draw() -> void:
|
|||
draw_rect(
|
||||
Rect2(margin - Vector2.ONE, size - margin * 2 + Vector2(2, 2)), Color.WHITE, false, 1
|
||||
)
|
||||
if Global.show_pixel_indices:
|
||||
var font := Themes.get_font()
|
||||
var str_pos := Vector2(size.x / 2, size.y - 2)
|
||||
var text_color := Global.control.theme.get_color(&"font_color", &"Label")
|
||||
draw_string_outline(
|
||||
font,
|
||||
str_pos,
|
||||
str(color_index),
|
||||
HORIZONTAL_ALIGNMENT_RIGHT,
|
||||
-1,
|
||||
size.x / 2,
|
||||
1,
|
||||
text_color.inverted()
|
||||
)
|
||||
draw_string(
|
||||
font, str_pos, str(color_index), HORIZONTAL_ALIGNMENT_RIGHT, -1, size.x / 2, text_color
|
||||
)
|
||||
|
||||
|
||||
## Enables drawing of highlights which indicate selected swatches
|
||||
|
|
|
@ -181,6 +181,13 @@ var preferences: Array[Preference] = [
|
|||
false,
|
||||
true
|
||||
),
|
||||
Preference.new(
|
||||
"dummy_audio_driver",
|
||||
"Performance/PerformanceContainer/DummyAudioDriver",
|
||||
"button_pressed",
|
||||
false,
|
||||
true
|
||||
),
|
||||
Preference.new("tablet_driver", "Drivers/DriversContainer/TabletDriver", "selected", 0)
|
||||
]
|
||||
|
||||
|
|
|
@ -1142,18 +1142,28 @@ mouse_default_cursor_shape = 2
|
|||
button_pressed = true
|
||||
text = "On"
|
||||
|
||||
[node name="WindowTransparencyLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Performance/PerformanceContainer"]
|
||||
[node name="WindowTransparencyLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Performance/PerformanceContainer" groups=["DesktopOnly"]]
|
||||
layout_mode = 2
|
||||
tooltip_text = "If enabled, the application window can become transparent. This affects performance, so keep it off if you don't need it."
|
||||
mouse_filter = 0
|
||||
text = "Enable window transparency"
|
||||
|
||||
[node name="WindowTransparency" type="CheckBox" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Performance/PerformanceContainer"]
|
||||
[node name="WindowTransparency" type="CheckBox" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Performance/PerformanceContainer" groups=["DesktopOnly"]]
|
||||
layout_mode = 2
|
||||
tooltip_text = "If enabled, the application window can become transparent. This affects performance, so keep it off if you don't need it."
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "On"
|
||||
|
||||
[node name="DummyAudioDriverLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Performance/PerformanceContainer" groups=["DesktopOnly"]]
|
||||
layout_mode = 2
|
||||
mouse_filter = 0
|
||||
text = "Use dummy audio driver"
|
||||
|
||||
[node name="DummyAudioDriver" type="CheckBox" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Performance/PerformanceContainer" groups=["DesktopOnly"]]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "On"
|
||||
|
||||
[node name="Drivers" type="VBoxContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
@ -1473,6 +1483,7 @@ text = "Pixelorama must be restarted for changes to take effect."
|
|||
mode = 1
|
||||
title = "Open File(s)"
|
||||
size = Vector2i(560, 400)
|
||||
always_on_top = true
|
||||
ok_button_text = "Open"
|
||||
file_mode = 1
|
||||
access = 2
|
||||
|
@ -1484,6 +1495,7 @@ transient = true
|
|||
|
||||
[node name="EnableExtensionConfirmation" type="ConfirmationDialog" parent="."]
|
||||
unique_name_in_owner = true
|
||||
always_on_top = true
|
||||
dialog_text = "Are you sure you want to enable this extension? Make sure to only enable extensions from sources that you trust."
|
||||
dialog_autowrap = true
|
||||
|
||||
|
@ -1491,11 +1503,13 @@ dialog_autowrap = true
|
|||
unique_name_in_owner = true
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(400, 100)
|
||||
always_on_top = true
|
||||
ok_button_text = "Delete Permanently"
|
||||
dialog_text = "Are you sure you want to delete this extension?"
|
||||
dialog_autowrap = true
|
||||
|
||||
[node name="ResetOptionsConfirmation" type="ConfirmationDialog" parent="."]
|
||||
always_on_top = true
|
||||
dialog_text = "Are you sure you want to reset the selected options? There will be no way to undo this."
|
||||
|
||||
[connection signal="about_to_popup" from="." to="." method="_on_PreferencesDialog_about_to_show"]
|
||||
|
|
67
src/Shaders/Effects/ColorCurves.gdshader
Normal file
67
src/Shaders/Effects/ColorCurves.gdshader
Normal file
|
@ -0,0 +1,67 @@
|
|||
shader_type canvas_item;
|
||||
|
||||
// CurveTexture(s)
|
||||
uniform sampler2D curve_rgb;
|
||||
uniform sampler2D curve_red;
|
||||
uniform sampler2D curve_green;
|
||||
uniform sampler2D curve_blue;
|
||||
uniform sampler2D curve_alpha;
|
||||
uniform sampler2D curve_hue;
|
||||
uniform sampler2D curve_sat;
|
||||
uniform sampler2D curve_value;
|
||||
uniform sampler2D selection : filter_nearest;
|
||||
|
||||
|
||||
vec3 rgb2hsb(vec3 c) {
|
||||
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||
vec4 p = mix(vec4(c.bg, K.wz),
|
||||
vec4(c.gb, K.xy),
|
||||
step(c.b, c.g));
|
||||
vec4 q = mix(vec4(p.xyw, c.r),
|
||||
vec4(c.r, p.yzx),
|
||||
step(p.x, c.r));
|
||||
float d = q.x - min(q.w, q.y);
|
||||
float e = 1.0e-10;
|
||||
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)),
|
||||
d / (q.x + e),
|
||||
q.x);
|
||||
}
|
||||
|
||||
vec3 hsb2rgb(vec3 c)
|
||||
{
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
|
||||
void fragment() {
|
||||
vec4 original_color = texture(TEXTURE, UV);
|
||||
vec4 selection_color = texture(selection, UV);
|
||||
vec4 col;
|
||||
float red_curve_color = texture(curve_red, vec2(COLOR.r, 0.0)).r;
|
||||
float green_curve_color = texture(curve_green, vec2(COLOR.g, 0.0)).r;
|
||||
float blue_curve_color = texture(curve_blue, vec2(COLOR.b, 0.0)).r;
|
||||
float alpha_curve_color = texture(curve_alpha, vec2(COLOR.a, 0.0)).r;
|
||||
col.r = red_curve_color;
|
||||
col.g = green_curve_color;
|
||||
col.b = blue_curve_color;
|
||||
col.a = alpha_curve_color;
|
||||
|
||||
vec3 hsb = rgb2hsb(col.rgb);
|
||||
float hue_curve_color = texture(curve_hue, vec2(hsb.r, 0.0)).r;
|
||||
float sat_curve_color = texture(curve_sat, vec2(hsb.g, 0.0)).r;
|
||||
float value_curve_color = texture(curve_value, vec2(hsb.b, 0.0)).r;
|
||||
hsb.r = hue_curve_color;
|
||||
hsb.g = sat_curve_color;
|
||||
hsb.b = value_curve_color;
|
||||
|
||||
col.rgb = hsb2rgb(hsb);
|
||||
|
||||
float rgb_curve_color_r = texture(curve_rgb, vec2(col.r, 0.0)).r;
|
||||
float rgb_curve_color_g = texture(curve_rgb, vec2(col.g, 0.0)).r;
|
||||
float rgb_curve_color_b = texture(curve_rgb, vec2(col.b, 0.0)).r;
|
||||
col.rgb = vec3(rgb_curve_color_r, rgb_curve_color_g, rgb_curve_color_b);
|
||||
vec4 output = mix(original_color.rgba, col, selection_color.a);
|
||||
COLOR = output;
|
||||
}
|
|
@ -212,8 +212,7 @@ func update_brush() -> void:
|
|||
$DensityValueSlider.visible = _brush.type not in IMAGE_BRUSHES
|
||||
$ColorInterpolation.visible = _brush.type in IMAGE_BRUSHES
|
||||
$RotationOptions.visible = _brush.type in IMAGE_BRUSHES
|
||||
var canvas_indicators := Global.canvas.indicators
|
||||
canvas_indicators.queue_redraw()
|
||||
Global.canvas.indicators.queue_redraw()
|
||||
|
||||
|
||||
func update_random_image() -> void:
|
||||
|
@ -378,6 +377,8 @@ func _prepare_tool() -> void:
|
|||
func _draw_tool(pos: Vector2) -> PackedVector2Array:
|
||||
if !Global.current_project.layers[Global.current_project.current_layer].can_layer_get_drawn():
|
||||
return PackedVector2Array() # empty fallback
|
||||
if Tools.is_placing_tiles():
|
||||
return _compute_draw_tool_pixel(pos)
|
||||
match _brush.type:
|
||||
Brushes.PIXEL:
|
||||
return _compute_draw_tool_pixel(pos)
|
||||
|
@ -428,9 +429,12 @@ func draw_fill_gap(start: Vector2i, end: Vector2i) -> void:
|
|||
|
||||
## Compute the array of coordinates that should be drawn
|
||||
func _compute_draw_tool_pixel(pos: Vector2) -> PackedVector2Array:
|
||||
var brush_size := _brush_size_dynamics
|
||||
if Tools.is_placing_tiles():
|
||||
brush_size = 1
|
||||
var result := PackedVector2Array()
|
||||
var start := pos - Vector2.ONE * (_brush_size_dynamics >> 1)
|
||||
var end := start + Vector2.ONE * _brush_size_dynamics
|
||||
var start := pos - Vector2.ONE * (brush_size >> 1)
|
||||
var end := start + Vector2.ONE * brush_size
|
||||
for y in range(start.y, end.y):
|
||||
for x in range(start.x, end.x):
|
||||
result.append(Vector2(x, y))
|
||||
|
|
|
@ -139,7 +139,7 @@ func text_to_pixels() -> void:
|
|||
var text := text_edit.text
|
||||
var color := tool_slot.color
|
||||
var font_ascent := font.get_ascent(text_size)
|
||||
var pos := Vector2(1, font_ascent + text_edit.get_theme_constant(&"line_spacing"))
|
||||
var pos := Vector2(0, font_ascent + text_edit.get_theme_constant(&"line_spacing"))
|
||||
pos += text_edit.position
|
||||
font.draw_multiline_string(ci_rid, pos, text, horizontal_alignment, -1, text_size, -1, color)
|
||||
|
||||
|
|
50
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd
Normal file
50
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd
Normal file
|
@ -0,0 +1,50 @@
|
|||
extends ImageEffect
|
||||
|
||||
enum Channel { RGB, RED, GREEN, BLUE, ALPHA, HUE, SATURATION, VALUE }
|
||||
const SHADER := preload("res://src/Shaders/Effects/ColorCurves.gdshader")
|
||||
|
||||
var curves: Array[Curve]
|
||||
@onready var channel_option_button := %ChannelOptionButton as OptionButton
|
||||
@onready var curve_edit := $VBoxContainer/CurveEdit as CurveEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
super._ready()
|
||||
var sm := ShaderMaterial.new()
|
||||
sm.shader = SHADER
|
||||
preview.set_material(sm)
|
||||
for i in channel_option_button.item_count:
|
||||
var curve := Curve.new()
|
||||
curve.add_point(Vector2.ZERO, 0, 1, Curve.TANGENT_LINEAR)
|
||||
curve.add_point(Vector2.ONE, 1, 0, Curve.TANGENT_LINEAR)
|
||||
curves.append(curve)
|
||||
curve_edit.curve = curves[Channel.RGB]
|
||||
|
||||
|
||||
func commit_action(cel: Image, project := Global.current_project) -> void:
|
||||
var selection_tex: ImageTexture
|
||||
if selection_checkbox.button_pressed and project.has_selection:
|
||||
var selection := project.selection_map.return_cropped_copy(project.size)
|
||||
selection_tex = ImageTexture.create_from_image(selection)
|
||||
|
||||
var params := {
|
||||
"curve_rgb": CurveEdit.to_texture(curves[Channel.RGB]),
|
||||
"curve_red": CurveEdit.to_texture(curves[Channel.RED]),
|
||||
"curve_green": CurveEdit.to_texture(curves[Channel.GREEN]),
|
||||
"curve_blue": CurveEdit.to_texture(curves[Channel.BLUE]),
|
||||
"curve_alpha": CurveEdit.to_texture(curves[Channel.ALPHA]),
|
||||
"curve_hue": CurveEdit.to_texture(curves[Channel.HUE]),
|
||||
"curve_sat": CurveEdit.to_texture(curves[Channel.SATURATION]),
|
||||
"curve_value": CurveEdit.to_texture(curves[Channel.VALUE]),
|
||||
"selection": selection_tex
|
||||
}
|
||||
if !has_been_confirmed:
|
||||
for param in params:
|
||||
preview.material.set_shader_parameter(param, params[param])
|
||||
else:
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(cel, SHADER, params, project.size)
|
||||
|
||||
|
||||
func _on_channel_option_button_item_selected(index: int) -> void:
|
||||
curve_edit.curve = curves[index]
|
61
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn
Normal file
61
src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn
Normal file
|
@ -0,0 +1,61 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://cthknpr74lawl"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bybqhhayl5ay5" path="res://src/UI/Dialogs/ImageEffects/ImageEffectParent.tscn" id="1_4g7xo"]
|
||||
[ext_resource type="Script" path="res://src/UI/Dialogs/ImageEffects/ColorCurvesDialog.gd" id="2_xkivc"]
|
||||
[ext_resource type="Script" path="res://src/UI/Nodes/CurveEditor/CurveEdit.gd" id="3_3yyhs"]
|
||||
|
||||
[sub_resource type="Curve" id="Curve_gvi51"]
|
||||
_data = [Vector2(0, 0), 0.0, 1.0, 0, 1, Vector2(1, 1), 1.0, 0.0, 1, 0]
|
||||
point_count = 2
|
||||
|
||||
[node name="ColorCurvesDialog" instance=ExtResource("1_4g7xo")]
|
||||
title = "Color Curves"
|
||||
size = Vector2i(362, 481)
|
||||
script = ExtResource("2_xkivc")
|
||||
|
||||
[node name="VBoxContainer" parent="." index="3"]
|
||||
offset_bottom = 432.0
|
||||
|
||||
[node name="ShowAnimate" parent="VBoxContainer" index="0"]
|
||||
visible = false
|
||||
|
||||
[node name="ColorOptions" type="GridContainer" parent="VBoxContainer" index="3"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="ChannelLabel" type="Label" parent="VBoxContainer/ColorOptions" index="0"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Channel:"
|
||||
|
||||
[node name="ChannelOptionButton" type="OptionButton" parent="VBoxContainer/ColorOptions" index="1"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
mouse_default_cursor_shape = 2
|
||||
selected = 0
|
||||
item_count = 8
|
||||
popup/item_0/text = "RGB"
|
||||
popup/item_1/text = "Red"
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Green"
|
||||
popup/item_2/id = 2
|
||||
popup/item_3/text = "Blue"
|
||||
popup/item_3/id = 3
|
||||
popup/item_4/text = "Alpha"
|
||||
popup/item_4/id = 4
|
||||
popup/item_5/text = "Hue"
|
||||
popup/item_5/id = 5
|
||||
popup/item_6/text = "Saturation"
|
||||
popup/item_6/id = 6
|
||||
popup/item_7/text = "Value"
|
||||
popup/item_7/id = 7
|
||||
|
||||
[node name="CurveEdit" type="VBoxContainer" parent="VBoxContainer" index="4"]
|
||||
custom_minimum_size = Vector2(32, 150)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("3_3yyhs")
|
||||
curve = SubResource("Curve_gvi51")
|
||||
|
||||
[connection signal="item_selected" from="VBoxContainer/ColorOptions/ChannelOptionButton" to="." method="_on_channel_option_button_item_selected"]
|
|
@ -3,8 +3,7 @@ extends ImageEffect
|
|||
var shader: Shader
|
||||
var params := {}
|
||||
|
||||
@onready var shader_loaded_label: Label = $VBoxContainer/ShaderLoadedLabel
|
||||
@onready var shader_params: BoxContainer = $VBoxContainer/ShaderParams
|
||||
@onready var shader_params := $VBoxContainer/ShaderParams as VBoxContainer
|
||||
|
||||
|
||||
func _about_to_popup() -> void:
|
||||
|
@ -17,36 +16,22 @@ func _about_to_popup() -> void:
|
|||
super._about_to_popup()
|
||||
|
||||
|
||||
func commit_action(cel: Image, project := Global.current_project) -> void:
|
||||
if !shader:
|
||||
return
|
||||
func set_nodes() -> void:
|
||||
aspect_ratio_container = $VBoxContainer/AspectRatioContainer
|
||||
preview = $VBoxContainer/AspectRatioContainer/Preview
|
||||
|
||||
|
||||
func commit_action(cel: Image, project := Global.current_project) -> void:
|
||||
if not is_instance_valid(shader):
|
||||
return
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(cel, shader, params, project.size)
|
||||
|
||||
|
||||
func _on_ChooseShader_pressed() -> void:
|
||||
if OS.get_name() == "Web":
|
||||
Html5FileExchange.load_shader()
|
||||
else:
|
||||
$FileDialog.popup_centered(Vector2(300, 340))
|
||||
|
||||
|
||||
func _on_FileDialog_file_selected(path: String) -> void:
|
||||
var shader_tmp = load(path)
|
||||
if !shader_tmp is Shader:
|
||||
return
|
||||
change_shader(shader_tmp, path.get_file().get_basename())
|
||||
|
||||
|
||||
func set_nodes() -> void:
|
||||
preview = $VBoxContainer/AspectRatioContainer/Preview
|
||||
|
||||
|
||||
func change_shader(shader_tmp: Shader, shader_name: String) -> void:
|
||||
shader = shader_tmp
|
||||
preview.material.shader = shader_tmp
|
||||
shader_loaded_label.text = tr("Shader loaded:") + " " + shader_name
|
||||
title = shader_name
|
||||
params.clear()
|
||||
for child in shader_params.get_children():
|
||||
child.queue_free()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://bkr47ocij684y"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://b1ola6loro5m7"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/UI/Dialogs/ImageEffects/ShaderEffect.gd" id="1"]
|
||||
[ext_resource type="PackedScene" uid="uid://3pmb60gpst7b" path="res://src/UI/Nodes/TransparentChecker.tscn" id="2"]
|
||||
|
@ -6,6 +6,8 @@
|
|||
[sub_resource type="ShaderMaterial" id="1"]
|
||||
|
||||
[node name="ShaderEffect" type="ConfirmationDialog"]
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(612, 350)
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
|
@ -15,17 +17,14 @@ anchor_bottom = 1.0
|
|||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -36.0
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "This is an experimental feature and may not be included in the stable version"
|
||||
offset_bottom = -49.0
|
||||
|
||||
[node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Preview" type="TextureRect" parent="VBoxContainer/AspectRatioContainer"]
|
||||
texture_filter = 1
|
||||
material = SubResource("1")
|
||||
custom_minimum_size = Vector2(200, 200)
|
||||
layout_mode = 2
|
||||
|
@ -39,22 +38,5 @@ anchors_preset = 0
|
|||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="ChooseShader" type="Button" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Choose Shader"
|
||||
|
||||
[node name="ShaderLoadedLabel" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "No shader loaded!"
|
||||
|
||||
[node name="ShaderParams" type="VBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FileDialog" type="FileDialog" parent="." groups=["FileDialogs"]]
|
||||
access = 2
|
||||
filters = PackedStringArray("*gdshader; Godot Shader File")
|
||||
show_hidden_files = true
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/ChooseShader" to="." method="_on_ChooseShader_pressed"]
|
||||
[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]
|
||||
|
|
|
@ -16,6 +16,7 @@ resource_name = "Layout"
|
|||
script = ExtResource("3")
|
||||
root = SubResource("1")
|
||||
hidden_tabs = {}
|
||||
windows = {}
|
||||
save_on_change = false
|
||||
|
||||
[node name="ManageLayouts" type="AcceptDialog"]
|
||||
|
@ -110,6 +111,7 @@ disabled = true
|
|||
text = "Delete"
|
||||
|
||||
[node name="LayoutSettings" type="ConfirmationDialog" parent="."]
|
||||
always_on_top = true
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="LayoutSettings"]
|
||||
offset_right = 40.0
|
||||
|
@ -131,12 +133,12 @@ text = "Copy from"
|
|||
[node name="LayoutFrom" type="OptionButton" parent="LayoutSettings/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
item_count = 1
|
||||
selected = 0
|
||||
item_count = 1
|
||||
popup/item_0/text = "Current layout"
|
||||
popup/item_0/id = 0
|
||||
|
||||
[node name="DeleteConfirmation" type="ConfirmationDialog" parent="."]
|
||||
always_on_top = true
|
||||
dialog_text = "Are you sure you want to delete this layout?"
|
||||
|
||||
[connection signal="about_to_popup" from="." to="." method="_on_ManageLayouts_about_to_show"]
|
||||
|
|
|
@ -194,7 +194,7 @@ func _on_ProgressTimer_timeout() -> void:
|
|||
update_progress()
|
||||
|
||||
|
||||
func _manage_enlarded_thumbnail_close() -> void:
|
||||
func _manage_enlarged_thumbnail_close() -> void:
|
||||
enlarged_picture.get_parent().hide()
|
||||
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ unique_name_in_owner = true
|
|||
[node name="Alert" type="AcceptDialog" parent="."]
|
||||
unique_name_in_owner = true
|
||||
size = Vector2i(421, 106)
|
||||
always_on_top = true
|
||||
|
||||
[node name="Text" type="Label" parent="Alert"]
|
||||
anchors_preset = 15
|
||||
|
@ -104,15 +105,16 @@ offset_right = -8.0
|
|||
offset_bottom = -49.0
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="EnlardedThumbnail" type="Window" parent="."]
|
||||
[node name="EnlargedThumbnail" type="Window" parent="."]
|
||||
title = "Image"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(440, 360)
|
||||
visible = false
|
||||
transient = true
|
||||
exclusive = true
|
||||
always_on_top = true
|
||||
|
||||
[node name="Enlarged" type="TextureRect" parent="EnlardedThumbnail"]
|
||||
[node name="Enlarged" type="TextureRect" parent="EnlargedThumbnail"]
|
||||
unique_name_in_owner = true
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
@ -131,5 +133,5 @@ stretch_mode = 5
|
|||
[connection signal="request_completed" from="DownloadRequest" to="." method="_on_DownloadRequest_request_completed"]
|
||||
[connection signal="close_requested" from="Alert" to="." method="_manage_alert_close"]
|
||||
[connection signal="focus_exited" from="Alert" to="." method="_manage_alert_close"]
|
||||
[connection signal="close_requested" from="EnlardedThumbnail" to="." method="_manage_enlarded_thumbnail_close"]
|
||||
[connection signal="focus_exited" from="EnlardedThumbnail" to="." method="_manage_enlarded_thumbnail_close"]
|
||||
[connection signal="close_requested" from="EnlargedThumbnail" to="." method="_manage_enlarged_thumbnail_close"]
|
||||
[connection signal="focus_exited" from="EnlargedThumbnail" to="." method="_manage_enlarged_thumbnail_close"]
|
||||
|
|
|
@ -12,6 +12,7 @@ size = Vector2i(760, 470)
|
|||
visible = false
|
||||
wrap_controls = true
|
||||
exclusive = true
|
||||
always_on_top = true
|
||||
script = ExtResource("1_pwcwi")
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
|
@ -24,9 +25,11 @@ offset_left = 7.0
|
|||
offset_top = 8.0
|
||||
offset_right = -7.0
|
||||
offset_bottom = -8.0
|
||||
current_tab = 0
|
||||
|
||||
[node name="Store" type="MarginContainer" parent="TabContainer"]
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="StoreMainContainer" type="HSplitContainer" parent="TabContainer/Store"]
|
||||
layout_mode = 2
|
||||
|
@ -68,6 +71,7 @@ size_flags_horizontal = 3
|
|||
[node name="Options" type="MarginContainer" parent="TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/Options"]
|
||||
layout_mode = 2
|
||||
|
@ -153,6 +157,7 @@ unique_name_in_owner = true
|
|||
position = Vector2i(0, 36)
|
||||
size = Vector2i(511, 326)
|
||||
unresizable = true
|
||||
always_on_top = true
|
||||
popup_window = true
|
||||
|
||||
[node name="Content" type="VBoxContainer" parent="Error"]
|
||||
|
@ -200,6 +205,7 @@ autowrap_mode = 3
|
|||
unique_name_in_owner = true
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(357, 110)
|
||||
always_on_top = true
|
||||
popup_window = true
|
||||
|
||||
[node name="Content" type="VBoxContainer" parent="ErrorCustom"]
|
||||
|
|
103
src/UI/Nodes/CurveEditor/CurveControlPoint.gd
Normal file
103
src/UI/Nodes/CurveEditor/CurveControlPoint.gd
Normal file
|
@ -0,0 +1,103 @@
|
|||
# Code taken and modified from Material Maker, licensed under MIT
|
||||
# gdlint: ignore=max-line-length
|
||||
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/control_point.gd
|
||||
class_name CurveEditControlPoint
|
||||
extends Control
|
||||
|
||||
signal moved(index: int)
|
||||
signal removed(index: int)
|
||||
|
||||
const OFFSET := Vector2(3, 3)
|
||||
|
||||
var moving := false
|
||||
|
||||
var min_x: float
|
||||
var max_x: float
|
||||
var min_y: float
|
||||
var max_y: float
|
||||
var left_slope: CurveEditTangentPoint
|
||||
var right_slope: CurveEditTangentPoint
|
||||
|
||||
@onready var parent := get_parent() as Control
|
||||
@onready var grandparent := parent.get_parent() as CurveEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
gui_input.connect(_on_gui_input)
|
||||
custom_minimum_size = Vector2(8, 8)
|
||||
left_slope = CurveEditTangentPoint.new()
|
||||
right_slope = CurveEditTangentPoint.new()
|
||||
add_child(left_slope)
|
||||
add_child(right_slope)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var color := Color.GRAY
|
||||
var current_scene := get_tree().current_scene
|
||||
if current_scene is Control:
|
||||
var current_theme := (current_scene as Control).theme
|
||||
color = current_theme.get_color("font_color", "Label")
|
||||
for c: CurveEditTangentPoint in get_children():
|
||||
if c.visible:
|
||||
draw_line(OFFSET, c.position + OFFSET, color)
|
||||
draw_rect(Rect2(0, 0, 7, 7), color)
|
||||
|
||||
|
||||
func initialize(curve: Curve, index: int) -> void:
|
||||
if not is_instance_valid(parent):
|
||||
await ready
|
||||
position = grandparent.transform_point(curve.get_point_position(index)) - OFFSET
|
||||
var left_tangent := curve.get_point_left_tangent(index)
|
||||
var right_tangent := curve.get_point_right_tangent(index)
|
||||
if left_tangent != INF:
|
||||
left_slope.position = (
|
||||
left_slope.distance * (parent.size * Vector2(1.0, -left_tangent)).normalized()
|
||||
)
|
||||
if right_tangent != INF:
|
||||
right_slope.position = (
|
||||
right_slope.distance * (parent.size * Vector2(1.0, -right_tangent)).normalized()
|
||||
)
|
||||
|
||||
|
||||
func set_control_point_visibility(left: bool, new_visible: bool) -> void:
|
||||
if not is_instance_valid(left_slope):
|
||||
await ready
|
||||
if left:
|
||||
left_slope.visible = new_visible
|
||||
else:
|
||||
right_slope.visible = new_visible
|
||||
|
||||
|
||||
func set_constraint(new_min_x: float, new_max_x: float, new_min_y: float, new_max_y: float) -> void:
|
||||
min_x = new_min_x
|
||||
max_x = new_max_x
|
||||
min_y = new_min_y
|
||||
max_y = new_max_y
|
||||
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
moving = true
|
||||
else:
|
||||
moving = false
|
||||
grandparent.update_controls()
|
||||
elif event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
|
||||
removed.emit(get_index())
|
||||
elif moving and event is InputEventMouseMotion:
|
||||
position += event.relative
|
||||
if position.x < min_x:
|
||||
position.x = min_x
|
||||
elif position.x > max_x:
|
||||
position.x = max_x
|
||||
if position.y < min_y:
|
||||
position.y = min_y
|
||||
elif position.y > max_y:
|
||||
position.y = max_y
|
||||
moved.emit(get_index())
|
||||
|
||||
|
||||
func update_tangents() -> void:
|
||||
queue_redraw()
|
||||
moved.emit(get_index())
|
273
src/UI/Nodes/CurveEditor/CurveEdit.gd
Normal file
273
src/UI/Nodes/CurveEditor/CurveEdit.gd
Normal file
|
@ -0,0 +1,273 @@
|
|||
# Code taken and modified from Material Maker, licensed under MIT
|
||||
# gdlint: ignore=max-line-length
|
||||
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/curve_view.gd
|
||||
# and
|
||||
# gdlint: ignore=max-line-length
|
||||
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/curve_editor.gd
|
||||
@tool
|
||||
class_name CurveEdit
|
||||
extends VBoxContainer
|
||||
|
||||
signal value_changed(value: Curve)
|
||||
|
||||
@export var show_axes := true
|
||||
@export var curve: Curve:
|
||||
set(value):
|
||||
curve = value
|
||||
queue_redraw()
|
||||
update_controls()
|
||||
|
||||
## Array of dictionaries of key [String] and value [Array] of type [CurveEdit.CurvePoint].
|
||||
var presets: Array[Dictionary] = [
|
||||
{"Linear": [CurvePoint.new(0.0, 0.0, 0.0, 1.0), CurvePoint.new(1.0, 1.0, 1.0, 0.0)]},
|
||||
{
|
||||
"Ease out":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 4.0),
|
||||
CurvePoint.new(0.292893, 0.707107, 1.0, 1.0),
|
||||
CurvePoint.new(1.0, 1.0, 0.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Ease in out":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 0.0),
|
||||
CurvePoint.new(0.5, 0.5, 3.0, 3.0),
|
||||
CurvePoint.new(1.0, 1.0, 0.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Ease in":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 0.0),
|
||||
CurvePoint.new(0.707107, 0.292893, 1.0, 1.0),
|
||||
CurvePoint.new(1.0, 1.0, 4.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sawtooth":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 2.0),
|
||||
CurvePoint.new(0.5, 1.0, 2.0, -2.0),
|
||||
CurvePoint.new(1.0, 0.0, -2.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Bounce":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 5.0),
|
||||
CurvePoint.new(0.15, 0.65, 2.45201, 2.45201),
|
||||
CurvePoint.new(0.5, 1.0, 0.0, 0.0),
|
||||
CurvePoint.new(0.85, 0.65, -2.45201, -2.45201),
|
||||
CurvePoint.new(1.0, 0.0, -5.0, 0.0)
|
||||
]
|
||||
},
|
||||
{
|
||||
"Bevel":
|
||||
[
|
||||
CurvePoint.new(0.0, 0.0, 0.0, 2.38507),
|
||||
CurvePoint.new(0.292893, 0.707107, 2.34362, 0.428147),
|
||||
CurvePoint.new(1.0, 1.0, 0.410866, 0.0)
|
||||
]
|
||||
}
|
||||
]
|
||||
var curve_editor := Control.new()
|
||||
var hbox := HBoxContainer.new()
|
||||
|
||||
|
||||
class CurvePoint:
|
||||
var pos: Vector2
|
||||
var left_tangent: float
|
||||
var right_tangent: float
|
||||
|
||||
func _init(x: float, y: float, _left_tangent := 0.0, _right_tangent := 0.0) -> void:
|
||||
pos = Vector2(x, y)
|
||||
left_tangent = _left_tangent
|
||||
right_tangent = _right_tangent
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not is_instance_valid(curve):
|
||||
curve = Curve.new()
|
||||
if custom_minimum_size.is_zero_approx():
|
||||
custom_minimum_size = Vector2(32, 150)
|
||||
curve_editor.gui_input.connect(_on_gui_input)
|
||||
resized.connect(_on_resize)
|
||||
curve_editor.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
add_child(curve_editor)
|
||||
add_child(hbox)
|
||||
var presets_button := MenuButton.new()
|
||||
presets_button.text = "Presets"
|
||||
presets_button.flat = false
|
||||
presets_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
presets_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
presets_button.get_popup().id_pressed.connect(_on_presets_item_selected)
|
||||
for preset in presets:
|
||||
presets_button.get_popup().add_item(preset.keys()[0])
|
||||
var invert_button := Button.new()
|
||||
invert_button.text = "Invert"
|
||||
invert_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
invert_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
invert_button.pressed.connect(_on_invert_button_pressed)
|
||||
hbox.add_child(presets_button)
|
||||
hbox.add_child(invert_button)
|
||||
_on_resize.call_deferred()
|
||||
|
||||
|
||||
func update_controls() -> void:
|
||||
for c in curve_editor.get_children():
|
||||
if c is CurveEditControlPoint:
|
||||
c.queue_free()
|
||||
for i in curve.point_count:
|
||||
var p := curve.get_point_position(i)
|
||||
var control_point := CurveEditControlPoint.new()
|
||||
curve_editor.add_child(control_point)
|
||||
control_point.initialize(curve, i)
|
||||
control_point.position = transform_point(p) - control_point.OFFSET
|
||||
if i == 0 or i == curve.point_count - 1:
|
||||
control_point.set_constraint(
|
||||
control_point.position.x,
|
||||
control_point.position.x,
|
||||
-control_point.OFFSET.y,
|
||||
available_size().y - control_point.OFFSET.y
|
||||
)
|
||||
if i == 0:
|
||||
control_point.set_control_point_visibility(true, false)
|
||||
else:
|
||||
control_point.set_control_point_visibility(false, false)
|
||||
else:
|
||||
var min_x := transform_point(curve.get_point_position(i - 1)).x + 1
|
||||
var max_x := transform_point(curve.get_point_position(i + 1)).x - 1
|
||||
control_point.set_constraint(
|
||||
min_x, max_x, -control_point.OFFSET.y, available_size().y - control_point.OFFSET.y
|
||||
)
|
||||
control_point.moved.connect(_on_control_point_moved)
|
||||
control_point.removed.connect(_on_control_point_removed)
|
||||
value_changed.emit(curve)
|
||||
|
||||
|
||||
static func to_texture(from_curve: Curve, width := 256) -> CurveTexture:
|
||||
var texture := CurveTexture.new()
|
||||
texture.texture_mode = CurveTexture.TEXTURE_MODE_RED
|
||||
texture.curve = from_curve
|
||||
texture.width = width
|
||||
return texture
|
||||
|
||||
|
||||
func set_default_curve() -> void:
|
||||
if not is_instance_valid(curve):
|
||||
curve = Curve.new()
|
||||
_on_presets_item_selected(0)
|
||||
|
||||
|
||||
func available_size() -> Vector2:
|
||||
if curve_editor.size.is_zero_approx():
|
||||
return Vector2.ONE
|
||||
return curve_editor.size
|
||||
|
||||
|
||||
func transform_point(p: Vector2) -> Vector2:
|
||||
return (Vector2(0.0, 1.0) + Vector2(1.0, -1.0) * p) * available_size()
|
||||
|
||||
|
||||
func reverse_transform_point(p: Vector2) -> Vector2:
|
||||
return Vector2(0.0, 1.0) + Vector2(1.0, -1.0) * p / available_size()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var bg := Color.DARK_GRAY
|
||||
var fg := Color.GRAY
|
||||
var current_scene := get_tree().current_scene
|
||||
if current_scene is Control:
|
||||
var current_theme := (current_scene as Control).theme
|
||||
var panel_stylebox := current_theme.get_stylebox("panel", "Panel")
|
||||
if panel_stylebox is StyleBoxFlat:
|
||||
bg = panel_stylebox.bg_color
|
||||
fg = current_theme.get_color("font_color", "Label")
|
||||
var axes_color := bg.lerp(fg, 0.25)
|
||||
var curve_color := bg.lerp(fg, 0.75)
|
||||
if show_axes:
|
||||
for i in range(5):
|
||||
var p := transform_point(0.25 * Vector2(i, i))
|
||||
draw_line(Vector2(p.x, 0), Vector2(p.x, available_size().y - 1), axes_color)
|
||||
draw_line(Vector2(0, p.y), Vector2(available_size().x - 1, p.y), axes_color)
|
||||
var points := PackedVector2Array()
|
||||
for i in range(curve.point_count - 1):
|
||||
var p1 := curve.get_point_position(i)
|
||||
var p2 := curve.get_point_position(i + 1)
|
||||
var d := (p2.x - p1.x) / 3.0
|
||||
var yac := p1.y + d * curve.get_point_right_tangent(i)
|
||||
var ybc := p2.y - d * curve.get_point_left_tangent(i + 1)
|
||||
var p := transform_point(p1)
|
||||
if points.is_empty():
|
||||
points.push_back(p)
|
||||
var count := maxi(1, transform_point(p2).x - p.x / 5.0)
|
||||
for tt in range(count):
|
||||
var t := (tt + 1.0) / count
|
||||
var omt := 1.0 - t
|
||||
var omt2 := omt * omt
|
||||
var omt3 := omt2 * omt
|
||||
var t2 := t * t
|
||||
var t3 := t2 * t
|
||||
var x := p1.x + (p2.x - p1.x) * t
|
||||
var y := p1.y * omt3 + yac * omt2 * t * 3.0 + ybc * omt * t2 * 3.0 + p2.y * t3
|
||||
p = transform_point(Vector2(x, y))
|
||||
points.push_back(p)
|
||||
draw_polyline(points, curve_color)
|
||||
|
||||
|
||||
func _on_control_point_moved(index: int) -> void:
|
||||
var control_point := curve_editor.get_child(index) as CurveEditControlPoint
|
||||
var new_point := reverse_transform_point(control_point.position + control_point.OFFSET)
|
||||
curve.set_point_offset(index, new_point.x)
|
||||
curve.set_point_value(index, new_point.y)
|
||||
if is_instance_valid(control_point.left_slope):
|
||||
var slope_vector := control_point.left_slope.position / available_size()
|
||||
if slope_vector.x != 0:
|
||||
curve.set_point_left_tangent(index, -slope_vector.y / slope_vector.x)
|
||||
if is_instance_valid(control_point.right_slope):
|
||||
var slope_vector := control_point.right_slope.position / available_size()
|
||||
if slope_vector.x != 0:
|
||||
curve.set_point_right_tangent(index, -slope_vector.y / slope_vector.x)
|
||||
queue_redraw()
|
||||
value_changed.emit(curve)
|
||||
|
||||
|
||||
func _on_control_point_removed(index: int) -> void:
|
||||
if index > 0 and index < curve.point_count:
|
||||
curve.remove_point(index)
|
||||
queue_redraw()
|
||||
update_controls()
|
||||
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.double_click:
|
||||
var new_point_position := reverse_transform_point(get_local_mouse_position())
|
||||
curve.add_point(new_point_position, 0.0, 0.0)
|
||||
update_controls()
|
||||
|
||||
|
||||
func _on_resize() -> void:
|
||||
queue_redraw()
|
||||
update_controls()
|
||||
|
||||
|
||||
func _on_presets_item_selected(index: int) -> void:
|
||||
curve.clear_points()
|
||||
var preset_points: Array = presets[index].values()[0]
|
||||
for point: CurvePoint in preset_points:
|
||||
curve.add_point(point.pos, point.left_tangent, point.right_tangent)
|
||||
curve = curve # Call setter
|
||||
|
||||
|
||||
func _on_invert_button_pressed() -> void:
|
||||
var copy_curve := curve.duplicate() as Curve
|
||||
curve.clear_points()
|
||||
for i in copy_curve.point_count:
|
||||
var point := copy_curve.get_point_position(i)
|
||||
point.y = 1.0 - point.y
|
||||
var left_tangent := -copy_curve.get_point_left_tangent(i)
|
||||
var right_tangent := -copy_curve.get_point_right_tangent(i)
|
||||
curve.add_point(point, left_tangent, right_tangent)
|
||||
curve = curve # Call setter
|
62
src/UI/Nodes/CurveEditor/CurveTangentPoint.gd
Normal file
62
src/UI/Nodes/CurveEditor/CurveTangentPoint.gd
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Code taken and modified from Material Maker, licensed under MIT
|
||||
# gdlint: ignore=max-line-length
|
||||
# https://github.com/RodZill4/material-maker/blob/master/material_maker/widgets/curve_edit/slope_point.gd
|
||||
class_name CurveEditTangentPoint
|
||||
extends Control
|
||||
|
||||
const OFFSET := Vector2(0, 0)
|
||||
|
||||
@export var distance := 30
|
||||
|
||||
var moving = false
|
||||
@onready var parent := get_parent() as CurveEditControlPoint
|
||||
@onready var grandparent := parent.get_parent() as Control
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
gui_input.connect(_on_gui_input)
|
||||
custom_minimum_size = Vector2(8, 8)
|
||||
if get_index() == 0:
|
||||
distance = -distance
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var color := Color.GRAY
|
||||
var current_scene := get_tree().current_scene
|
||||
if current_scene is Control:
|
||||
var current_theme := (current_scene as Control).theme
|
||||
color = current_theme.get_color("font_color", "Label")
|
||||
draw_circle(Vector2(3.0, 3.0), 3.0, color)
|
||||
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
if event.double_click:
|
||||
var vector: Vector2
|
||||
if get_index() == 0:
|
||||
vector = (
|
||||
parent.position - grandparent.get_child(parent.get_index() - 1).position
|
||||
)
|
||||
else:
|
||||
vector = (
|
||||
grandparent.get_child(parent.get_index() + 1).position - parent.position
|
||||
)
|
||||
vector = distance * vector.normalized()
|
||||
position = vector - OFFSET
|
||||
if event.is_control_or_command_pressed():
|
||||
parent.get_child(1 - get_index()).position = -vector - OFFSET
|
||||
parent.update_tangents()
|
||||
else:
|
||||
moving = true
|
||||
else:
|
||||
moving = false
|
||||
elif moving and event is InputEventMouseMotion:
|
||||
var vector := get_global_mouse_position() - parent.get_global_rect().position + OFFSET
|
||||
vector *= signf(vector.x)
|
||||
vector = distance * vector.normalized()
|
||||
position = vector - OFFSET
|
||||
if event.is_command_or_control_pressed():
|
||||
parent.get_child(1 - get_index()).position = -vector - OFFSET
|
||||
parent.update_tangents()
|
|
@ -5,15 +5,18 @@ var font: Font:
|
|||
set(value):
|
||||
font = value
|
||||
add_theme_font_override(&"font", font)
|
||||
var _border_node := Control.new()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Global.camera.zoom_changed.connect(func(): _border_node.queue_redraw())
|
||||
_border_node.draw.connect(_on_border_redraw)
|
||||
_border_node.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_border_node.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_border_node)
|
||||
caret_blink = true
|
||||
var stylebox := StyleBoxFlat.new()
|
||||
stylebox.draw_center = false
|
||||
stylebox.border_width_left = 1
|
||||
stylebox.border_width_top = 1
|
||||
stylebox.border_width_right = 1
|
||||
stylebox.border_width_bottom = 1
|
||||
add_theme_stylebox_override(&"normal", stylebox)
|
||||
add_theme_stylebox_override(&"focus", stylebox)
|
||||
add_theme_constant_override(&"line_spacing", 0)
|
||||
|
@ -44,3 +47,9 @@ func _on_text_changed() -> void:
|
|||
var string_size := font.get_string_size(max_line, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size)
|
||||
size.x = font_size + string_size.x
|
||||
size.y = (get_line_count() + 1) * font.get_height(font_size)
|
||||
_border_node.queue_redraw()
|
||||
|
||||
|
||||
func _on_border_redraw() -> void:
|
||||
var border_width := (1.0 / Global.camera.zoom.x) * 2.0 + 1.0
|
||||
_border_node.draw_rect(_border_node.get_rect(), Color.WHITE, false, border_width)
|
||||
|
|
|
@ -47,9 +47,10 @@ var button_size := 36:
|
|||
button.custom_minimum_size = Vector2(button_size, button_size)
|
||||
button.size = Vector2(button_size, button_size)
|
||||
|
||||
@onready var place_tiles: CheckBox = $VBoxContainer/PlaceTiles
|
||||
@onready var transform_buttons_container: HFlowContainer = $VBoxContainer/TransformButtonsContainer
|
||||
@onready var place_tiles: Button = %PlaceTiles
|
||||
@onready var transform_buttons_container: HFlowContainer = %TransformButtonsContainer
|
||||
@onready var tile_button_container: HFlowContainer = %TileButtonContainer
|
||||
@onready var mode_buttons_container: HFlowContainer = %ModeButtonsContainer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
@ -57,6 +58,7 @@ func _ready() -> void:
|
|||
Global.cel_switched.connect(_on_cel_switched)
|
||||
for child: Button in transform_buttons_container.get_children():
|
||||
Global.disable_button(child, true)
|
||||
update_tip()
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
|
@ -80,6 +82,15 @@ func set_tileset(tileset: TileSetCustom) -> void:
|
|||
current_tileset.updated.connect(_update_tileset)
|
||||
|
||||
|
||||
func update_tip():
|
||||
var tip = %Tip
|
||||
tip.get_parent().visible = true
|
||||
if placing_tiles:
|
||||
tip.text = "Select a tile to place it on the canvas."
|
||||
else:
|
||||
tip.text = "Modify tiles on the canvas."
|
||||
|
||||
|
||||
func _on_cel_switched() -> void:
|
||||
if Global.current_project.get_current_cel() is not CelTileMap:
|
||||
set_tileset(null)
|
||||
|
@ -157,8 +168,15 @@ func _clear_tile_buttons() -> void:
|
|||
|
||||
func _on_place_tiles_toggled(toggled_on: bool) -> void:
|
||||
placing_tiles = toggled_on
|
||||
transform_buttons_container.visible = placing_tiles
|
||||
mode_buttons_container.visible = !placing_tiles
|
||||
for child: Button in transform_buttons_container.get_children():
|
||||
Global.disable_button(child, not toggled_on)
|
||||
if toggled_on:
|
||||
Global.change_button_texturerect(place_tiles.get_child(0), "place_tiles_enabled.png")
|
||||
else:
|
||||
Global.change_button_texturerect(place_tiles.get_child(0), "place_tiles_disabled.png")
|
||||
update_tip()
|
||||
|
||||
|
||||
func _on_manual_toggled(toggled_on: bool) -> void:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[gd_scene load_steps=22 format=3 uid="uid://bfbragmmdwfbl"]
|
||||
[gd_scene load_steps=23 format=3 uid="uid://bfbragmmdwfbl"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/UI/TilesPanel.gd" id="1_d2oc5"]
|
||||
[ext_resource type="Texture2D" uid="uid://bqr3n3tm8b6w2" path="res://assets/graphics/tileset/place_tiles_disabled.png" id="2_pv2cd"]
|
||||
[ext_resource type="Texture2D" uid="uid://bv7ldl8obhawm" path="res://assets/graphics/misc/icon_reload.png" id="2_r1kie"]
|
||||
[ext_resource type="Texture2D" uid="uid://bpsfilx47bw3r" path="res://assets/graphics/misc/mirror_x.svg" id="3_5o62r"]
|
||||
[ext_resource type="Texture2D" uid="uid://bk6iaxiyl74ih" path="res://assets/graphics/misc/mirror_y.svg" id="4_2xhnr"]
|
||||
|
@ -66,23 +67,52 @@ script = ExtResource("1_d2oc5")
|
|||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PlaceTiles" type="CheckBox" parent="VBoxContainer"]
|
||||
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Buttons" type="HBoxContainer" parent="VBoxContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PlaceTiles" type="Button" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(25, 25)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
mouse_default_cursor_shape = 2
|
||||
toggle_mode = true
|
||||
shortcut = SubResource("Shortcut_6ebuw")
|
||||
text = "Draw tiles"
|
||||
|
||||
[node name="TransformButtonsContainer" type="HFlowContainer" parent="VBoxContainer"]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/PlaceTiles"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
texture = ExtResource("2_pv2cd")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RotateLeftButton" type="Button" parent="VBoxContainer/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
[node name="TransformButtonsContainer" type="HFlowContainer" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="RotateLeftButton" type="Button" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Rotate tile left (counterclockwise)"
|
||||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_yas23")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/TransformButtonsContainer/RotateLeftButton"]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/RotateLeftButton"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
@ -92,14 +122,14 @@ grow_vertical = 2
|
|||
texture = ExtResource("2_r1kie")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="RotateRightButton" type="Button" parent="VBoxContainer/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
[node name="RotateRightButton" type="Button" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Rotate tile right (clockwise)"
|
||||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_cmy2w")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/TransformButtonsContainer/RotateRightButton"]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/RotateRightButton"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
@ -110,14 +140,14 @@ texture = ExtResource("2_r1kie")
|
|||
stretch_mode = 3
|
||||
flip_h = true
|
||||
|
||||
[node name="FlipHorizontalButton" type="Button" parent="VBoxContainer/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
[node name="FlipHorizontalButton" type="Button" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Flip tile horizontally"
|
||||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_ouoxo")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/TransformButtonsContainer/FlipHorizontalButton"]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/FlipHorizontalButton"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
@ -127,14 +157,14 @@ grow_vertical = 2
|
|||
texture = ExtResource("3_5o62r")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="FlipVerticalButton" type="Button" parent="VBoxContainer/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
[node name="FlipVerticalButton" type="Button" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Flip tile vertically"
|
||||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_jj4yy")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/TransformButtonsContainer/FlipVerticalButton"]
|
||||
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/FlipVerticalButton"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
@ -144,17 +174,19 @@ grow_vertical = 2
|
|||
texture = ExtResource("4_2xhnr")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="ModeButtonsContainer" type="HFlowContainer" parent="VBoxContainer"]
|
||||
[node name="ModeButtonsContainer" type="HFlowContainer" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Manual" type="CheckBox" parent="VBoxContainer/ModeButtonsContainer"]
|
||||
[node name="Manual" type="CheckBox" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/ModeButtonsContainer"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
button_group = SubResource("ButtonGroup_uxnt0")
|
||||
shortcut = SubResource("Shortcut_pgg48")
|
||||
text = "Manual"
|
||||
|
||||
[node name="Auto" type="CheckBox" parent="VBoxContainer/ModeButtonsContainer"]
|
||||
[node name="Auto" type="CheckBox" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/ModeButtonsContainer"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
button_pressed = true
|
||||
|
@ -162,28 +194,43 @@ button_group = SubResource("ButtonGroup_uxnt0")
|
|||
shortcut = SubResource("Shortcut_a0fx5")
|
||||
text = "Auto"
|
||||
|
||||
[node name="Stack" type="CheckBox" parent="VBoxContainer/ModeButtonsContainer"]
|
||||
[node name="Stack" type="CheckBox" parent="VBoxContainer/MarginContainer/VBoxContainer/Buttons/ModeButtonsContainer"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
button_group = SubResource("ButtonGroup_uxnt0")
|
||||
shortcut = SubResource("Shortcut_ysxej")
|
||||
text = "Stack"
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Tip" type="Label" parent="VBoxContainer/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(20, 0)
|
||||
layout_mode = 2
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer/MarginContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="TileButtonContainer" type="HFlowContainer" parent="VBoxContainer/ScrollContainer"]
|
||||
unique_name_in_owner = true
|
||||
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[connection signal="toggled" from="VBoxContainer/PlaceTiles" to="." method="_on_place_tiles_toggled"]
|
||||
[connection signal="pressed" from="VBoxContainer/TransformButtonsContainer/RotateLeftButton" to="." method="_on_rotate_pressed" binds= [false]]
|
||||
[connection signal="pressed" from="VBoxContainer/TransformButtonsContainer/RotateRightButton" to="." method="_on_rotate_pressed" binds= [true]]
|
||||
[connection signal="pressed" from="VBoxContainer/TransformButtonsContainer/FlipHorizontalButton" to="." method="_on_flip_horizontal_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/TransformButtonsContainer/FlipVerticalButton" to="." method="_on_flip_vertical_button_pressed"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModeButtonsContainer/Manual" to="." method="_on_manual_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModeButtonsContainer/Auto" to="." method="_on_auto_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModeButtonsContainer/Stack" to="." method="_on_stack_toggled"]
|
||||
[node name="TileButtonContainer" type="HFlowContainer" parent="VBoxContainer/ScrollContainer/MarginContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="toggled" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/PlaceTiles" to="." method="_on_place_tiles_toggled"]
|
||||
[connection signal="pressed" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/RotateLeftButton" to="." method="_on_rotate_pressed" binds= [false]]
|
||||
[connection signal="pressed" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/RotateRightButton" to="." method="_on_rotate_pressed" binds= [true]]
|
||||
[connection signal="pressed" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/FlipHorizontalButton" to="." method="_on_flip_horizontal_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/TransformButtonsContainer/FlipVerticalButton" to="." method="_on_flip_vertical_button_pressed"]
|
||||
[connection signal="toggled" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/ModeButtonsContainer/Manual" to="." method="_on_manual_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/ModeButtonsContainer/Auto" to="." method="_on_auto_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/MarginContainer/VBoxContainer/Buttons/ModeButtonsContainer/Stack" to="." method="_on_stack_toggled"]
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
extends Panel
|
||||
|
||||
## Emitted when the animation starts playing.
|
||||
signal animation_started(forward: bool)
|
||||
## Emitted when the animation reaches the final frame and is not looping,
|
||||
## or if the animation is manually paused.
|
||||
## Note: This signal is not emitted if the animation is looping.
|
||||
signal animation_finished
|
||||
## Emitted when the animation loops, meaning when it reaches the final frame
|
||||
## and the animation keeps playing.
|
||||
signal animation_looped
|
||||
|
||||
enum LoopType { NO, CYCLE, PINGPONG }
|
||||
|
||||
|
@ -41,6 +48,7 @@ var global_layer_expand := true
|
|||
@onready var move_up_layer := %MoveUpLayer as Button
|
||||
@onready var move_down_layer := %MoveDownLayer as Button
|
||||
@onready var merge_down_layer := %MergeDownLayer as Button
|
||||
@onready var layer_fx := %LayerFX as Button
|
||||
@onready var blend_modes_button := %BlendModes as OptionButton
|
||||
@onready var opacity_slider := %OpacitySlider as ValueSlider
|
||||
@onready var frame_scroll_container := %FrameScrollContainer as Control
|
||||
|
@ -687,9 +695,11 @@ func _on_AnimationTimer_timeout() -> void:
|
|||
animation_timer.wait_time = (
|
||||
project.frames[project.current_frame].duration * (1 / fps)
|
||||
)
|
||||
animation_looped.emit()
|
||||
animation_timer.start()
|
||||
LoopType.PINGPONG:
|
||||
animation_forward = false
|
||||
animation_looped.emit()
|
||||
_on_AnimationTimer_timeout()
|
||||
|
||||
else:
|
||||
|
@ -712,9 +722,11 @@ func _on_AnimationTimer_timeout() -> void:
|
|||
animation_timer.wait_time = (
|
||||
project.frames[project.current_frame].duration * (1 / fps)
|
||||
)
|
||||
animation_looped.emit()
|
||||
animation_timer.start()
|
||||
LoopType.PINGPONG:
|
||||
animation_forward = true
|
||||
animation_looped.emit()
|
||||
_on_AnimationTimer_timeout()
|
||||
frame_scroll_container.ensure_control_visible(
|
||||
Global.frame_hbox.get_child(project.current_frame)
|
||||
|
@ -855,6 +867,8 @@ func _on_add_layer_list_id_pressed(id: int) -> void:
|
|||
Global.LayerTypes.THREE_D:
|
||||
layer = Layer3D.new(project)
|
||||
SteamManager.set_achievement("ACH_3D_LAYER")
|
||||
Global.LayerTypes.AUDIO:
|
||||
layer = AudioLayer.new(project)
|
||||
add_layer(layer, project)
|
||||
|
||||
|
||||
|
@ -904,6 +918,8 @@ func _on_CloneLayer_pressed() -> void:
|
|||
cl_layer = LayerTileMap.new(project, src_layer.tileset)
|
||||
else:
|
||||
cl_layer = src_layer.get_script().new(project)
|
||||
if src_layer is AudioLayer:
|
||||
cl_layer.audio = src_layer.audio
|
||||
cl_layer.project = project
|
||||
cl_layer.index = src_layer.index
|
||||
var src_layer_data: Dictionary = src_layer.serialize()
|
||||
|
@ -1191,10 +1207,13 @@ func _toggle_layer_buttons() -> void:
|
|||
(
|
||||
project.current_layer == child_count
|
||||
or layer is GroupLayer
|
||||
or layer is AudioLayer
|
||||
or project.layers[project.current_layer - 1] is GroupLayer
|
||||
or project.layers[project.current_layer - 1] is Layer3D
|
||||
or project.layers[project.current_layer - 1] is AudioLayer
|
||||
)
|
||||
)
|
||||
Global.disable_button(layer_fx, layer is AudioLayer)
|
||||
|
||||
|
||||
func project_changed() -> void:
|
||||
|
|
|
@ -240,7 +240,7 @@ offset_left = -22.0
|
|||
offset_top = -10.0
|
||||
offset_bottom = 10.0
|
||||
mouse_default_cursor_shape = 2
|
||||
item_count = 4
|
||||
item_count = 5
|
||||
popup/item_0/text = "Add Pixel Layer"
|
||||
popup/item_1/text = "Add Group Layer"
|
||||
popup/item_1/id = 1
|
||||
|
@ -248,6 +248,8 @@ popup/item_2/text = "Add 3D Layer"
|
|||
popup/item_2/id = 2
|
||||
popup/item_3/text = "Add Tilemap Layer"
|
||||
popup/item_3/id = 3
|
||||
popup/item_4/text = "Add Audio Layer"
|
||||
popup/item_4/id = 4
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/LayerTools/MarginContainer/LayerSettingsContainer/LayerButtons/AddLayer/AddLayerList"]
|
||||
layout_mode = 0
|
||||
|
@ -380,6 +382,7 @@ texture = ExtResource("5")
|
|||
stretch_mode = 3
|
||||
|
||||
[node name="LayerFX" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/MarginContainer/LayerSettingsContainer/LayerButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
@ -424,17 +427,20 @@ vertical_scroll_mode = 0
|
|||
layout_mode = 2
|
||||
size_flags_horizontal = 10
|
||||
|
||||
[node name="AnimationButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools"]
|
||||
[node name="MarginContainer" type="MarginContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AnimationButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer"]
|
||||
custom_minimum_size = Vector2(0, 24)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 24
|
||||
alignment = 2
|
||||
|
||||
[node name="FrameButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons"]
|
||||
[node name="FrameButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AddFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
[node name="AddFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
|
@ -443,7 +449,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_mvoxm")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/AddFrame"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/AddFrame"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -455,7 +461,7 @@ offset_right = 6.0
|
|||
offset_bottom = 6.0
|
||||
texture = ExtResource("19")
|
||||
|
||||
[node name="DeleteFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
[node name="DeleteFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -465,7 +471,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_o40ql")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/DeleteFrame"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/DeleteFrame"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -479,7 +485,7 @@ size_flags_horizontal = 0
|
|||
size_flags_vertical = 0
|
||||
texture = ExtResource("20")
|
||||
|
||||
[node name="CopyFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
[node name="CopyFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
|
@ -488,7 +494,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_5g7t7")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/CopyFrame"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/CopyFrame"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -500,7 +506,7 @@ offset_right = 5.0
|
|||
offset_bottom = 7.0
|
||||
texture = ExtResource("27")
|
||||
|
||||
[node name="MoveFrameLeft" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
[node name="MoveFrameLeft" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -510,7 +516,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_kwn1u")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/MoveFrameLeft"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/MoveFrameLeft"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -523,7 +529,7 @@ offset_bottom = 5.5
|
|||
texture = ExtResource("8")
|
||||
flip_h = true
|
||||
|
||||
[node name="MoveFrameRight" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
[node name="MoveFrameRight" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -533,7 +539,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_m2mvi")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/MoveFrameRight"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/MoveFrameRight"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -545,10 +551,10 @@ offset_right = 7.5
|
|||
offset_bottom = 5.5
|
||||
texture = ExtResource("8")
|
||||
|
||||
[node name="PlaybackButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons"]
|
||||
[node name="PlaybackButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FirstFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
[node name="FirstFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Jump to the first frame"
|
||||
|
@ -556,7 +562,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("4")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/FirstFrame"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/FirstFrame"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -568,7 +574,7 @@ offset_right = 5.5
|
|||
offset_bottom = 6.0
|
||||
texture = ExtResource("21")
|
||||
|
||||
[node name="PreviousFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
[node name="PreviousFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Go to the previous frame"
|
||||
|
@ -576,7 +582,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("6")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/PreviousFrame"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/PreviousFrame"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -588,7 +594,7 @@ offset_right = 5.5
|
|||
offset_bottom = 6.0
|
||||
texture = ExtResource("23")
|
||||
|
||||
[node name="PlayBackwards" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
[node name="PlayBackwards" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -598,7 +604,7 @@ mouse_default_cursor_shape = 2
|
|||
toggle_mode = true
|
||||
shortcut = SubResource("8")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/PlayBackwards"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/PlayBackwards"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -610,7 +616,7 @@ offset_right = 3.0
|
|||
offset_bottom = 6.0
|
||||
texture = ExtResource("24")
|
||||
|
||||
[node name="PlayForward" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
[node name="PlayForward" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -621,7 +627,7 @@ mouse_default_cursor_shape = 2
|
|||
toggle_mode = true
|
||||
shortcut = SubResource("10")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/PlayForward"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/PlayForward"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -633,7 +639,7 @@ offset_right = 3.5
|
|||
offset_bottom = 6.0
|
||||
texture = ExtResource("22")
|
||||
|
||||
[node name="NextFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
[node name="NextFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Go to the next frame"
|
||||
|
@ -641,7 +647,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("12")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/NextFrame"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/NextFrame"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -653,7 +659,7 @@ offset_right = 5.5
|
|||
offset_bottom = 6.0
|
||||
texture = ExtResource("26")
|
||||
|
||||
[node name="LastFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
[node name="LastFrame" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Jump to the last frame"
|
||||
|
@ -661,7 +667,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("14")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/LastFrame"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/LastFrame"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -673,10 +679,10 @@ offset_right = 5.5
|
|||
offset_bottom = 6.0
|
||||
texture = ExtResource("25")
|
||||
|
||||
[node name="LoopButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons"]
|
||||
[node name="LoopButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TimelineSettingsButton" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons" groups=["UIButtons"]]
|
||||
[node name="TimelineSettingsButton" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons" groups=["UIButtons"]]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
|
@ -685,7 +691,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_tke6v")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons/TimelineSettingsButton"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons/TimelineSettingsButton"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -699,7 +705,7 @@ size_flags_horizontal = 0
|
|||
size_flags_vertical = 0
|
||||
texture = ExtResource("30")
|
||||
|
||||
[node name="OnionSkinning" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons" groups=["UIButtons"]]
|
||||
[node name="OnionSkinning" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -708,7 +714,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_pouu1")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons/OnionSkinning"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons/OnionSkinning"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -722,7 +728,7 @@ size_flags_horizontal = 0
|
|||
size_flags_vertical = 0
|
||||
texture = ExtResource("29")
|
||||
|
||||
[node name="LoopAnim" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons" groups=["UIButtons"]]
|
||||
[node name="LoopAnim" type="Button" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
|
@ -731,7 +737,7 @@ focus_mode = 0
|
|||
mouse_default_cursor_shape = 2
|
||||
shortcut = SubResource("Shortcut_1onr8")
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons/LoopAnim"]
|
||||
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons/LoopAnim"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
@ -743,7 +749,7 @@ offset_right = 7.0
|
|||
offset_bottom = 7.0
|
||||
texture = ExtResource("31")
|
||||
|
||||
[node name="FPSValue" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons" instance=ExtResource("9")]
|
||||
[node name="FPSValue" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons" instance=ExtResource("9")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(72, 24)
|
||||
layout_mode = 2
|
||||
|
@ -1136,21 +1142,21 @@ color = Color(0, 0.741176, 1, 0.501961)
|
|||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/MarginContainer/LayerSettingsContainer/LayerButtons/MergeDownLayer" to="." method="_on_MergeDownLayer_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/MarginContainer/LayerSettingsContainer/LayerButtons/LayerFX" to="." method="_on_layer_fx_pressed"]
|
||||
[connection signal="item_selected" from="TimelineContainer/TimelineButtons/LayerTools/MarginContainer/LayerSettingsContainer/BlendContainer/BlendModes" to="." method="_on_blend_modes_item_selected"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/AddFrame" to="." method="add_frame"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/DeleteFrame" to="." method="_on_DeleteFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/CopyFrame" to="." method="_on_CopyFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/MoveFrameLeft" to="." method="_on_MoveLeft_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/MoveFrameRight" to="." method="_on_MoveRight_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/FirstFrame" to="." method="_on_FirstFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/PreviousFrame" to="." method="_on_PreviousFrame_pressed"]
|
||||
[connection signal="toggled" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/PlayBackwards" to="." method="_on_PlayBackwards_toggled"]
|
||||
[connection signal="toggled" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/PlayForward" to="." method="_on_PlayForward_toggled"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/NextFrame" to="." method="_on_NextFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/PlaybackButtons/LastFrame" to="." method="_on_LastFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons/TimelineSettingsButton" to="." method="_on_timeline_settings_button_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons/OnionSkinning" to="." method="_on_OnionSkinning_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons/LoopAnim" to="." method="_on_LoopAnim_pressed"]
|
||||
[connection signal="value_changed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/LoopButtons/FPSValue" to="." method="_on_FPSValue_value_changed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/AddFrame" to="." method="add_frame"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/DeleteFrame" to="." method="_on_DeleteFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/CopyFrame" to="." method="_on_CopyFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/MoveFrameLeft" to="." method="_on_MoveLeft_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/FrameButtons/MoveFrameRight" to="." method="_on_MoveRight_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/FirstFrame" to="." method="_on_FirstFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/PreviousFrame" to="." method="_on_PreviousFrame_pressed"]
|
||||
[connection signal="toggled" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/PlayBackwards" to="." method="_on_PlayBackwards_toggled"]
|
||||
[connection signal="toggled" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/PlayForward" to="." method="_on_PlayForward_toggled"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/NextFrame" to="." method="_on_NextFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/PlaybackButtons/LastFrame" to="." method="_on_LastFrame_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons/TimelineSettingsButton" to="." method="_on_timeline_settings_button_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons/OnionSkinning" to="." method="_on_OnionSkinning_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons/LoopAnim" to="." method="_on_LoopAnim_pressed"]
|
||||
[connection signal="value_changed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/MarginContainer/AnimationButtons/LoopButtons/FPSValue" to="." method="_on_FPSValue_value_changed"]
|
||||
[connection signal="dragged" from="TimelineContainer/MainBodyPanel/MainBodyVBoxContainer/MarginContainer/LayerFrameHeaderHSplit" to="." method="_on_layer_frame_h_split_dragged"]
|
||||
[connection signal="gui_input" from="TimelineContainer/MainBodyPanel/MainBodyVBoxContainer/MarginContainer/LayerFrameHeaderHSplit" to="." method="_on_LayerFrameSplitContainer_gui_input"]
|
||||
[connection signal="pressed" from="TimelineContainer/MainBodyPanel/MainBodyVBoxContainer/MarginContainer/LayerFrameHeaderHSplit/LayerHeaderContainer/GlobalVisibilityButton" to="." method="_on_global_visibility_button_pressed"]
|
||||
|
|
|
@ -9,7 +9,7 @@ var cel: BaseCel
|
|||
var _is_guide_stylebox := false
|
||||
|
||||
@onready var popup_menu: PopupMenu = get_node_or_null("PopupMenu")
|
||||
@onready var linked: ColorRect = $Linked
|
||||
@onready var linked_rect: ColorRect = $Linked
|
||||
@onready var cel_texture: TextureRect = $CelTexture
|
||||
@onready var transparent_checker: ColorRect = $CelTexture/TransparentChecker
|
||||
@onready var properties: AcceptDialog = Global.control.find_child("CelProperties")
|
||||
|
@ -31,6 +31,14 @@ func _ready() -> void:
|
|||
popup_menu.add_item("Unlink Cels")
|
||||
elif cel is GroupCel:
|
||||
transparent_checker.visible = false
|
||||
elif cel is AudioCel:
|
||||
popup_menu.add_item("Play audio here")
|
||||
_is_playing_audio()
|
||||
Global.cel_switched.connect(_is_playing_audio)
|
||||
Themes.theme_switched.connect(_is_playing_audio)
|
||||
Global.current_project.fps_changed.connect(_is_playing_audio)
|
||||
Global.current_project.layers[layer].audio_changed.connect(_is_playing_audio)
|
||||
Global.current_project.layers[layer].playback_frame_changed.connect(_is_playing_audio)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
|
@ -66,11 +74,12 @@ func button_setup() -> void:
|
|||
|
||||
var base_layer := Global.current_project.layers[layer]
|
||||
tooltip_text = tr("Frame: %s, Layer: %s") % [frame + 1, base_layer.name]
|
||||
cel_texture.texture = cel.image_texture
|
||||
if is_instance_valid(linked):
|
||||
linked.visible = cel.link_set != null
|
||||
if cel is not AudioCel:
|
||||
cel_texture.texture = cel.image_texture
|
||||
if is_instance_valid(linked_rect):
|
||||
linked_rect.visible = cel.link_set != null
|
||||
if cel.link_set != null:
|
||||
linked.color.h = cel.link_set["hue"]
|
||||
linked_rect.color.h = cel.link_set["hue"]
|
||||
|
||||
|
||||
func _on_CelButton_pressed() -> void:
|
||||
|
@ -129,7 +138,11 @@ func _on_PopupMenu_id_pressed(id: int) -> void:
|
|||
properties.cel_indices = _get_cel_indices()
|
||||
properties.popup_centered()
|
||||
MenuOptions.DELETE:
|
||||
_delete_cel_content()
|
||||
var layer_class := Global.current_project.layers[layer]
|
||||
if layer_class is AudioLayer:
|
||||
layer_class.playback_frame = frame
|
||||
else:
|
||||
_delete_cel_content()
|
||||
|
||||
MenuOptions.LINK, MenuOptions.UNLINK:
|
||||
var project := Global.current_project
|
||||
|
@ -396,3 +409,29 @@ func _sort_cel_indices_by_frame(a: Array, b: Array) -> bool:
|
|||
if frame_a < frame_b:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _is_playing_audio() -> void:
|
||||
var project := Global.current_project
|
||||
var frame_class := project.frames[frame]
|
||||
var layer_class := project.layers[layer] as AudioLayer
|
||||
var audio_length := layer_class.get_audio_length()
|
||||
var frame_pos := frame_class.position_in_seconds(project, layer_class.playback_frame)
|
||||
var audio_color := Color.LIGHT_GRAY
|
||||
var pressed_stylebox := Global.control.theme.get_stylebox(&"pressed", &"CelButton")
|
||||
if pressed_stylebox is StyleBoxFlat:
|
||||
audio_color = pressed_stylebox.border_color
|
||||
var is_last_frame := frame + 1 >= project.frames.size()
|
||||
if not is_last_frame:
|
||||
is_last_frame = (
|
||||
project.frames[frame + 1].position_in_seconds(project, layer_class.playback_frame)
|
||||
>= audio_length
|
||||
)
|
||||
if frame_pos == 0 or (is_last_frame and frame_pos < audio_length):
|
||||
cel_texture.texture = preload("res://assets/graphics/misc/musical_note.png")
|
||||
cel_texture.self_modulate = audio_color
|
||||
linked_rect.visible = false
|
||||
else:
|
||||
linked_rect.visible = frame_pos < audio_length and frame_pos > 0
|
||||
linked_rect.color = audio_color
|
||||
cel_texture.texture = null
|
||||
|
|
|
@ -74,7 +74,6 @@ grow_vertical = 2
|
|||
[node name="PopupMenu" type="PopupMenu" parent="."]
|
||||
item_count = 1
|
||||
item_0/text = "Properties"
|
||||
item_0/id = 0
|
||||
|
||||
[connection signal="pressed" from="." to="." method="_on_CelButton_pressed"]
|
||||
[connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"]
|
||||
|
|
|
@ -15,18 +15,18 @@ func _on_visibility_changed() -> void:
|
|||
Global.dialog_open(visible)
|
||||
var first_cel := Global.current_project.frames[cel_indices[0][0]].cels[cel_indices[0][1]]
|
||||
if visible:
|
||||
var first_layer := Global.current_project.layers[cel_indices[0][1]]
|
||||
if cel_indices.size() == 1:
|
||||
var layer := Global.current_project.layers[cel_indices[0][1]]
|
||||
frame_num.text = str(cel_indices[0][0] + 1)
|
||||
layer_num.text = layer.name
|
||||
layer_num.text = first_layer.name
|
||||
else:
|
||||
var first_layer := Global.current_project.layers[cel_indices[0][1]]
|
||||
var last_layer := Global.current_project.layers[cel_indices[-1][1]]
|
||||
frame_num.text = "[%s...%s]" % [cel_indices[0][0] + 1, cel_indices[-1][0] + 1]
|
||||
layer_num.text = "[%s...%s]" % [first_layer.name, last_layer.name]
|
||||
opacity_slider.value = first_cel.opacity * 100.0
|
||||
z_index_slider.value = first_cel.z_index
|
||||
user_data_text_edit.text = first_cel.user_data
|
||||
get_tree().set_group(&"VisualCels", "visible", first_layer is not AudioLayer)
|
||||
else:
|
||||
cel_indices = []
|
||||
|
||||
|
|
|
@ -33,12 +33,12 @@ layout_mode = 2
|
|||
text = "1"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="OpacityLabel" type="Label" parent="GridContainer"]
|
||||
[node name="OpacityLabel" type="Label" parent="GridContainer" groups=["VisualCels"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Opacity:"
|
||||
|
||||
[node name="OpacitySlider" type="TextureProgressBar" parent="GridContainer"]
|
||||
[node name="OpacitySlider" type="TextureProgressBar" parent="GridContainer" groups=["VisualCels"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
focus_mode = 2
|
||||
|
@ -52,12 +52,12 @@ stretch_margin_right = 3
|
|||
stretch_margin_bottom = 3
|
||||
script = ExtResource("1_85pb7")
|
||||
|
||||
[node name="ZIndexLabel" type="Label" parent="GridContainer"]
|
||||
[node name="ZIndexLabel" type="Label" parent="GridContainer" groups=["VisualCels"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Z-Index:"
|
||||
|
||||
[node name="ZIndexSlider" type="TextureProgressBar" parent="GridContainer"]
|
||||
[node name="ZIndexSlider" type="TextureProgressBar" parent="GridContainer" groups=["VisualCels"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
focus_mode = 2
|
||||
|
@ -76,11 +76,13 @@ script = ExtResource("1_85pb7")
|
|||
|
||||
[node name="UserDataLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 0
|
||||
text = "User data:"
|
||||
|
||||
[node name="UserDataTextEdit" type="TextEdit" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
scroll_fit_content_height = true
|
||||
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
||||
|
|
|
@ -19,8 +19,9 @@ func _ready() -> void:
|
|||
|
||||
|
||||
func _update_tooltip() -> void:
|
||||
var duration := Global.current_project.frames[frame].duration
|
||||
var duration_sec := duration * (1.0 / Global.current_project.fps)
|
||||
var frame_class := Global.current_project.frames[frame]
|
||||
var duration := frame_class.duration
|
||||
var duration_sec := frame_class.get_duration_in_seconds(Global.current_project.fps)
|
||||
var duration_str := str(duration_sec)
|
||||
if "." in duration_str: # If its a decimal value
|
||||
duration_str = "%.2f" % duration_sec # Up to 2 decimal places
|
||||
|
|
|
@ -14,7 +14,10 @@ var button_pressed := false:
|
|||
main_button.button_pressed = value
|
||||
get:
|
||||
return main_button.button_pressed
|
||||
var animation_running := false
|
||||
var audio_playing_at_frame := 0
|
||||
|
||||
var audio_player: AudioStreamPlayer
|
||||
@onready var properties: AcceptDialog = Global.control.find_child("LayerProperties")
|
||||
@onready var main_button := %LayerMainButton as Button
|
||||
@onready var expand_button := %ExpandButton as BaseButton
|
||||
|
@ -31,14 +34,22 @@ var button_pressed := false:
|
|||
func _ready() -> void:
|
||||
main_button.layer_index = layer_index
|
||||
main_button.hierarchy_depth_pixel_shift = HIERARCHY_DEPTH_PIXEL_SHIFT
|
||||
Global.cel_switched.connect(func(): z_index = 1 if button_pressed else 0)
|
||||
Global.cel_switched.connect(_on_cel_switched)
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
layer.name_changed.connect(func(): label.text = layer.name)
|
||||
layer.visibility_changed.connect(update_buttons)
|
||||
layer.visibility_changed.connect(_on_layer_visibility_changed)
|
||||
if layer is PixelLayer:
|
||||
linked_button.visible = true
|
||||
elif layer is GroupLayer:
|
||||
expand_button.visible = true
|
||||
elif layer is AudioLayer:
|
||||
audio_player = AudioStreamPlayer.new()
|
||||
audio_player.stream = layer.audio
|
||||
layer.audio_changed.connect(func(): audio_player.stream = layer.audio)
|
||||
add_child(audio_player)
|
||||
Global.animation_timeline.animation_started.connect(_on_animation_started)
|
||||
Global.animation_timeline.animation_looped.connect(_on_animation_looped)
|
||||
Global.animation_timeline.animation_finished.connect(_on_animation_finished)
|
||||
custom_minimum_size.y = Global.animation_timeline.cel_size
|
||||
label.text = layer.name
|
||||
line_edit.text = layer.name
|
||||
|
@ -56,6 +67,76 @@ func _ready() -> void:
|
|||
update_buttons()
|
||||
|
||||
|
||||
func _on_cel_switched() -> void:
|
||||
z_index = 1 if button_pressed else 0
|
||||
var project := Global.current_project
|
||||
var layer := project.layers[layer_index]
|
||||
if layer is AudioLayer:
|
||||
if not is_instance_valid(audio_player):
|
||||
return
|
||||
if not layer.is_visible_in_hierarchy():
|
||||
audio_player.stop()
|
||||
return
|
||||
if animation_running:
|
||||
var current_frame := project.current_frame
|
||||
if (
|
||||
current_frame == layer.playback_frame
|
||||
or (current_frame == 0 and layer.playback_frame < 0)
|
||||
## True when switching cels while the animation is running
|
||||
or current_frame != audio_playing_at_frame + 1
|
||||
):
|
||||
_play_audio(false)
|
||||
audio_playing_at_frame = current_frame
|
||||
else:
|
||||
_play_audio(true)
|
||||
|
||||
|
||||
func _on_layer_visibility_changed() -> void:
|
||||
update_buttons()
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
if layer is AudioLayer:
|
||||
_play_audio(not animation_running)
|
||||
|
||||
|
||||
func _on_animation_started(_dir: bool) -> void:
|
||||
animation_running = true
|
||||
_play_audio(false)
|
||||
|
||||
|
||||
func _on_animation_looped() -> void:
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
if layer is AudioLayer:
|
||||
if layer.playback_frame > 0 or not layer.is_visible_in_hierarchy():
|
||||
if is_instance_valid(audio_player):
|
||||
audio_player.stop()
|
||||
|
||||
|
||||
func _on_animation_finished() -> void:
|
||||
animation_running = false
|
||||
if is_instance_valid(audio_player):
|
||||
audio_player.stop()
|
||||
|
||||
|
||||
func _play_audio(single_frame: bool) -> void:
|
||||
if not is_instance_valid(audio_player):
|
||||
return
|
||||
var project := Global.current_project
|
||||
var layer := project.layers[layer_index] as AudioLayer
|
||||
if not layer.is_visible_in_hierarchy():
|
||||
return
|
||||
var audio_length := layer.get_audio_length()
|
||||
var frame := project.frames[project.current_frame]
|
||||
var frame_pos := frame.position_in_seconds(project, layer.playback_frame)
|
||||
if frame_pos >= 0 and frame_pos < audio_length:
|
||||
audio_player.play(frame_pos)
|
||||
audio_playing_at_frame = project.current_frame
|
||||
if single_frame:
|
||||
var timer := get_tree().create_timer(frame.get_duration_in_seconds(project.fps))
|
||||
timer.timeout.connect(func(): audio_player.stop())
|
||||
else:
|
||||
audio_player.stop()
|
||||
|
||||
|
||||
func update_buttons() -> void:
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
if layer is GroupLayer:
|
||||
|
|
|
@ -169,7 +169,6 @@ caret_blink_interval = 0.5
|
|||
disable_3d = true
|
||||
item_count = 2
|
||||
item_0/text = "Properties"
|
||||
item_0/id = 0
|
||||
item_1/text = "Clipping mask"
|
||||
item_1/checkable = 1
|
||||
item_1/id = 1
|
||||
|
|
|
@ -20,6 +20,7 @@ var effects: Array[LayerEffect] = [
|
|||
"Adjust Brightness/Contrast",
|
||||
preload("res://src/Shaders/Effects/BrightnessContrast.gdshader")
|
||||
),
|
||||
LayerEffect.new("Color Curves", preload("res://src/Shaders/Effects/ColorCurves.gdshader")),
|
||||
LayerEffect.new("Palettize", preload("res://src/Shaders/Effects/Palettize.gdshader")),
|
||||
LayerEffect.new("Pixelize", preload("res://src/Shaders/Effects/Pixelize.gdshader")),
|
||||
LayerEffect.new("Posterize", preload("res://src/Shaders/Effects/Posterize.gdshader")),
|
||||
|
@ -36,6 +37,11 @@ var effects: Array[LayerEffect] = [
|
|||
func _ready() -> void:
|
||||
for effect in effects:
|
||||
effect_list.get_popup().add_item(effect.name)
|
||||
if not DirAccess.dir_exists_absolute(OpenSave.SHADERS_DIRECTORY):
|
||||
DirAccess.make_dir_recursive_absolute(OpenSave.SHADERS_DIRECTORY)
|
||||
for file_name in DirAccess.get_files_at(OpenSave.SHADERS_DIRECTORY):
|
||||
_load_shader_file(OpenSave.SHADERS_DIRECTORY.path_join(file_name))
|
||||
OpenSave.shader_copied.connect(_load_shader_file)
|
||||
effect_list.get_popup().id_pressed.connect(_on_effect_list_id_pressed)
|
||||
|
||||
|
||||
|
@ -48,7 +54,8 @@ func _on_about_to_popup() -> void:
|
|||
var layer := Global.current_project.layers[Global.current_project.current_layer]
|
||||
enabled_button.button_pressed = layer.effects_enabled
|
||||
for effect in layer.effects:
|
||||
_create_effect_ui(layer, effect)
|
||||
if is_instance_valid(effect.shader):
|
||||
_create_effect_ui(layer, effect)
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
|
@ -58,6 +65,14 @@ func _on_visibility_changed() -> void:
|
|||
child.queue_free()
|
||||
|
||||
|
||||
func _load_shader_file(file_path: String) -> void:
|
||||
var file := load(file_path)
|
||||
if file is Shader:
|
||||
var effect_name := file_path.get_file().get_basename()
|
||||
effects.append(LayerEffect.new(effect_name, file))
|
||||
effect_list.get_popup().add_item(effect_name)
|
||||
|
||||
|
||||
func _on_effect_list_id_pressed(index: int) -> void:
|
||||
var layer := Global.current_project.layers[Global.current_project.current_layer]
|
||||
var effect := effects[index].duplicate()
|
||||
|
|
|
@ -4,11 +4,18 @@ signal layer_property_changed
|
|||
|
||||
var layer_indices: PackedInt32Array
|
||||
|
||||
@onready var grid_container: GridContainer = $GridContainer
|
||||
@onready var name_line_edit := $GridContainer/NameLineEdit as LineEdit
|
||||
@onready var opacity_slider := $GridContainer/OpacitySlider as ValueSlider
|
||||
@onready var blend_modes_button := $GridContainer/BlendModeOptionButton as OptionButton
|
||||
@onready var play_at_frame_slider := $GridContainer/PlayAtFrameSlider as ValueSlider
|
||||
@onready var user_data_text_edit := $GridContainer/UserDataTextEdit as TextEdit
|
||||
@onready var tileset_option_button := $GridContainer/TilesetOptionButton as OptionButton
|
||||
@onready var audio_file_dialog := $AudioFileDialog as FileDialog
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
audio_file_dialog.use_native_dialog = Global.use_native_file_dialogs
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
|
@ -23,8 +30,13 @@ func _on_visibility_changed() -> void:
|
|||
opacity_slider.value = first_layer.opacity * 100.0
|
||||
var blend_mode_index := blend_modes_button.get_item_index(first_layer.blend_mode)
|
||||
blend_modes_button.selected = blend_mode_index
|
||||
if first_layer is AudioLayer:
|
||||
play_at_frame_slider.value = first_layer.playback_frame + 1
|
||||
play_at_frame_slider.max_value = project.frames.size()
|
||||
user_data_text_edit.text = first_layer.user_data
|
||||
get_tree().set_group(&"VisualLayers", "visible", first_layer is not AudioLayer)
|
||||
get_tree().set_group(&"TilemapLayers", "visible", first_layer is LayerTileMap)
|
||||
get_tree().set_group(&"AudioLayers", "visible", first_layer is AudioLayer)
|
||||
tileset_option_button.clear()
|
||||
if first_layer is LayerTileMap:
|
||||
for i in project.tilesets.size():
|
||||
|
@ -149,3 +161,28 @@ func _on_tileset_option_button_item_selected(index: int) -> void:
|
|||
project.undo_redo.add_undo_method(Global.canvas.draw_layers)
|
||||
project.undo_redo.add_undo_method(func(): Global.cel_switched.emit())
|
||||
project.undo_redo.commit_action()
|
||||
|
||||
|
||||
func _on_audio_file_button_pressed() -> void:
|
||||
audio_file_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_play_at_frame_slider_value_changed(value: float) -> void:
|
||||
if layer_indices.size() == 0:
|
||||
return
|
||||
for layer_index in layer_indices:
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
if layer is AudioLayer:
|
||||
layer.playback_frame = value - 1
|
||||
|
||||
|
||||
func _on_audio_file_dialog_file_selected(path: String) -> void:
|
||||
var audio_stream: AudioStream
|
||||
if path.get_extension() == "mp3":
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
audio_stream = AudioStreamMP3.new()
|
||||
audio_stream.data = file.get_buffer(file.get_length())
|
||||
for layer_index in layer_indices:
|
||||
var layer := Global.current_project.layers[layer_index]
|
||||
if layer is AudioLayer:
|
||||
layer.audio = audio_stream
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
|
||||
[node name="LayerProperties" type="AcceptDialog"]
|
||||
title = "Layer properties"
|
||||
size = Vector2i(300, 208)
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(300, 270)
|
||||
script = ExtResource("1_54q1t")
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 292.0
|
||||
offset_bottom = 159.0
|
||||
offset_bottom = 221.0
|
||||
columns = 2
|
||||
|
||||
[node name="NameLabel" type="Label" parent="GridContainer"]
|
||||
|
@ -24,12 +25,12 @@ text = "Name:"
|
|||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="OpacityLabel" type="Label" parent="GridContainer"]
|
||||
[node name="OpacityLabel" type="Label" parent="GridContainer" groups=["VisualLayers"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Opacity:"
|
||||
|
||||
[node name="OpacitySlider" type="TextureProgressBar" parent="GridContainer"]
|
||||
[node name="OpacitySlider" type="TextureProgressBar" parent="GridContainer" groups=["VisualLayers"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
focus_mode = 2
|
||||
|
@ -42,16 +43,44 @@ stretch_margin_right = 3
|
|||
stretch_margin_bottom = 3
|
||||
script = ExtResource("2_bwpwc")
|
||||
|
||||
[node name="BlendModeLabel" type="Label" parent="GridContainer"]
|
||||
[node name="BlendModeLabel" type="Label" parent="GridContainer" groups=["VisualLayers"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Blend mode:"
|
||||
|
||||
[node name="BlendModeOptionButton" type="OptionButton" parent="GridContainer"]
|
||||
[node name="BlendModeOptionButton" type="OptionButton" parent="GridContainer" groups=["VisualLayers"]]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
mouse_default_cursor_shape = 2
|
||||
|
||||
[node name="AudioFileLabel" type="Label" parent="GridContainer" groups=["AudioLayers"]]
|
||||
layout_mode = 2
|
||||
text = "Audio file:"
|
||||
|
||||
[node name="AudioFileButton" type="Button" parent="GridContainer" groups=["AudioLayers"]]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Load file"
|
||||
|
||||
[node name="PlayAtFrameLabel" type="Label" parent="GridContainer" groups=["AudioLayers"]]
|
||||
layout_mode = 2
|
||||
text = "Play at frame:"
|
||||
|
||||
[node name="PlayAtFrameSlider" type="TextureProgressBar" parent="GridContainer" groups=["AudioLayers"]]
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme_type_variation = &"ValueSlider"
|
||||
min_value = 1.0
|
||||
value = 1.0
|
||||
allow_greater = true
|
||||
nine_patch_stretch = true
|
||||
stretch_margin_left = 3
|
||||
stretch_margin_top = 3
|
||||
stretch_margin_right = 3
|
||||
stretch_margin_bottom = 3
|
||||
script = ExtResource("2_bwpwc")
|
||||
|
||||
[node name="UserDataLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
@ -73,9 +102,21 @@ text = "Tileset:"
|
|||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
|
||||
[node name="AudioFileDialog" type="FileDialog" parent="."]
|
||||
title = "Open a File"
|
||||
size = Vector2i(870, 400)
|
||||
always_on_top = true
|
||||
ok_button_text = "Open"
|
||||
file_mode = 0
|
||||
access = 2
|
||||
filters = PackedStringArray("*.mp3 ; MP3 Audio")
|
||||
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
||||
[connection signal="text_changed" from="GridContainer/NameLineEdit" to="." method="_on_name_line_edit_text_changed"]
|
||||
[connection signal="value_changed" from="GridContainer/OpacitySlider" to="." method="_on_opacity_slider_value_changed"]
|
||||
[connection signal="item_selected" from="GridContainer/BlendModeOptionButton" to="." method="_on_blend_mode_option_button_item_selected"]
|
||||
[connection signal="pressed" from="GridContainer/AudioFileButton" to="." method="_on_audio_file_button_pressed"]
|
||||
[connection signal="value_changed" from="GridContainer/PlayAtFrameSlider" to="." method="_on_play_at_frame_slider_value_changed"]
|
||||
[connection signal="text_changed" from="GridContainer/UserDataTextEdit" to="." method="_on_user_data_text_edit_text_changed"]
|
||||
[connection signal="item_selected" from="GridContainer/TilesetOptionButton" to="." method="_on_tileset_option_button_item_selected"]
|
||||
[connection signal="file_selected" from="AudioFileDialog" to="." method="_on_audio_file_dialog_file_selected"]
|
||||
|
|
|
@ -14,6 +14,7 @@ const HEART_ICON := preload("res://assets/graphics/misc/heart.svg")
|
|||
var recent_projects := []
|
||||
var selected_layout := 0
|
||||
var zen_mode := false
|
||||
var loaded_effects_submenu: PopupMenu
|
||||
|
||||
# Dialogs
|
||||
var new_image_dialog := Dialog.new("res://src/UI/Dialogs/CreateNewImage.tscn")
|
||||
|
@ -33,13 +34,14 @@ var hsv_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/HSVDialog.tscn")
|
|||
var adjust_brightness_saturation_dialog := Dialog.new(
|
||||
"res://src/UI/Dialogs/ImageEffects/BrightnessContrastDialog.tscn"
|
||||
)
|
||||
var color_curves_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ColorCurvesDialog.tscn")
|
||||
var gaussian_blur_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GaussianBlur.tscn")
|
||||
var gradient_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GradientDialog.tscn")
|
||||
var gradient_map_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn")
|
||||
var palettize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/PalettizeDialog.tscn")
|
||||
var pixelize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/PixelizeDialog.tscn")
|
||||
var posterize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/Posterize.tscn")
|
||||
var shader_effect_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn")
|
||||
var loaded_effect_dialogs: Array[Dialog] = []
|
||||
var manage_layouts_dialog := Dialog.new("res://src/UI/Dialogs/ManageLayouts.tscn")
|
||||
var window_opacity_dialog := Dialog.new("res://src/UI/Dialogs/WindowOpacityDialog.tscn")
|
||||
var about_dialog := Dialog.new("res://src/UI/Dialogs/AboutDialog.tscn")
|
||||
|
@ -78,21 +80,24 @@ class Dialog:
|
|||
|
||||
func popup(dialog_size := Vector2i.ZERO) -> void:
|
||||
if not is_instance_valid(node):
|
||||
var scene := load(scene_path)
|
||||
if not scene is PackedScene:
|
||||
return
|
||||
node = scene.instantiate()
|
||||
if not is_instance_valid(node):
|
||||
return
|
||||
Global.control.get_node("Dialogs").add_child(node)
|
||||
instantiate_scene()
|
||||
node.popup_centered(dialog_size)
|
||||
var is_file_dialog := node is FileDialog
|
||||
Global.dialog_open(true, is_file_dialog)
|
||||
|
||||
func instantiate_scene() -> void:
|
||||
var scene := load(scene_path)
|
||||
if not scene is PackedScene:
|
||||
return
|
||||
node = scene.instantiate()
|
||||
if is_instance_valid(node):
|
||||
Global.control.get_node("Dialogs").add_child(node)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Global.project_switched.connect(_project_switched)
|
||||
Global.cel_switched.connect(_update_current_frame_mark)
|
||||
OpenSave.shader_copied.connect(_load_shader_file)
|
||||
_setup_file_menu()
|
||||
_setup_edit_menu()
|
||||
_setup_view_menu()
|
||||
|
@ -449,21 +454,52 @@ func _setup_effects_menu() -> void:
|
|||
"Desaturation": "desaturation",
|
||||
"Adjust Hue/Saturation/Value": "adjust_hsv",
|
||||
"Adjust Brightness/Contrast": "adjust_brightness_contrast",
|
||||
"Color Curves": "color_curves",
|
||||
"Palettize": "palettize",
|
||||
"Pixelize": "pixelize",
|
||||
"Posterize": "posterize",
|
||||
"Gaussian Blur": "gaussian_blur",
|
||||
"Gradient": "gradient",
|
||||
"Gradient Map": "gradient_map",
|
||||
# "Shader": ""
|
||||
"Loaded": ""
|
||||
}
|
||||
var i := 0
|
||||
for item in menu_items:
|
||||
_set_menu_shortcut(menu_items[item], effects_menu, i, item)
|
||||
if item == "Loaded":
|
||||
_setup_loaded_effects_submenu()
|
||||
else:
|
||||
_set_menu_shortcut(menu_items[item], effects_menu, i, item)
|
||||
i += 1
|
||||
effects_menu.id_pressed.connect(effects_menu_id_pressed)
|
||||
|
||||
|
||||
func _setup_loaded_effects_submenu() -> void:
|
||||
if not DirAccess.dir_exists_absolute(OpenSave.SHADERS_DIRECTORY):
|
||||
DirAccess.make_dir_recursive_absolute(OpenSave.SHADERS_DIRECTORY)
|
||||
var shader_files := DirAccess.get_files_at(OpenSave.SHADERS_DIRECTORY)
|
||||
if shader_files.size() == 0:
|
||||
return
|
||||
for shader_file in shader_files:
|
||||
_load_shader_file(OpenSave.SHADERS_DIRECTORY.path_join(shader_file))
|
||||
|
||||
|
||||
func _load_shader_file(file_path: String) -> void:
|
||||
var file := load(file_path)
|
||||
if file is not Shader:
|
||||
return
|
||||
var effect_name := file_path.get_file().get_basename()
|
||||
if not is_instance_valid(loaded_effects_submenu):
|
||||
loaded_effects_submenu = PopupMenu.new()
|
||||
loaded_effects_submenu.set_name("loaded_effects_submenu")
|
||||
loaded_effects_submenu.id_pressed.connect(_loaded_effects_submenu_id_pressed)
|
||||
effects_menu.add_child(loaded_effects_submenu)
|
||||
effects_menu.add_submenu_item("Loaded", loaded_effects_submenu.get_name())
|
||||
loaded_effects_submenu.add_item(effect_name)
|
||||
var effect_index := loaded_effects_submenu.item_count - 1
|
||||
loaded_effects_submenu.set_item_metadata(effect_index, file)
|
||||
loaded_effect_dialogs.append(Dialog.new("res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn"))
|
||||
|
||||
|
||||
func _setup_select_menu() -> void:
|
||||
# Order as in Global.SelectMenu enum
|
||||
var select_menu_items := {
|
||||
|
@ -768,6 +804,17 @@ func _snap_to_submenu_id_pressed(id: int) -> void:
|
|||
snap_to_submenu.set_item_checked(id, Global.snap_to_perspective_guides)
|
||||
|
||||
|
||||
func _loaded_effects_submenu_id_pressed(id: int) -> void:
|
||||
var dialog := loaded_effect_dialogs[id]
|
||||
if is_instance_valid(dialog.node):
|
||||
dialog.popup()
|
||||
else:
|
||||
dialog.instantiate_scene()
|
||||
var shader := loaded_effects_submenu.get_item_metadata(id) as Shader
|
||||
dialog.node.change_shader(shader, loaded_effects_submenu.get_item_text(id))
|
||||
dialog.popup()
|
||||
|
||||
|
||||
func _panels_submenu_id_pressed(id: int) -> void:
|
||||
if zen_mode:
|
||||
return
|
||||
|
@ -934,6 +981,8 @@ func effects_menu_id_pressed(id: int) -> void:
|
|||
hsv_dialog.popup()
|
||||
Global.EffectsMenu.BRIGHTNESS_SATURATION:
|
||||
adjust_brightness_saturation_dialog.popup()
|
||||
Global.EffectsMenu.COLOR_CURVES:
|
||||
color_curves_dialog.popup()
|
||||
Global.EffectsMenu.GAUSSIAN_BLUR:
|
||||
gaussian_blur_dialog.popup()
|
||||
Global.EffectsMenu.GRADIENT:
|
||||
|
@ -946,8 +995,6 @@ func effects_menu_id_pressed(id: int) -> void:
|
|||
pixelize_dialog.popup()
|
||||
Global.EffectsMenu.POSTERIZE:
|
||||
posterize_dialog.popup()
|
||||
#Global.EffectsMenu.SHADER:
|
||||
#shader_effect_dialog.popup()
|
||||
_:
|
||||
_handle_metadata(id, effects_menu)
|
||||
|
||||
|
|
Loading…
Reference in a new issue