Port to Godot 4 (#900)
* Initial conversion * Hide some dialogs * Update addons * Fix errors in scripts Temporarily commented out adding new file import types and certain Control methods that are not available in the Window class, such as get_global_mouse_position() * Update shaders * Fix some more errors and signals, rename "pressed" to "button_pressed" * Even more error fixes and renaming corrections * Fixed more errors, Pixelorama almost runs * Update ValueSlider.gd * Remove lock() and unlock(), more ImageTexture.create_from_image() static usage * More static function using * Re-add some of the dialog signals, fix window transparency * Change instances of popup_hide to visibility_changed * More more errors and warnings * Fix more errors and warnings * Get rid of errors in the output when opening Pixelorama in Godot * Properly connect most signals without using strings * Fix some scenes * Don't load Main.tscn * Emit signals directly instead of using strings * Fix Keychain menu nodes * Get rid of self. on most instances, as setters and getters are now always called * Some more static typing * Disable texture filters * Fix zooming * Fix int as enum warnings * Fix tools and rename doubleclick to double_click * Update tool scenes * Fix tabs * Fix create new image * Use static typing on flood fill to speed it up * Update static-checks.yml * Reverts #729 for a speedup, hopefully the bug won't get re-introduced * Fix TransparentChecker warning * Re-add Default template * Fix 3D cels * Fix rotation Project converted bug. Should be fixed by https://github.com/godotengine/godot/pull/79264 * Fix UITransparency alignment issue, thanks Variable * Add missing OptionButton items Hopefully that should be all of them * Fix the appearance of CollapsibleContainer * Change instances of world to world_3d * Fix tool button backgrounds * Fix Splash dialog * Fix brush selection * Update Main.gd * Fix About Dialog * Fix more zooming issues * Fix canvas preview zooming * Use signals for queue_redraw on project change * Fix layer button's look * Fix gradients * Some gradient fixes and code cleanups, dithering is still broken * Fix bucket * Fix the rest of the undo_redo.add_(un)do_method() cases * Fix guides * Fix guide text * Some small changes in Main * Update Tools.gd * Fix palette importing * Get rid of TODOGODOT4s * Fix the rest of the dialogs * Update the rest of the scenes * Fix onion skinning and frame tag dialogs * Fix file brushes being imported twice * Fix palette swatch crashing on double click * Use nearest filter for some of the windows * Remove old .tres font files * Fix language switching * Get rid of Keychain.action_get_first_key() on the extra shortcuts of tools * Get rid of Keychain.MenuInputActions and directly set shortcuts to the menu items This temporarily removes echoing support for undo and redo, this will be re-added once https://github.com/godotengine/godot/pull/36493 or https://github.com/godotengine/godot/pull/64317 is merged. * Clean shortcut-related duplicate code in TopMenuContainer * Remove DroidSansFallback now that system fonts can be used as fallback * Remove 3.x settings from project.godot * Format * Format gdgifexporter * Reset Keychain to its original state * Remove textures from the dark and gray themes * Remove all textures from the dark theme * Better static typing in DrawingAlgos * Use Vector2i for project size * [Risky commit] Use Vector2i instead of Vector2 for tools I tested it and everything seems to be working the same as before, but more testing would be appreciated. * Format after previous commit * Fix line angle constraint being rotated 180 degrees This is not a regression from the previous commit(s), Godot 4 probably reversed the logic of `angle_to_point()`. * Fix input map action not found errors when pressing Shift or Control * Make AnimatePanel bigger, add spring interpolation * Fix some layouts/extensions/preferences loading errors * Fix dithering * Update layout resources Probably doesn't change anything at all, but I suppose it might be a good thing to do * Small changes * Disable filter in ResizeCanvas dialog * Fix some preferences default button states * Fix tile mode always having masking on * Use integers in tile mode * Fix checkboxes in preferences not working * More statically typed arrays! No need to have these # Array of X comments anymore! * Fix "apply all" for multiple preview dialogs * Update theme.tres * Add HeaderSmall theme type variation * Fix dynamics buttons * Don't allow sub-zero zoom values * Let zoom_out_max always remain Vector2(0.01, 0.01) This fixes zooming on large canvases * Bump version to v1.0-dev * Fix ambient light not working on 3D cels * Fix .obj loading * Don't allow greater than max values in the zoom slider * Set maximum zoom value to always be (500, 500) * Set zoom slider minimum value to 1 * Some UI changes, mostly related to buttons and the timeline * Change window titles to what they were before * [COMPATIBILITY BROKEN WITH v0.x] Fix loading 3D cels * Avoid changing Cel3DObject's file_path if it's the same * Make preferences window bigger * Fix png exporting * Fix reference image initial size and filter setting * Fix perspective line reverse scaling on zoom and facing 180 degrees away from cursor * Format and some linting * Remove most Images from the rest of the themes * Remove all textures from all themes * Fix drawing when the mouse gets released outside the canvas boundaries * Format Keychain * Implement #890 * Fix recorder * Fix layout deletion * Better static typing and fix empty_clicked signal-connected methods not having arguments * Fix layout and extension directory creation if they don't already exist * Change all instances of "HTML5" to "Web" OS.get_name() now returns "Web" instead of "HTML5" in Godot 4 * Fix JavaScript detection Opening files in the Web version does not yet work for some reason * Fix formatting * Fix lint errors * Remove unneeded lines from rotation shaders * Clean some rotation shader related code * Remove ErrorManager from #891, as it's no longer needed in Godot 4 * Some docstrings * More Vector2i and Recti replacing their float counterparts * Remove the hardcoded shortcut from ValueSlider Note that the brush size shortcut may not change properly because Keychain isn't updated to support Godot 4's input map properly yet. * Fix bugs from the rebase, integer zooming is currently broken * Format * Fix bug where some imported images would fail to load when using smart slice * Fix integer zooming (I think) * Fix errors after #898 * Fix some UI issues with PreviewDialog * Use ctrl/command instead of just ctrl in the shortcuts of the InputMap - gets rid of the need for _use_osx_shortcuts() in Main.gd * Update Keychain and addons/README.md * Update CI to Godot 4.1.1 (probably will not work) * Remove XDGDataPaths.gd * Make windows non-exclusive * Attempt to fix macOS CI * Attempt to fix CI * Attempt to fix CI * Minor fix in the dark theme, more will follow * Silence enumerator/integer warning * Attempt to fix macOS CI * Another attempt to fix macOS CI * Attempt to fix Windows & macOS CI * fix: Recorder directory create (#903) * Update Keychain so that the brush size shortcuts can be changed This update generally lets users use modifier buttons (control, alt, shift, meta) with mouse buttons * Change OSX to macOS * Detect if multi-threading is enabled when exporting gifs * Fix color picker not working on the top color mode * Make some public methods private in Export.gd * Remove Global.window_title variable * Fix frame UI in the timeline breaking after 100 frames * Static typing improvements for the timeline * Better static typing for grids * Fix typo * Fix pixel grid not appearing * Move preference updating code to Global using setters This should make the code a bit more readable, as the logic for each property update can be found directly under the variable declaration, and not hidden in PreferencesDialog's preference_update() method. This also allows for changing these properties outside the preferences, if that will ever be needed. In theory it's also faster as we don't have to do all these string comparisons anymore, but I doubt this will be noticeable in practice. * Remove RestoreDefaultButton.tscn * Implement changing font size in the preferences * Resize HeaderSmall font size along with the default font size * A step towards fixing image loading in the Web version Doesn't completely fix the issue, it requires a fix from Godot's side as well * Implement missing input event actions for buttons TODO: Add default shortcuts * Do not change language and theme if they are already the defaults Reduces the initial loading time a bit * Remove update_hint_tooltips() as it's no longer needed, only keep it for tools This was needed in order for hint tooltips to be successfully translated while taking the shortcuts into account. Godot 4 already it correctly for us now. Tools still need it because they contain multiple shortcut data in their tooltips. * Change ExportDialog's PathDialog's file mode to be "open directory" and make them bigger * Fix Vector2i + Vector2 errors in grid center snapping * Update tooltips when the shortcut profile changes * Fix copy-paste mistake * Update tooltips during startup if the shortcut profile is not the default * Fix gif warning label size in ExportDialog * Fix BBCode in ExportDialog * Fix some Godot 4.2 warnings * Some CI fixes * Static typing improvements and more inline functions * Format * Even more static typing, inline methods, docstrings etc * Some more static typing improvements and inline setters * Remove unneeded project type specifying * Fix splash dialog error * Fix enumerator warning * Don't preload the font in the rules and guides * Fix some integer division warnings Sometimes we indeed need them to be floats * Change some Rect2s to Rect2is * Minor static typing improvements * Update README, CHANGELOG, Translations * Only load translation files when needed, reduces loading time a bit * Update Keychain so it doesn't load languages during startup * Lazy load all tool scenes, breaks compatibility with the extension API Decreases initial loading time * Format * Very minor loading time speedups * Remove unneeded project type specifying * Even more static typing and docstring improvements * Fix extension loading * Palette docstrings --------- Co-authored-by: ppphp <kevinniub@gmail.com>
|
@ -1,5 +1,7 @@
|
|||
disable:
|
||||
- no-elif-return
|
||||
- no-else-return
|
||||
- max-returns
|
||||
- private-method-call
|
||||
|
||||
max-file-lines: 2000
|
||||
|
|
61
.github/workflows/dev-desktop-builds.yml
vendored
|
@ -10,15 +10,12 @@ on:
|
|||
- "installer/*.pot"
|
||||
- "installer/po/*"
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-devdesktop
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GODOT_VERSION: 3.5.2
|
||||
GODOT_VERSION_MAC: 3.5.2
|
||||
RASPBERRY_PI_BUILDS_VERSION: 1.15.0
|
||||
GODOT_VERSION: 4.1.1
|
||||
EXPORT_NAME: Pixelorama
|
||||
|
||||
jobs:
|
||||
|
@ -26,7 +23,7 @@ jobs:
|
|||
name: Windows Export 🗔
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://barichello/godot-ci:3.5.2
|
||||
image: docker://barichello/godot-ci:4.1.1
|
||||
steps:
|
||||
- name: Setup WINE and rcedit 🍷
|
||||
run: |
|
||||
|
@ -35,19 +32,19 @@ jobs:
|
|||
wget https://github.com/electron/rcedit/releases/download/v1.1.1/rcedit-x64.exe
|
||||
mkdir -v -p ~/.local/share/rcedit
|
||||
mv rcedit-x64.exe ~/.local/share/rcedit
|
||||
godot -q
|
||||
echo 'export/windows/wine = "/usr/bin/wine"' >> ~/.config/godot/editor_settings-3.tres
|
||||
echo 'export/windows/rcedit = "/github/home/.local/share/rcedit/rcedit-x64.exe"' >> ~/.config/godot/editor_settings-3.tres
|
||||
godot --headless --quit
|
||||
echo 'export/windows/wine = "/usr/bin/wine"' >> ~/.config/godot/editor_settings-4.tres
|
||||
echo 'export/windows/rcedit = "/github/home/.local/share/rcedit/rcedit-x64.exe"' >> ~/.config/godot/editor_settings-4.tres
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup 💻
|
||||
run: |
|
||||
mkdir -v -p build/windows-64bit ~/.local/share/godot/templates
|
||||
mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable
|
||||
mkdir -v -p build/windows-64bit ~/.local/share/godot/export_templates
|
||||
mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable
|
||||
- name: Windows Build 🔧
|
||||
run: godot -v --export "Windows Desktop 64-bit" ./build/windows-64bit/$EXPORT_NAME.exe
|
||||
run: godot --headless -v --export-release "Windows Desktop 64-bit" ./build/windows-64bit/$EXPORT_NAME.exe
|
||||
- name: Copy pixelorama_data folder 📁
|
||||
run: |
|
||||
cp -R ./pixelorama_data ./build/windows-64bit
|
||||
|
@ -63,7 +60,7 @@ jobs:
|
|||
name: Linux Export 🐧
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://barichello/godot-ci:3.5.2
|
||||
image: docker://barichello/godot-ci:4.1.1
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
|
@ -71,42 +68,28 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup 💻
|
||||
run: |
|
||||
mkdir -v -p build/linux-64bit build/linux-rpi4 ~/.local/share/godot/templates
|
||||
mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable
|
||||
- name: Download Unofficial Godot Raspberry Pi 4 Builds 🍇
|
||||
run: |
|
||||
wget https://github.com/hiulit/Unofficial-Godot-Engine-Raspberry-Pi/releases/download/v${RASPBERRY_PI_BUILDS_VERSION}/godot_${GODOT_VERSION}-stable_rpi4.zip
|
||||
unzip -a godot_${GODOT_VERSION}-stable_rpi4.zip
|
||||
mkdir -v -p build/linux-64bit ~/.local/share/godot/export_templates
|
||||
mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable
|
||||
- name: Linux Build 🔧
|
||||
run: |
|
||||
godot -v --export "Linux/X11 64-bit" ./build/linux-64bit/$EXPORT_NAME.x86_64
|
||||
godot -v --export "Raspberry Pi 4" ./build/linux-rpi4/$EXPORT_NAME.rpi4
|
||||
godot --headless -v --export-release "Linux/X11 64-bit" ./build/linux-64bit/$EXPORT_NAME.x86_64
|
||||
- name: Give execute permission ☑️
|
||||
run: |
|
||||
chmod +x ./build/linux-64bit/$EXPORT_NAME.x86_64
|
||||
chmod +x ./build/linux-rpi4/$EXPORT_NAME.rpi4
|
||||
- name: Copy pixelorama_data folder 📁
|
||||
run: |
|
||||
rm ./pixelorama_data/.gdignore
|
||||
cp -R ./pixelorama_data ./build/linux-64bit
|
||||
cp -R ./pixelorama_data ./build/linux-rpi4
|
||||
- name: Create tar.gz archive 🗜️
|
||||
run: |
|
||||
cd build
|
||||
tar zcvf linux-64bit.tar.gz linux-64bit
|
||||
tar zcvf linux-rpi4.tar.gz linux-rpi4
|
||||
- name: Upload Artifact 🚀
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Linux-64bit
|
||||
path: ./build/linux-64bit.tar.gz
|
||||
retention-days: 14
|
||||
- name: Upload Raspberry Pi 4 Artifact 🚀
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Linux-rpi4
|
||||
path: ./build/linux-rpi4.tar.gz
|
||||
retention-days: 14
|
||||
|
||||
export-mac:
|
||||
name: Mac Export 🍎
|
||||
|
@ -118,25 +101,25 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup environment 🔧
|
||||
run: |
|
||||
export GODOT_VERSION=${GODOT_VERSION_MAC}
|
||||
export GODOT_VERSION=${GODOT_VERSION}
|
||||
export EXPORT_NAME=Pixelorama
|
||||
- name: Download and extract export templates 💾
|
||||
run: |
|
||||
mkdir -v -p ~/.local/share/godot/templates/${GODOT_VERSION_MAC}.stable
|
||||
curl -O https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION_MAC}/Godot_v${GODOT_VERSION_MAC}-stable_export_templates.tpz
|
||||
unzip -a Godot_v${GODOT_VERSION_MAC}-stable_export_templates.tpz
|
||||
mv ./templates/* ~/.local/share/godot/templates/${GODOT_VERSION_MAC}.stable
|
||||
mkdir -v -p "/Users/runner/Library/Application Support/Godot/export_templates/${GODOT_VERSION}.stable"
|
||||
curl -O https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}/Godot_v${GODOT_VERSION}-stable_export_templates.tpz
|
||||
unzip -a Godot_v${GODOT_VERSION}-stable_export_templates.tpz
|
||||
mv ./templates/* "/Users/runner/Library/Application Support/Godot/export_templates/${GODOT_VERSION}.stable"
|
||||
- name: Download Godot headless binary 🤖
|
||||
run: |
|
||||
wget https://github.com/huskeee/godot-headless-mac/releases/download/${GODOT_VERSION_MAC}-stable/Godot_v${GODOT_VERSION_MAC}-stable_mac_headless.64.zip
|
||||
unzip -a Godot_v${GODOT_VERSION_MAC}-stable_mac_headless.64.zip
|
||||
wget https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}-stable/Godot_v${GODOT_VERSION}-stable_macos.universal.zip
|
||||
unzip -a Godot_v${GODOT_VERSION}-stable_macos.universal.zip
|
||||
- name: Setup 💻
|
||||
run: mkdir -v -p ./build/mac
|
||||
- name: Mac Build 🔧
|
||||
run: |
|
||||
chown runner ./bin/Godot
|
||||
chmod +x ./bin/Godot
|
||||
./bin/Godot -v --export "Mac OSX" ./build/mac/Pixelorama.zip
|
||||
chown runner ./Godot.app/Contents/MacOS/Godot
|
||||
chmod +x ./Godot.app/Contents/MacOS/Godot
|
||||
./Godot.app/Contents/MacOS/Godot --headless -v --export-release "macOS" ./build/mac/Pixelorama.zip
|
||||
- name: Make application executable 🔧
|
||||
run: |
|
||||
unzip -a ./build/mac/Pixelorama.zip -d ./build/mac
|
||||
|
|
10
.github/workflows/dev-web.yml
vendored
|
@ -9,7 +9,7 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GODOT_VERSION: 3.5.2
|
||||
GODOT_VERSION: 4.1.1
|
||||
EXPORT_NAME: Pixelorama
|
||||
|
||||
jobs:
|
||||
|
@ -17,7 +17,7 @@ jobs:
|
|||
name: Web Export 🌐
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://barichello/godot-ci:3.5.2
|
||||
image: docker://barichello/godot-ci:4.1.1
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
|
@ -25,10 +25,10 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup 💻
|
||||
run: |
|
||||
mkdir -v -p build/web ~/.local/share/godot/templates
|
||||
mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable
|
||||
mkdir -v -p build/web ~/.local/share/godot/export_templates
|
||||
mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable
|
||||
- name: Web Build 🔧
|
||||
run: godot -v --export "HTML5" ./build/web/index.html
|
||||
run: godot --headless -v --export-release "Web" ./build/web/index.html
|
||||
- name: Install rsync 📚
|
||||
run: |
|
||||
apt-get update && apt-get install -y rsync
|
||||
|
|
65
.github/workflows/release.yml
vendored
|
@ -5,11 +5,9 @@ on:
|
|||
branches: [ release ]
|
||||
|
||||
env:
|
||||
GODOT_VERSION: 3.5.2
|
||||
GODOT_VERSION_MAC: 3.5.2
|
||||
RASPBERRY_PI_BUILDS_VERSION: 1.15.0
|
||||
GODOT_VERSION: 4.1.1
|
||||
EXPORT_NAME: Pixelorama
|
||||
TAG: v0.11.2
|
||||
TAG: v1.0
|
||||
BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
|
||||
|
||||
jobs:
|
||||
|
@ -17,7 +15,7 @@ jobs:
|
|||
name: Windows Export 🗔
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://barichello/godot-ci:3.5.2
|
||||
image: docker://barichello/godot-ci:4.1.1
|
||||
steps:
|
||||
- name: Setup WINE, rcedit and NSIS 🍷
|
||||
run: |
|
||||
|
@ -26,21 +24,21 @@ jobs:
|
|||
wget https://github.com/electron/rcedit/releases/download/v1.1.1/rcedit-x64.exe
|
||||
mkdir -v -p ~/.local/share/rcedit
|
||||
mv rcedit-x64.exe ~/.local/share/rcedit
|
||||
godot -q
|
||||
echo 'export/windows/wine = "/usr/bin/wine"' >> ~/.config/godot/editor_settings-3.tres
|
||||
echo 'export/windows/rcedit = "/github/home/.local/share/rcedit/rcedit-x64.exe"' >> ~/.config/godot/editor_settings-3.tres
|
||||
godot --headless --quit
|
||||
echo 'export/windows/wine = "/usr/bin/wine"' >> ~/.config/godot/editor_settings-4.tres
|
||||
echo 'export/windows/rcedit = "/github/home/.local/share/rcedit/rcedit-x64.exe"' >> ~/.config/godot/editor_settings-4.tres
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup 💻
|
||||
run: |
|
||||
mkdir -v -p build/windows-64bit build/windows-32bit ~/.local/share/godot/templates
|
||||
mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable
|
||||
mkdir -v -p build/windows-64bit build/windows-32bit ~/.local/share/godot/export_templates
|
||||
mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable
|
||||
- name: Windows Build 🔧
|
||||
run: |
|
||||
godot -v --export "Windows Desktop 64-bit" ./build/windows-64bit/$EXPORT_NAME.exe
|
||||
godot -v --export "Windows Desktop 32-bit" ./build/windows-32bit/$EXPORT_NAME.exe
|
||||
godot --headless -v --export-release "Windows Desktop 64-bit" ./build/windows-64bit/$EXPORT_NAME.exe
|
||||
godot --headless -v --export-release "Windows Desktop 32-bit" ./build/windows-32bit/$EXPORT_NAME.exe
|
||||
- name: Copy pixelorama_data folder 📁
|
||||
run: |
|
||||
rm ./pixelorama_data/.gdignore
|
||||
|
@ -76,7 +74,7 @@ jobs:
|
|||
name: Linux Export 🐧
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://barichello/godot-ci:3.5.2
|
||||
image: docker://barichello/godot-ci:4.1.1
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
|
@ -84,39 +82,30 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup 💻
|
||||
run: |
|
||||
mkdir -v -p build/linux-64bit build/linux-32bit build/linux-rpi4 ~/.local/share/godot/templates
|
||||
mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable
|
||||
- name: Download Unofficial Godot Raspberry Pi 4 Builds 🍇
|
||||
run: |
|
||||
wget https://github.com/hiulit/Unofficial-Godot-Engine-Raspberry-Pi/releases/download/v${RASPBERRY_PI_BUILDS_VERSION}/godot_${GODOT_VERSION}-stable_rpi4.zip
|
||||
unzip -a godot_${GODOT_VERSION}-stable_rpi4.zip
|
||||
mkdir -v -p build/linux-64bit build/linux-32bit ~/.local/share/godot/export_templates
|
||||
mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable
|
||||
- name: Linux Build 🔧
|
||||
run: |
|
||||
godot -v --export "Linux/X11 64-bit" ./build/linux-64bit/$EXPORT_NAME.x86_64
|
||||
godot -v --export "Linux/X11 32-bit" ./build/linux-32bit/$EXPORT_NAME.x86
|
||||
godot -v --export "Raspberry Pi 4" ./build/linux-rpi4/$EXPORT_NAME.rpi4
|
||||
godot --headless -v --export-release "Linux/X11 64-bit" ./build/linux-64bit/$EXPORT_NAME.x86_64
|
||||
godot --headless -v --export-release "Linux/X11 32-bit" ./build/linux-32bit/$EXPORT_NAME.x86
|
||||
- name: Give execute permission ☑️
|
||||
run: |
|
||||
chmod +x ./build/linux-64bit/$EXPORT_NAME.x86_64
|
||||
chmod +x ./build/linux-32bit/$EXPORT_NAME.x86
|
||||
chmod +x ./build/linux-rpi4/$EXPORT_NAME.rpi4
|
||||
- name: Copy pixelorama_data folder 📁
|
||||
run: |
|
||||
rm ./pixelorama_data/.gdignore
|
||||
cp -R ./pixelorama_data ./build/linux-64bit
|
||||
cp -R ./pixelorama_data ./build/linux-32bit
|
||||
cp -R ./pixelorama_data ./build/linux-rpi4
|
||||
- name: Create tar.gz archive 🗜️
|
||||
run: |
|
||||
cd build
|
||||
tar zcvf Pixelorama.Linux-64bit.tar.gz linux-64bit
|
||||
tar zcvf Pixelorama.Linux-32bit.tar.gz linux-32bit
|
||||
tar zcvf Pixelorama.Linux-RPI4.tar.gz linux-rpi4
|
||||
- name: Upload Release Assets to itch.io 🎮
|
||||
run: |
|
||||
butler push ./build/Pixelorama.Linux-64bit.tar.gz ${{ secrets.ITCHIO_USERNAME }}/${{ secrets.ITCHIO_GAME }}:linux-64 --userversion ${{env.TAG}}
|
||||
butler push ./build/Pixelorama.Linux-32bit.tar.gz ${{ secrets.ITCHIO_USERNAME }}/${{ secrets.ITCHIO_GAME }}:linux-32 --userversion ${{env.TAG}}
|
||||
butler push ./build/Pixelorama.Linux-RPI4.tar.gz ${{ secrets.ITCHIO_USERNAME }}/${{ secrets.ITCHIO_GAME }}:linux-rpi4 --userversion ${{env.TAG}}
|
||||
- name: Upload Release Asset 🚀
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
|
@ -136,25 +125,25 @@ jobs:
|
|||
submodules: true
|
||||
- name: Setup environment 🔧
|
||||
run: |
|
||||
export GODOT_VERSION=${GODOT_VERSION_MAC}
|
||||
export GODOT_VERSION=${GODOT_VERSION}
|
||||
export EXPORT_NAME=Pixelorama
|
||||
- name: Download and extract export templates 💾
|
||||
run: |
|
||||
mkdir -v -p ~/.local/share/godot/templates/${GODOT_VERSION_MAC}.stable
|
||||
curl -O https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION_MAC}/Godot_v${GODOT_VERSION_MAC}-stable_export_templates.tpz
|
||||
unzip -a Godot_v${GODOT_VERSION_MAC}-stable_export_templates.tpz
|
||||
mv ./templates/* ~/.local/share/godot/templates/${GODOT_VERSION_MAC}.stable
|
||||
mkdir -v -p "/Users/runner/Library/Application Support/Godot/export_templates/${GODOT_VERSION}.stable"
|
||||
curl -O https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}/Godot_v${GODOT_VERSION}-stable_export_templates.tpz
|
||||
unzip -a Godot_v${GODOT_VERSION}-stable_export_templates.tpz
|
||||
mv ./templates/* "/Users/runner/Library/Application Support/Godot/export_templates/${GODOT_VERSION}.stable"
|
||||
- name: Download Godot headless binary 🤖
|
||||
run: |
|
||||
wget https://github.com/huskeee/godot-headless-mac/releases/download/${GODOT_VERSION_MAC}-stable/Godot_v${GODOT_VERSION_MAC}-stable_mac_headless.64.zip
|
||||
unzip -a Godot_v${GODOT_VERSION_MAC}-stable_mac_headless.64.zip
|
||||
wget https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}-stable/Godot_v${GODOT_VERSION}-stable_macos.universal.zip
|
||||
unzip -a Godot_v${GODOT_VERSION}-stable_macos.universal.zip
|
||||
- name: Setup 💻
|
||||
run: mkdir -v -p ./build/mac
|
||||
- name: Mac Build 🔧
|
||||
run: |
|
||||
chown runner ./bin/Godot
|
||||
chmod +x ./bin/Godot
|
||||
./bin/Godot -v --export "Mac OSX" ./build/mac/Pixelorama.zip
|
||||
chown runner ./Godot.app/Contents/MacOS/Godot
|
||||
chmod +x ./Godot.app/Contents/MacOS/Godot
|
||||
./Godot.app/Contents/MacOS/Godot --headless -v --export-release "macOS" ./build/mac/Pixelorama.zip
|
||||
- name: Make application executable 🔧
|
||||
run: |
|
||||
unzip -a ./build/mac/Pixelorama.zip -d ./build/mac
|
||||
|
@ -189,7 +178,7 @@ jobs:
|
|||
name: Web Export 🌐
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker://barichello/godot-ci:3.5.2
|
||||
image: docker://barichello/godot-ci:4.1.1
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
|
@ -200,7 +189,7 @@ jobs:
|
|||
mkdir -v -p build/web ~/.local/share/godot/templates
|
||||
mv /root/.local/share/godot/templates/${GODOT_VERSION}.stable ~/.local/share/godot/templates/${GODOT_VERSION}.stable
|
||||
- name: Web Build 🔧
|
||||
run: godot -v --export "HTML5" ./build/web/index.html
|
||||
run: godot --headless -v --export-release "Web" ./build/web/index.html
|
||||
- name: Install rsync 📚
|
||||
run: |
|
||||
apt-get update && apt-get install -y rsync
|
||||
|
|
2
.github/workflows/static-checks.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install gdtoolkit
|
||||
run: pip3 install "gdtoolkit==3.*"
|
||||
run: pip3 install "gdtoolkit==4.*"
|
||||
- name: Formatting checks
|
||||
run: gdformat --diff .
|
||||
- name: Linting checks
|
||||
|
|
17
CHANGELOG.md
|
@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). All the dates are in YYYY-MM-DD format.
|
||||
<br><br>
|
||||
|
||||
## [v1.0] - Unreleased
|
||||
|
||||
Built using Godot 4.1.1
|
||||
|
||||
### Added
|
||||
- Added some missing shortcuts for buttons. [#900](https://github.com/Orama-Interactive/Pixelorama/pull/900)
|
||||
- The brush increment/decrement shortcuts can now be changed. [#900](https://github.com/Orama-Interactive/Pixelorama/pull/900)
|
||||
- 3D layers now support torus shapes. [#900](https://github.com/Orama-Interactive/Pixelorama/pull/900)
|
||||
- Image effect animation now supports the tweening transition method of spring. [#900](https://github.com/Orama-Interactive/Pixelorama/pull/900)
|
||||
|
||||
### Changed
|
||||
- Every shader-based image effect is automatically working without the need to change renderers, and they all work now on the Web version. This comes at the cost of less compatibility, as the desktop version now requires OpenGL 3.3 minimum instead of 2.1, and the Web version requires WebGL 2 instead of WebGL 1. [#900](https://github.com/Orama-Interactive/Pixelorama/pull/900)
|
||||
|
||||
### Fixed
|
||||
- Performance when drawing and doing operations such as bucket area fill should be better now. [#900](https://github.com/Orama-Interactive/Pixelorama/pull/900)
|
||||
- Dividing by zero in value sliders and spinboxes no longer crashes the program.
|
||||
|
||||
## [v0.11.2] - 2023-08-31
|
||||
This update has been brought to you by the contributions of:
|
||||
[@Lsbt1](https://github.com/Lsbt1), Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind))
|
||||
|
|
|
@ -61,7 +61,7 @@ You can find Online Documentation for Pixelorama here: https://orama-interactive
|
|||
It's still work in progress so there are some pages missing. If you want to contribute, you can do so in [Pixelorama-Docs' GitHub Repository](https://github.com/Orama-Interactive/Pixelorama-Docs).
|
||||
|
||||
## Cloning Instructions
|
||||
Pixelorama uses Godot 3.5, so you will need to have it in order to run the project. Older versions may not work.
|
||||
Pixelorama uses Godot 4.1, so you will need to have it in order to run the project. Older versions will not work.
|
||||
As of right now, most of the code is written using GDScript, so the mono version of Godot is not required, but Pixelorama should also work with it.
|
||||
|
||||
## Current features:
|
||||
|
|
|
@ -1655,27 +1655,21 @@ msgid "Current frame as spritesheet"
|
|||
msgstr ""
|
||||
|
||||
msgid "Jump to the first frame\n"
|
||||
"(%s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to the previous frame\n"
|
||||
"(%s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Play the animation backwards (from end to beginning)\n"
|
||||
"(%s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Play the animation forward (from beginning to end)\n"
|
||||
"(%s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to the next frame\n"
|
||||
"(%s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jump to the last frame\n"
|
||||
"(%s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Onion Skinning settings"
|
||||
|
@ -2614,6 +2608,10 @@ msgstr ""
|
|||
msgid "Backing out at ends"
|
||||
msgstr ""
|
||||
|
||||
#. Found under certain image effects that support properties that can be animated. A type of interpolation.
|
||||
msgid "Spring towards the end"
|
||||
msgstr ""
|
||||
|
||||
msgid "Silhouette"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -3,19 +3,19 @@
|
|||
## aimgio
|
||||
|
||||
- Upstream: https://gitlab.com/20kdc/20kdc_godot_addons
|
||||
- Version: Presently git commit b6a1411758c856ad543f4f661ca4105aa7c0ab6d
|
||||
- License: [Unlicense](https://gitlab.com/20kdc/20kdc_godot_addons/-/blob/b6a1411758c856ad543f4f661ca4105aa7c0ab6d/addons/aimg_io/COPYING.txt)
|
||||
- Version: ba00188e9da1ae229181f106788fcb72ccdd85fa
|
||||
- License: [Unlicense](https://gitlab.com/20kdc/20kdc_godot_addons/-/blob/master/godot4/addons/aimg_io/COPYING.txt)
|
||||
|
||||
## Keychain
|
||||
|
||||
- Upstream: https://github.com/Orama-Interactive/Keychain
|
||||
- Version: Based on git commit 22539c2b9b9e781c0cd54f32d92c77b6f36837e0 with slight modifications in Keychain.gd and ShortcutEdit.tscn.
|
||||
- Version: 4a08d6b380929031b2c9202811ae0a65d850a5ac
|
||||
- License: [MIT](https://github.com/Orama-Interactive/Keychain/blob/main/LICENSE)
|
||||
|
||||
## gdgifexporter
|
||||
|
||||
- Upstream: https://github.com/jegor377/godot-gdgifexporter
|
||||
- Version: custom
|
||||
- Version: c368952a97bbbbcfa491358a61043be7009536a6
|
||||
- License: [MIT](https://github.com/jegor377/godot-gdgifexporter/blob/master/LICENSE)
|
||||
|
||||
Files extracted from source:
|
||||
|
@ -24,13 +24,13 @@ Files extracted from source:
|
|||
## godot-dockable-container
|
||||
|
||||
- Upstream: https://github.com/gilzoide/godot-dockable-container
|
||||
- Version: Based on git commit e5df60ed1d53246e03dba36053ff009846ba5174 with a modification on dockable_container.gd (lines 187-191).
|
||||
- Version: 9a93231d7f449aad039b28ff05bc1ef40a9956ce
|
||||
- License: [CC0-1.0](https://github.com/gilzoide/godot-dockable-container/blob/main/LICENSE)
|
||||
|
||||
## SmartSlicer
|
||||
|
||||
- Upstream: https://github.com/Variable-Interactive/SmartSlicer
|
||||
- Version: Based on git commit 2804e6109f9667022c66522ce88a99a56fd67ca8 with a modification on SmartSlicePreview.gd (lines 31-32). Only the contents of addons folder are used and the script SmartSlicePreview.gd is moved to res://src/UI/Dialogs/HelperScripts/ for better organization
|
||||
- Version: Based on git commit 5d65d2ff556fed878099c96546ceb307aa3bc030 with a modification on SmartSlicePreview.gd (lines 31-32). Only the contents of addons folder are used and the script SmartSlicePreview.gd is moved to res://src/UI/Dialogs/HelperScripts/ for better organization
|
||||
- License: [MIT](https://github.com/Variable-Interactive/SmartSlicer/blob/main/LICENSE)
|
||||
|
||||
|
||||
|
|
|
@ -1,19 +1,42 @@
|
|||
class_name RegionUnpacker
|
||||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
# THIS CLASS TAKES INSPIRATION FROM PIXELORAMA'S FLOOD FILL
|
||||
# AND HAS BEEN MODIFIED FOR OPTIMIZATION
|
||||
|
||||
var slice_thread := Thread.new()
|
||||
|
||||
var _include_boundary_threshold: int # the size of rect below which merging accounts for boundaty
|
||||
var _merge_dist: int # after crossing threshold the smaller image will merge with larger image
|
||||
# if it is within the _merge_dist
|
||||
var _include_boundary_threshold: int ## Τhe size of rect below which merging accounts for boundary
|
||||
## After crossing threshold the smaller image will merge with larger image
|
||||
## if it is within the _merge_dist
|
||||
var _merge_dist: int
|
||||
|
||||
# working array used as buffer for segments while flooding
|
||||
var _allegro_flood_segments: Array
|
||||
# results array per image while flooding
|
||||
var _allegro_image_segments: Array
|
||||
## Working array used as buffer for segments while flooding
|
||||
var _allegro_flood_segments: Array[Segment]
|
||||
## Results array per image while flooding
|
||||
var _allegro_image_segments: Array[Segment]
|
||||
|
||||
|
||||
class RectData:
|
||||
var rects: Array[Rect2i]
|
||||
var frame_size: Vector2i
|
||||
|
||||
func _init(_rects: Array[Rect2i], _frame_size: Vector2i):
|
||||
rects = _rects
|
||||
frame_size = _frame_size
|
||||
|
||||
|
||||
class Segment:
|
||||
var flooding := false
|
||||
var todo_above := false
|
||||
var todo_below := false
|
||||
var left_position := -5
|
||||
var right_position := -5
|
||||
var y := 0
|
||||
var next := 0
|
||||
|
||||
func _init(_y: int) -> void:
|
||||
y = _y
|
||||
|
||||
|
||||
func _init(threshold: int, merge_dist: int) -> void:
|
||||
|
@ -21,63 +44,58 @@ func _init(threshold: int, merge_dist: int) -> void:
|
|||
_merge_dist = merge_dist
|
||||
|
||||
|
||||
func get_used_rects(image: Image) -> Dictionary:
|
||||
if OS.get_name() == "HTML5":
|
||||
func get_used_rects(image: Image) -> RectData:
|
||||
if ProjectSettings.get_setting("rendering/driver/threads/thread_model") != 2:
|
||||
# Single-threaded mode
|
||||
return get_rects(image)
|
||||
else:
|
||||
# If Thread model is set to "Multi-Threaded" in project settings>threads>thread model
|
||||
if slice_thread.is_active():
|
||||
else: # Multi-threaded mode
|
||||
if slice_thread.is_started():
|
||||
slice_thread.wait_to_finish()
|
||||
var error = slice_thread.start(self, "get_rects", image)
|
||||
var error := slice_thread.start(get_rects.bind(image))
|
||||
if error == OK:
|
||||
return slice_thread.wait_to_finish()
|
||||
else:
|
||||
return get_rects(image)
|
||||
# If Thread model is set to "Single-Safe" in project settings>threads>thread model then
|
||||
# comment the above code and uncomment below
|
||||
#return get_rects({"image": image})
|
||||
|
||||
|
||||
func get_rects(image: Image) -> Dictionary:
|
||||
# make a smaller image to make the loop shorter
|
||||
var used_rect = image.get_used_rect()
|
||||
if used_rect.size == Vector2.ZERO:
|
||||
func get_rects(image: Image) -> RectData:
|
||||
# Make a smaller image to make the loop shorter
|
||||
var used_rect := image.get_used_rect()
|
||||
if used_rect.size == Vector2i.ZERO:
|
||||
return clean_rects([])
|
||||
var test_image = image.get_rect(used_rect)
|
||||
# prepare a bitmap to keep track of previous places
|
||||
var test_image := image.get_region(used_rect)
|
||||
# Prepare a bitmap to keep track of previous places
|
||||
var scanned_area := BitMap.new()
|
||||
scanned_area.create(test_image.get_size())
|
||||
test_image.lock()
|
||||
# Scan the image
|
||||
var rects = []
|
||||
var frame_size = Vector2.ZERO
|
||||
var rects: Array[Rect2i] = []
|
||||
var frame_size := Vector2i.ZERO
|
||||
for y in test_image.get_size().y:
|
||||
for x in test_image.get_size().x:
|
||||
var position = Vector2(x, y)
|
||||
var position := Vector2i(x, y)
|
||||
if test_image.get_pixelv(position).a > 0: # used portion of image detected
|
||||
if !scanned_area.get_bit(position):
|
||||
if !scanned_area.get_bitv(position):
|
||||
var rect := _estimate_rect(test_image, position)
|
||||
scanned_area.set_bit_rect(rect, true)
|
||||
rect.position += used_rect.position
|
||||
rects.append(rect)
|
||||
test_image.unlock()
|
||||
var rects_info = clean_rects(rects)
|
||||
rects_info["rects"].sort_custom(self, "sort_rects")
|
||||
var rects_info := clean_rects(rects)
|
||||
rects_info.rects.sort_custom(sort_rects)
|
||||
return rects_info
|
||||
|
||||
|
||||
func clean_rects(rects: Array) -> Dictionary:
|
||||
var frame_size = Vector2.ZERO
|
||||
func clean_rects(rects: Array[Rect2i]) -> RectData:
|
||||
var frame_size := Vector2i.ZERO
|
||||
for i in rects.size():
|
||||
var target: Rect2 = rects.pop_front()
|
||||
var test_rect = target
|
||||
var target: Rect2i = rects.pop_front()
|
||||
var test_rect := target
|
||||
if (
|
||||
target.size.x < _include_boundary_threshold
|
||||
or target.size.y < _include_boundary_threshold
|
||||
):
|
||||
test_rect.size += Vector2(_merge_dist, _merge_dist)
|
||||
test_rect.position -= Vector2(_merge_dist, _merge_dist) / 2
|
||||
var merged = false
|
||||
test_rect.size += Vector2i(_merge_dist, _merge_dist)
|
||||
test_rect.position -= Vector2i(_merge_dist, _merge_dist) / 2
|
||||
var merged := false
|
||||
for rect_i in rects.size():
|
||||
if test_rect.intersects(rects[rect_i]):
|
||||
rects[rect_i] = target.merge(rects[rect_i])
|
||||
|
@ -91,61 +109,51 @@ func clean_rects(rects: Array) -> Dictionary:
|
|||
frame_size.x = target.size.x
|
||||
if target.size.y > frame_size.y:
|
||||
frame_size.y = target.size.y
|
||||
return {"rects": rects, "frame_size": frame_size}
|
||||
return RectData.new(rects, frame_size)
|
||||
|
||||
|
||||
func sort_rects(rect_a: Rect2, rect_b: Rect2) -> bool:
|
||||
func sort_rects(rect_a: Rect2i, rect_b: Rect2i) -> bool:
|
||||
# After many failed attempts, this version works for some reason (it's best not to disturb it)
|
||||
if rect_a.end.y < rect_b.position.y:
|
||||
return true
|
||||
if rect_a.position.x < rect_b.position.x:
|
||||
# if both lie in the same row
|
||||
var start = rect_a.position
|
||||
var size = Vector2(rect_b.end.x, rect_a.end.y)
|
||||
if Rect2(start, size).intersects(rect_b):
|
||||
var start := rect_a.position
|
||||
var size := Vector2i(rect_b.end.x, rect_a.end.y)
|
||||
if Rect2i(start, size).intersects(rect_b):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _estimate_rect(image: Image, position: Vector2) -> Rect2:
|
||||
func _estimate_rect(image: Image, position: Vector2) -> Rect2i:
|
||||
var cel_image := Image.new()
|
||||
cel_image.copy_from(image)
|
||||
cel_image.lock()
|
||||
var small_rect: Rect2 = _flood_fill(position, cel_image)
|
||||
cel_image.unlock()
|
||||
var small_rect := _flood_fill(position, cel_image)
|
||||
return small_rect
|
||||
|
||||
|
||||
# Add a new segment to the array
|
||||
func _add_new_segment(y: int = 0) -> void:
|
||||
var segment = {}
|
||||
segment.flooding = false
|
||||
segment.todo_above = false
|
||||
segment.todo_below = false
|
||||
segment.left_position = -5 # anything less than -1 is ok
|
||||
segment.right_position = -5
|
||||
segment.y = y
|
||||
segment.next = 0
|
||||
_allegro_flood_segments.append(segment)
|
||||
## Add a new segment to the array
|
||||
func _add_new_segment(y := 0) -> void:
|
||||
_allegro_flood_segments.append(Segment.new(y))
|
||||
|
||||
|
||||
# fill an horizontal segment around the specified position, and adds it to the
|
||||
# list of segments filled. Returns the first x coordinate after the part of the
|
||||
# line that has been filled.
|
||||
func _flood_line_around_point(position: Vector2, image: Image) -> int:
|
||||
# this method is called by `_flood_fill` after the required data structures
|
||||
# have been initialized
|
||||
## Fill an horizontal segment around the specified position, and adds it to the
|
||||
## list of segments filled. Returns the first x coordinate after the part of the
|
||||
## line that has been filled.
|
||||
## this method is called by `_flood_fill` after the required data structures
|
||||
## have been initialized
|
||||
func _flood_line_around_point(position: Vector2i, image: Image) -> int:
|
||||
if not image.get_pixelv(position).a > 0:
|
||||
return int(position.x) + 1
|
||||
var west: Vector2 = position
|
||||
var east: Vector2 = position
|
||||
return position.x + 1
|
||||
var west := position
|
||||
var east := position
|
||||
while west.x >= 0 && image.get_pixelv(west).a > 0:
|
||||
west += Vector2.LEFT
|
||||
west += Vector2i.LEFT
|
||||
while east.x < image.get_width() && image.get_pixelv(east).a > 0:
|
||||
east += Vector2.RIGHT
|
||||
east += Vector2i.RIGHT
|
||||
# Make a note of the stuff we processed
|
||||
var c = int(position.y)
|
||||
var segment = _allegro_flood_segments[c]
|
||||
var c := position.y
|
||||
var segment := _allegro_flood_segments[c]
|
||||
# we may have already processed some segments on this y coordinate
|
||||
if segment.flooding:
|
||||
while segment.next > 0:
|
||||
|
@ -154,7 +162,7 @@ func _flood_line_around_point(position: Vector2, image: Image) -> int:
|
|||
# found last current segment on this line
|
||||
c = _allegro_flood_segments.size()
|
||||
segment.next = c
|
||||
_add_new_segment(int(position.y))
|
||||
_add_new_segment(position.y)
|
||||
segment = _allegro_flood_segments[c]
|
||||
# set the values for the current segment
|
||||
segment.flooding = true
|
||||
|
@ -177,28 +185,28 @@ func _flood_line_around_point(position: Vector2, image: Image) -> int:
|
|||
_allegro_image_segments.append(segment)
|
||||
# we know the point just east of the segment is not part of a segment that should be
|
||||
# processed, else it would be part of this segment
|
||||
return int(east.x) + 1
|
||||
return east.x + 1
|
||||
|
||||
|
||||
func _check_flooded_segment(y: int, left: int, right: int, image: Image) -> bool:
|
||||
var ret = false
|
||||
var c: int = 0
|
||||
var ret := false
|
||||
var c := 0
|
||||
while left <= right:
|
||||
c = y
|
||||
while true:
|
||||
var segment = _allegro_flood_segments[c]
|
||||
var segment := _allegro_flood_segments[c]
|
||||
if left >= segment.left_position and left <= segment.right_position:
|
||||
left = segment.right_position + 2
|
||||
break
|
||||
c = segment.next
|
||||
if c == 0: # couldn't find a valid segment, so we draw a new one
|
||||
left = _flood_line_around_point(Vector2(left, y), image)
|
||||
left = _flood_line_around_point(Vector2i(left, y), image)
|
||||
ret = true
|
||||
break
|
||||
return ret
|
||||
|
||||
|
||||
func _flood_fill(position: Vector2, image: Image) -> Rect2:
|
||||
func _flood_fill(position: Vector2i, image: Image) -> Rect2i:
|
||||
# implements the floodfill routine by Shawn Hargreaves
|
||||
# from https://www1.udel.edu/CIS/software/dist/allegro-4.2.1/src/flood.c
|
||||
# init flood data structures
|
||||
|
@ -208,17 +216,15 @@ func _flood_fill(position: Vector2, image: Image) -> Rect2:
|
|||
# now actually color the image: since we have already checked a few things for the points
|
||||
# we'll process here, we're going to skip a bunch of safety checks to speed things up.
|
||||
|
||||
var final_image = Image.new()
|
||||
var final_image := Image.new()
|
||||
final_image.copy_from(image)
|
||||
final_image.fill(Color.transparent)
|
||||
final_image.lock()
|
||||
final_image.fill(Color.TRANSPARENT)
|
||||
_select_segments(final_image)
|
||||
final_image.unlock()
|
||||
|
||||
return final_image.get_used_rect()
|
||||
|
||||
|
||||
func _compute_segments_for_image(position: Vector2, image: Image) -> void:
|
||||
func _compute_segments_for_image(position: Vector2i, image: Image) -> void:
|
||||
# initially allocate at least 1 segment per line of image
|
||||
for j in image.get_height():
|
||||
_add_new_segment(j)
|
||||
|
@ -228,9 +234,9 @@ func _compute_segments_for_image(position: Vector2, image: Image) -> void:
|
|||
var done := false
|
||||
while not done:
|
||||
done = true
|
||||
var max_index = _allegro_flood_segments.size()
|
||||
var max_index := _allegro_flood_segments.size()
|
||||
for c in max_index:
|
||||
var p = _allegro_flood_segments[c]
|
||||
var p := _allegro_flood_segments[c]
|
||||
if p.todo_below: # check below the segment?
|
||||
p.todo_below = false
|
||||
if _check_flooded_segment(p.y + 1, p.left_position, p.right_position, image):
|
||||
|
@ -244,8 +250,8 @@ func _compute_segments_for_image(position: Vector2, image: Image) -> void:
|
|||
func _select_segments(map: Image) -> void:
|
||||
# short circuit for flat colors
|
||||
for c in _allegro_image_segments.size():
|
||||
var p = _allegro_image_segments[c]
|
||||
var rect = Rect2()
|
||||
rect.position = Vector2(p.left_position, p.y)
|
||||
rect.end = Vector2(p.right_position + 1, p.y + 1)
|
||||
map.fill_rect(rect, Color.white)
|
||||
var p := _allegro_image_segments[c]
|
||||
var rect := Rect2i()
|
||||
rect.position = Vector2i(p.left_position, p.y)
|
||||
rect.end = Vector2i(p.right_position + 1, p.y + 1)
|
||||
map.fill_rect(rect, Color.WHITE)
|
||||
|
|
|
@ -13,7 +13,7 @@ func export_animation(
|
|||
progress_report_obj: Object,
|
||||
progress_report_method,
|
||||
progress_report_args
|
||||
) -> PoolByteArray:
|
||||
) -> PackedByteArray:
|
||||
var frame_count := len(frames)
|
||||
var result := AImgIOAPNGStream.new()
|
||||
# Magic number
|
||||
|
@ -65,7 +65,7 @@ func export_animation(
|
|||
# setup chunk interior...
|
||||
var ichk := result.start_chunk()
|
||||
write_padded_lines(ichk, image)
|
||||
chunk.put_data(ichk.data_array.compress(File.COMPRESSION_DEFLATE))
|
||||
chunk.put_data(ichk.data_array.compress(FileAccess.COMPRESSION_DEFLATE))
|
||||
# done with chunk interior
|
||||
if i == 0:
|
||||
result.write_chunk("IDAT", chunk.data_array)
|
||||
|
@ -74,7 +74,7 @@ func export_animation(
|
|||
# Done with this frame!
|
||||
progress_report_obj.callv(progress_report_method, progress_report_args)
|
||||
# Final chunk.
|
||||
result.write_chunk("IEND", PoolByteArray())
|
||||
result.write_chunk("IEND", PackedByteArray())
|
||||
return result.finish()
|
||||
|
||||
|
||||
|
@ -88,7 +88,7 @@ func write_delay(sp: StreamPeer, duration: float, fps_hint: float):
|
|||
# Precision is increased so we catch more complex cases.
|
||||
# But you should always get perfection for integers.
|
||||
var den := min(32767, max(fps_hint, 1))
|
||||
var num := max(duration, 0) * den
|
||||
var num: float = max(duration, 0) * den
|
||||
# If the FPS hint brings us out of range before we start, try some obvious integers
|
||||
var fallback := 10000
|
||||
while num > 32767:
|
||||
|
@ -123,7 +123,7 @@ func write_padded_lines(sp: StreamPeer, img: Image):
|
|||
var base := 0
|
||||
while y < h:
|
||||
var nl := base + (w * 4)
|
||||
var line := data.subarray(base, nl - 1)
|
||||
var line := data.slice(base, nl)
|
||||
sp.put_8(0)
|
||||
sp.put_data(line)
|
||||
y += 1
|
||||
|
|
|
@ -1,80 +1,60 @@
|
|||
tool
|
||||
@tool
|
||||
class_name AImgIOAPNGImportPlugin
|
||||
extends EditorImportPlugin
|
||||
|
||||
|
||||
func get_importer_name() -> String:
|
||||
func _get_importer_name() -> String:
|
||||
return "aimgio.apng_animatedtexture"
|
||||
|
||||
|
||||
func get_visible_name() -> String:
|
||||
func _get_visible_name() -> String:
|
||||
return "APNG as AnimatedTexture"
|
||||
|
||||
|
||||
func get_save_extension() -> String:
|
||||
func _get_save_extension() -> String:
|
||||
return "res"
|
||||
|
||||
|
||||
func get_resource_type() -> String:
|
||||
func _get_resource_type() -> String:
|
||||
return "AnimatedTexture"
|
||||
|
||||
|
||||
func get_recognized_extensions() -> Array:
|
||||
return ["png"]
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return PackedStringArray(["png"])
|
||||
|
||||
|
||||
func get_preset_count():
|
||||
func _get_preset_count():
|
||||
return 1
|
||||
|
||||
|
||||
func get_preset_name(_i):
|
||||
func _get_preset_name(_i):
|
||||
return "Default"
|
||||
|
||||
|
||||
func get_import_options(_i):
|
||||
# GDLint workaround - it really does not want this string to exist due to length.
|
||||
var hint = "Mipmaps,Repeat,Filter,Anisotropic Filter,Convert To Linear,Mirrored Repeat"
|
||||
return [
|
||||
{
|
||||
"name": "image_texture_storage",
|
||||
"default_value": 2,
|
||||
"property_hint": PROPERTY_HINT_ENUM_SUGGESTION,
|
||||
"hint_string": "Raw,Lossy,Lossless"
|
||||
},
|
||||
{"name": "image_texture_lossy_quality", "default_value": 0.7},
|
||||
{
|
||||
"name": "texture_flags",
|
||||
"default_value": 7,
|
||||
"property_hint": PROPERTY_HINT_FLAGS,
|
||||
"hint_string": hint
|
||||
},
|
||||
# We don't know if Godot will change things somehow.
|
||||
{"name": "texture_flags_add", "default_value": 0}
|
||||
]
|
||||
func _get_import_options(_path: String, _i: int) -> Array[Dictionary]:
|
||||
return []
|
||||
|
||||
|
||||
func get_option_visibility(_option, _options):
|
||||
func _get_import_order():
|
||||
return 0
|
||||
|
||||
|
||||
func _get_option_visibility(_path: String, _option, _options):
|
||||
return true
|
||||
|
||||
|
||||
func import(load_path: String, save_path: String, options, _platform_variants, _gen_files):
|
||||
func _import(load_path: String, save_path: String, _options, _platform_variants, _gen_files):
|
||||
var res := AImgIOAPNGImporter.load_from_file(load_path)
|
||||
if res[0] != null:
|
||||
push_error("AImgIOPNGImporter: " + res[0])
|
||||
return ERR_FILE_UNRECOGNIZED
|
||||
var frames: Array = res[1]
|
||||
var root: AnimatedTexture = AnimatedTexture.new()
|
||||
var flags: int = options["texture_flags"]
|
||||
flags |= options["texture_flags_add"]
|
||||
root.flags = flags
|
||||
root.frames = len(frames)
|
||||
root.fps = 1
|
||||
for i in range(len(frames)):
|
||||
var f: AImgIOFrame = frames[i]
|
||||
root.set_frame_delay(i, f.duration - 1.0)
|
||||
root.set_frame_duration(i, f.duration)
|
||||
var tx := ImageTexture.new()
|
||||
tx.storage = options["image_texture_storage"]
|
||||
tx.lossy_quality = options["image_texture_lossy_quality"]
|
||||
tx.create_from_image(f.content, flags)
|
||||
tx.create_from_image(f.content)
|
||||
root.set_frame_texture(i, tx)
|
||||
return ResourceSaver.save(save_path + ".res", root)
|
||||
return ResourceSaver.save(root, save_path + ".res")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
tool
|
||||
@tool
|
||||
class_name AImgIOAPNGImporter
|
||||
extends Reference
|
||||
extends RefCounted
|
||||
# Will NOT import regular, unanimated PNGs - use Image.load_png_from_buffer
|
||||
# This is because we don't want to import the default image as a frame
|
||||
# Therefore it just uses the rule:
|
||||
|
@ -10,7 +10,7 @@ extends Reference
|
|||
# Imports an APNG PoolByteArray into an animation as an Array of frames.
|
||||
# Returns [error, frames] similar to some read functions.
|
||||
# However, error is a string.
|
||||
static func load_from_buffer(buffer: PoolByteArray) -> Array:
|
||||
static func load_from_buffer(buffer: PackedByteArray) -> Array:
|
||||
var stream := AImgIOAPNGStream.new(buffer)
|
||||
var magic_str = stream.read_magic()
|
||||
if magic_str != null:
|
||||
|
@ -35,14 +35,14 @@ static func load_from_buffer(buffer: PoolByteArray) -> Array:
|
|||
# So to convert an APNG frame to a PNG for reading, we need to stitch:
|
||||
# IHDR (modified), PLTE (if present), tRNS (if present), IDAT (from fdAT),
|
||||
# and IEND (generated).
|
||||
var ihdr := PoolByteArray()
|
||||
var plte := PoolByteArray()
|
||||
var trns := PoolByteArray()
|
||||
var ihdr := PackedByteArray()
|
||||
var plte := PackedByteArray()
|
||||
var trns := PackedByteArray()
|
||||
# stored full width/height for buffer
|
||||
var width := 0
|
||||
var height := 0
|
||||
# parse chunks
|
||||
var frames := []
|
||||
var frames: Array[BFrame] = []
|
||||
while stream.read_chunk() == OK:
|
||||
if stream.chunk_type == "IHDR":
|
||||
ihdr = stream.chunk_data
|
||||
|
@ -78,17 +78,16 @@ static func load_from_buffer(buffer: PoolByteArray) -> Array:
|
|||
if len(frames) > 0:
|
||||
var f: BFrame = frames[len(frames) - 1]
|
||||
if len(stream.chunk_data) >= 4:
|
||||
var data := stream.chunk_data.subarray(4, len(stream.chunk_data) - 1)
|
||||
var data := stream.chunk_data.slice(4, len(stream.chunk_data))
|
||||
f.add_data(data)
|
||||
# theoretically we *could* store the default frame somewhere, but *why*?
|
||||
# just use Image functions if you want that
|
||||
if len(frames) == 0:
|
||||
return ["No frames", null]
|
||||
# prepare initial operating buffer
|
||||
var operating := Image.new()
|
||||
operating.create(width, height, false, Image.FORMAT_RGBA8)
|
||||
var operating := Image.create(width, height, false, Image.FORMAT_RGBA8)
|
||||
operating.fill(Color(0, 0, 0, 0))
|
||||
var finished := []
|
||||
var finished: Array[AImgIOFrame] = []
|
||||
for v in frames:
|
||||
var fv: BFrame = v
|
||||
# Ok, so to avoid having to deal with filters and stuff,
|
||||
|
@ -106,9 +105,9 @@ static func load_from_buffer(buffer: PoolByteArray) -> Array:
|
|||
var blit_target := operating
|
||||
var copy_blit_target := true
|
||||
# rectangles and such
|
||||
var blit_src := Rect2(Vector2.ZERO, intermediary_img.get_size())
|
||||
var blit_pos := Vector2(fv.x, fv.y)
|
||||
var blit_tgt := Rect2(blit_pos, intermediary_img.get_size())
|
||||
var blit_src := Rect2i(Vector2i.ZERO, intermediary_img.get_size())
|
||||
var blit_pos := Vector2i(fv.x, fv.y)
|
||||
var blit_tgt := Rect2i(blit_pos, intermediary_img.get_size())
|
||||
# early dispose ops
|
||||
if fv.dispose_op == 2:
|
||||
# previous
|
||||
|
@ -118,6 +117,7 @@ static func load_from_buffer(buffer: PoolByteArray) -> Array:
|
|||
blit_target.copy_from(operating)
|
||||
copy_blit_target = false
|
||||
# actually blit
|
||||
if blit_src.size != Vector2i.ZERO:
|
||||
if fv.blend_op == 0:
|
||||
blit_target.blit_rect(intermediary_img, blit_src, blit_pos)
|
||||
else:
|
||||
|
@ -143,10 +143,10 @@ static func load_from_buffer(buffer: PoolByteArray) -> Array:
|
|||
# Imports an APNG file into an animation as an array of frames.
|
||||
# Returns null on error.
|
||||
static func load_from_file(path: String) -> Array:
|
||||
var o := File.new()
|
||||
if o.open(path, File.READ) != OK:
|
||||
var o := FileAccess.open(path, FileAccess.READ)
|
||||
if o == null:
|
||||
return [null, "Unable to open file: " + path]
|
||||
var l = o.get_len()
|
||||
var l = o.get_length()
|
||||
var data = o.get_buffer(l)
|
||||
o.close()
|
||||
return load_from_buffer(data)
|
||||
|
@ -154,7 +154,7 @@ static func load_from_file(path: String) -> Array:
|
|||
|
||||
# Intermediate frame structure
|
||||
class BFrame:
|
||||
extends Reference
|
||||
extends RefCounted
|
||||
var dispose_op: int
|
||||
var blend_op: int
|
||||
var x: int
|
||||
|
@ -162,9 +162,9 @@ class BFrame:
|
|||
var w: int
|
||||
var h: int
|
||||
var duration: float
|
||||
var data: PoolByteArray
|
||||
var data: PackedByteArray
|
||||
|
||||
func setup(fctl: PoolByteArray):
|
||||
func setup(fctl: PackedByteArray):
|
||||
if len(fctl) < 26:
|
||||
return ""
|
||||
var sp := StreamPeerBuffer.new()
|
||||
|
@ -191,8 +191,8 @@ class BFrame:
|
|||
# This can be loaded by Godot directly.
|
||||
# This basically skips most of the APNG decoding process.
|
||||
func intermediary(
|
||||
ihdr: PoolByteArray, plte: PoolByteArray, trns: PoolByteArray
|
||||
) -> PoolByteArray:
|
||||
ihdr: PackedByteArray, plte: PackedByteArray, trns: PackedByteArray
|
||||
) -> PackedByteArray:
|
||||
# Might be important to note this operates on a copy of ihdr (by-value).
|
||||
var sp := StreamPeerBuffer.new()
|
||||
sp.data_array = ihdr
|
||||
|
@ -207,8 +207,8 @@ class BFrame:
|
|||
if len(trns) > 0:
|
||||
intermed.write_chunk("tRNS", trns)
|
||||
intermed.write_chunk("IDAT", data)
|
||||
intermed.write_chunk("IEND", PoolByteArray())
|
||||
intermed.write_chunk("IEND", PackedByteArray())
|
||||
return intermed.finish()
|
||||
|
||||
func add_data(d: PoolByteArray):
|
||||
func add_data(d: PackedByteArray):
|
||||
data.append_array(d)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
tool
|
||||
@tool
|
||||
class_name AImgIOAPNGStream
|
||||
extends Reference
|
||||
extends RefCounted
|
||||
# APNG IO context. To be clear, this is still effectively magic.
|
||||
|
||||
# Quite critical we preload this. Preloading creates static variables.
|
||||
|
@ -8,7 +8,7 @@ extends Reference
|
|||
var crc32: AImgIOCRC32 = preload("apng_crc32.tres")
|
||||
|
||||
var chunk_type: String
|
||||
var chunk_data: PoolByteArray
|
||||
var chunk_data: PackedByteArray
|
||||
|
||||
# The reason this must be a StreamPeerBuffer is simple:
|
||||
# 1. We need to support in-memory IO for HTML5 to really work
|
||||
|
@ -21,7 +21,7 @@ var chunk_data: PoolByteArray
|
|||
var _target: StreamPeerBuffer
|
||||
|
||||
|
||||
func _init(t: PoolByteArray = PoolByteArray()):
|
||||
func _init(t: PackedByteArray = PackedByteArray()):
|
||||
crc32.ensure_ready()
|
||||
_target = StreamPeerBuffer.new()
|
||||
_target.big_endian = true
|
||||
|
@ -80,9 +80,9 @@ func start_chunk() -> StreamPeerBuffer:
|
|||
|
||||
|
||||
# Writes a PNG chunk.
|
||||
func write_chunk(type: String, data: PoolByteArray):
|
||||
func write_chunk(type: String, data: PackedByteArray):
|
||||
_target.put_32(len(data))
|
||||
var at := type.to_ascii()
|
||||
var at := type.to_ascii_buffer()
|
||||
_target.put_data(at)
|
||||
_target.put_data(data)
|
||||
var crc := crc32.update(crc32.mask, at)
|
||||
|
@ -91,5 +91,5 @@ func write_chunk(type: String, data: PoolByteArray):
|
|||
|
||||
|
||||
# Returns the data_array of the stream (to be used when you're done writing the file)
|
||||
func finish() -> PoolByteArray:
|
||||
func finish() -> PackedByteArray:
|
||||
return _target.data_array
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class_name AImgIOBaseExporter
|
||||
extends Reference
|
||||
extends RefCounted
|
||||
# Represents a method for exporting animations.
|
||||
|
||||
var mime_type: String
|
||||
|
@ -17,5 +17,5 @@ func export_animation(
|
|||
_progress_report_obj: Object,
|
||||
_progress_report_method,
|
||||
_progress_report_args
|
||||
) -> PoolByteArray:
|
||||
return PoolByteArray()
|
||||
) -> PackedByteArray:
|
||||
return PackedByteArray()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
tool
|
||||
@tool
|
||||
class_name AImgIOCRC32
|
||||
extends Resource
|
||||
# CRC32 implementation that uses a Resource for better caching
|
||||
|
@ -6,10 +6,10 @@ extends Resource
|
|||
const INIT = 0xFFFFFFFF
|
||||
|
||||
# The reversed polynomial.
|
||||
export var reversed_polynomial: int = 0xEDB88320
|
||||
@export var reversed_polynomial: int = 0xEDB88320
|
||||
|
||||
# The mask (and initialization value).
|
||||
export var mask: int = 0xFFFFFFFF
|
||||
@export var mask: int = 0xFFFFFFFF
|
||||
|
||||
var crc32_table = []
|
||||
var _table_init_mutex: Mutex = Mutex.new()
|
||||
|
@ -38,7 +38,7 @@ func ensure_ready():
|
|||
# Performs the update step of CRC32 over some bytes.
|
||||
# Note that this is not the whole story.
|
||||
# The CRC must be initialized to 0xFFFFFFFF, then updated, then bitwise-inverted.
|
||||
func update(crc: int, data: PoolByteArray) -> int:
|
||||
func update(crc: int, data: PackedByteArray) -> int:
|
||||
var i := 0
|
||||
var l := len(data)
|
||||
while i < l:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
tool
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
var apng_importer
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@tool
|
||||
class_name AImgIOFrame
|
||||
extends Reference
|
||||
extends RefCounted
|
||||
# Represents a variable-timed frame of an animation.
|
||||
# Typically stuffed into an array.
|
||||
|
||||
|
|
|
@ -1,42 +1,69 @@
|
|||
tool
|
||||
@tool
|
||||
class_name DockableContainer
|
||||
extends Container
|
||||
|
||||
const SplitHandle = preload("split_handle.gd")
|
||||
const DockablePanel = preload("dockable_panel.gd")
|
||||
const ReferenceControl = preload("dockable_panel_reference_control.gd")
|
||||
const DragNDropPanel = preload("drag_n_drop_panel.gd")
|
||||
const Layout = preload("layout.gd")
|
||||
const SplitHandle := preload("split_handle.gd")
|
||||
const DockablePanel := preload("dockable_panel.gd")
|
||||
const DragNDropPanel := preload("drag_n_drop_panel.gd")
|
||||
|
||||
# gdlint: ignore=max-line-length
|
||||
export(int, "Left", "Center", "Right") var tab_align = TabContainer.ALIGN_CENTER setget set_tab_align, get_tab_align
|
||||
export(bool) var tabs_visible := true setget set_tabs_visible, get_tabs_visible
|
||||
# gdlint: ignore=max-line-length
|
||||
export(bool) var use_hidden_tabs_for_min_size: bool setget set_use_hidden_tabs_for_min_size, get_use_hidden_tabs_for_min_size
|
||||
export(int) var rearrange_group = 0
|
||||
export(Resource) var layout = Layout.new() setget set_layout, get_layout
|
||||
# If `clone_layout_on_ready` is true, `layout` will be cloned on `_ready`.
|
||||
# This is useful for leaving layout Resources untouched in case you want to
|
||||
# restore layout to its default later.
|
||||
export(bool) var clone_layout_on_ready = true
|
||||
@export var tab_alignment := TabBar.ALIGNMENT_CENTER:
|
||||
get:
|
||||
return _tab_align
|
||||
set(value):
|
||||
_tab_align = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel := _panel_container.get_child(i) as DockablePanel
|
||||
panel.tab_alignment = value
|
||||
@export var use_hidden_tabs_for_min_size := false:
|
||||
get:
|
||||
return _use_hidden_tabs_for_min_size
|
||||
set(value):
|
||||
_use_hidden_tabs_for_min_size = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel := _panel_container.get_child(i) as DockablePanel
|
||||
panel.use_hidden_tabs_for_min_size = value
|
||||
@export var tabs_visible := true:
|
||||
get:
|
||||
return _tabs_visible
|
||||
set(value):
|
||||
_tabs_visible = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel := _panel_container.get_child(i) as DockablePanel
|
||||
panel.tabs_visible = value
|
||||
@export var rearrange_group := 0
|
||||
@export var layout := DockableLayout.new():
|
||||
get:
|
||||
return _layout
|
||||
set(value):
|
||||
set_layout(value)
|
||||
## If `clone_layout_on_ready` is true, `layout` will be cloned checked `_ready`.
|
||||
## This is useful for leaving layout Resources untouched in case you want to
|
||||
## restore layout to its default later.
|
||||
@export var clone_layout_on_ready := true
|
||||
|
||||
var _layout = Layout.new()
|
||||
var _panel_container = Container.new()
|
||||
var _split_container = Container.new()
|
||||
var _drag_n_drop_panel = DragNDropPanel.new()
|
||||
var _layout := DockableLayout.new()
|
||||
var _panel_container := Container.new()
|
||||
var _split_container := Container.new()
|
||||
var _drag_n_drop_panel := DragNDropPanel.new()
|
||||
var _drag_panel: DockablePanel
|
||||
var _tab_align = TabContainer.ALIGN_CENTER
|
||||
var _tabs_visible = true
|
||||
var _use_hidden_tabs_for_min_size = false
|
||||
var _current_panel_index = 0
|
||||
var _current_split_index = 0
|
||||
var _children_names = {}
|
||||
var _layout_dirty = false
|
||||
var _tab_align := TabBar.ALIGNMENT_CENTER
|
||||
var _tabs_visible := true
|
||||
var _use_hidden_tabs_for_min_size := false
|
||||
var _current_panel_index := 0
|
||||
var _current_split_index := 0
|
||||
var _children_names := {}
|
||||
var _layout_dirty := false
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
child_entered_tree.connect(_child_entered_tree)
|
||||
child_exiting_tree.connect(_child_exiting_tree)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_process_input(false)
|
||||
_panel_container.name = "_panel_container"
|
||||
.add_child(_panel_container)
|
||||
add_child(_panel_container)
|
||||
move_child(_panel_container, 0)
|
||||
_split_container.name = "_split_container"
|
||||
_split_container.mouse_filter = MOUSE_FILTER_PASS
|
||||
|
@ -44,13 +71,12 @@ func _ready() -> void:
|
|||
|
||||
_drag_n_drop_panel.name = "_drag_n_drop_panel"
|
||||
_drag_n_drop_panel.mouse_filter = MOUSE_FILTER_PASS
|
||||
_drag_n_drop_panel.set_drag_forwarding(self)
|
||||
_drag_n_drop_panel.visible = false
|
||||
.add_child(_drag_n_drop_panel)
|
||||
add_child(_drag_n_drop_panel)
|
||||
|
||||
if not _layout:
|
||||
set_layout(null)
|
||||
elif clone_layout_on_ready and not Engine.editor_hint:
|
||||
elif clone_layout_on_ready and not Engine.is_editor_hint():
|
||||
set_layout(_layout.clone())
|
||||
|
||||
|
||||
|
@ -61,7 +87,7 @@ func _notification(what: int) -> void:
|
|||
what == NOTIFICATION_DRAG_BEGIN
|
||||
and _can_handle_drag_data(get_viewport().gui_get_drag_data())
|
||||
):
|
||||
_drag_n_drop_panel.set_enabled(true, not _layout.root.empty())
|
||||
_drag_n_drop_panel.set_enabled(true, not _layout.root.is_empty())
|
||||
set_process_input(true)
|
||||
elif what == NOTIFICATION_DRAG_END:
|
||||
_drag_n_drop_panel.set_enabled(false)
|
||||
|
@ -71,10 +97,10 @@ func _notification(what: int) -> void:
|
|||
func _input(event: InputEvent) -> void:
|
||||
assert(get_viewport().gui_is_dragging(), "FIXME: should only be called when dragging")
|
||||
if event is InputEventMouseMotion:
|
||||
var local_position = get_local_mouse_position()
|
||||
var panel
|
||||
var local_position := get_local_mouse_position()
|
||||
var panel: DockablePanel
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var p = _panel_container.get_child(i)
|
||||
var p := _panel_container.get_child(i) as DockablePanel
|
||||
if p.get_rect().has_point(local_position):
|
||||
panel = p
|
||||
break
|
||||
|
@ -84,43 +110,37 @@ func _input(event: InputEvent) -> void:
|
|||
fit_child_in_rect(_drag_n_drop_panel, panel.get_child_rect())
|
||||
|
||||
|
||||
func add_child(node: Node, legible_unique_name: bool = false) -> void:
|
||||
.add_child(node, legible_unique_name)
|
||||
_drag_n_drop_panel.raise()
|
||||
func _child_entered_tree(node: Node) -> void:
|
||||
if node == _panel_container or node == _drag_n_drop_panel:
|
||||
return
|
||||
_drag_n_drop_panel.move_to_front()
|
||||
_track_and_add_node(node)
|
||||
|
||||
|
||||
func add_child_below_node(node: Node, child_node: Node, legible_unique_name: bool = false) -> void:
|
||||
.add_child_below_node(node, child_node, legible_unique_name)
|
||||
_drag_n_drop_panel.raise()
|
||||
_track_and_add_node(child_node)
|
||||
|
||||
|
||||
func remove_child(node: Node) -> void:
|
||||
.remove_child(node)
|
||||
func _child_exiting_tree(node: Node) -> void:
|
||||
if node == _panel_container or node == _drag_n_drop_panel:
|
||||
return
|
||||
_untrack_node(node)
|
||||
|
||||
|
||||
func can_drop_data_fw(_position: Vector2, data, from_control) -> bool:
|
||||
return from_control == _drag_n_drop_panel and _can_handle_drag_data(data)
|
||||
func _can_drop_data(_position: Vector2, data) -> bool:
|
||||
return _can_handle_drag_data(data)
|
||||
|
||||
|
||||
func drop_data_fw(_position: Vector2, data, from_control) -> void:
|
||||
assert(from_control == _drag_n_drop_panel, "FIXME")
|
||||
|
||||
var from_node: TabContainer = get_node(data.from_path)
|
||||
func _drop_data(_position: Vector2, data) -> void:
|
||||
var from_node := get_node(data.from_path) as DockablePanel
|
||||
if from_node == _drag_panel and _drag_panel.get_child_count() == 1:
|
||||
return
|
||||
|
||||
var moved_tab = from_node.get_tab_control(data.tabc_element)
|
||||
if moved_tab is ReferenceControl:
|
||||
var moved_tab := from_node.get_tab_control(data.tabc_element)
|
||||
if moved_tab is DockableReferenceControl:
|
||||
moved_tab = moved_tab.reference_to
|
||||
if not _is_managed_node(moved_tab):
|
||||
moved_tab.get_parent().remove_child(moved_tab)
|
||||
add_child(moved_tab)
|
||||
|
||||
if _drag_panel != null:
|
||||
var margin = _drag_n_drop_panel.get_hover_margin()
|
||||
var margin := _drag_n_drop_panel.get_hover_margin()
|
||||
_layout.split_leaf_with_node(_drag_panel.leaf, moved_tab, margin)
|
||||
|
||||
_layout_dirty = true
|
||||
|
@ -135,66 +155,47 @@ func set_control_as_current_tab(control: Control) -> void:
|
|||
if is_control_hidden(control):
|
||||
push_warning("Trying to focus a hidden control")
|
||||
return
|
||||
var leaf = _layout.get_leaf_for_node(control)
|
||||
var leaf := _layout.get_leaf_for_node(control)
|
||||
if not leaf:
|
||||
return
|
||||
var position_in_leaf = leaf.find_node(control)
|
||||
var position_in_leaf := leaf.find_child(control)
|
||||
if position_in_leaf < 0:
|
||||
return
|
||||
var panel
|
||||
var panel: DockablePanel
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var p = _panel_container.get_child(i)
|
||||
var p := _panel_container.get_child(i) as DockablePanel
|
||||
if p.leaf == leaf:
|
||||
panel = p
|
||||
break
|
||||
if not panel:
|
||||
return
|
||||
panel.current_tab = clamp(position_in_leaf, 0, panel.get_tab_count() - 1)
|
||||
panel.current_tab = clampi(position_in_leaf, 0, panel.get_tab_count() - 1)
|
||||
|
||||
|
||||
func set_layout(value: Layout) -> void:
|
||||
func set_layout(value: DockableLayout) -> void:
|
||||
if value == null:
|
||||
value = Layout.new()
|
||||
value = DockableLayout.new()
|
||||
if value == _layout:
|
||||
return
|
||||
if _layout and _layout.is_connected("changed", self, "queue_sort"):
|
||||
_layout.disconnect("changed", self, "queue_sort")
|
||||
if _layout and _layout.changed.is_connected(queue_sort):
|
||||
_layout.changed.disconnect(queue_sort)
|
||||
_layout = value
|
||||
_layout.connect("changed", self, "queue_sort")
|
||||
_layout.changed.connect(queue_sort)
|
||||
_layout_dirty = true
|
||||
queue_sort()
|
||||
|
||||
|
||||
func get_layout() -> Layout:
|
||||
return _layout
|
||||
|
||||
|
||||
func set_tab_align(value: int) -> void:
|
||||
func set_tab_alignment(value: TabBar.AlignmentMode) -> void:
|
||||
_tab_align = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel = _panel_container.get_child(i)
|
||||
panel.tab_align = value
|
||||
panel.tab_alignment = value
|
||||
|
||||
|
||||
func get_tab_align() -> int:
|
||||
return _tab_align
|
||||
|
||||
|
||||
func set_tabs_visible(value: bool) -> void:
|
||||
_tabs_visible = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
var panel = _panel_container.get_child(i)
|
||||
if panel.get_tab_count() >= 2:
|
||||
panel.tabs_visible = true
|
||||
else:
|
||||
panel.tabs_visible = value
|
||||
queue_sort()
|
||||
|
||||
|
||||
func get_tabs_visible() -> bool:
|
||||
return _tabs_visible
|
||||
|
||||
|
||||
func set_use_hidden_tabs_for_min_size(value: bool) -> void:
|
||||
_use_hidden_tabs_for_min_size = value
|
||||
for i in range(1, _panel_container.get_child_count()):
|
||||
|
@ -206,35 +207,35 @@ func get_use_hidden_tabs_for_min_size() -> bool:
|
|||
return _use_hidden_tabs_for_min_size
|
||||
|
||||
|
||||
func set_control_hidden(child: Control, hidden: bool) -> void:
|
||||
_layout.set_node_hidden(child, hidden)
|
||||
func set_control_hidden(child: Control, is_hidden: bool) -> void:
|
||||
_layout.set_node_hidden(child, is_hidden)
|
||||
|
||||
|
||||
func is_control_hidden(child: Control) -> bool:
|
||||
return _layout.is_node_hidden(child)
|
||||
|
||||
|
||||
func get_tabs() -> Array:
|
||||
var tabs = []
|
||||
func get_tabs() -> Array[Control]:
|
||||
var tabs: Array[Control] = []
|
||||
for i in get_child_count():
|
||||
var child = get_child(i)
|
||||
var child := get_child(i)
|
||||
if _is_managed_node(child):
|
||||
tabs.append(child)
|
||||
return tabs
|
||||
|
||||
|
||||
func get_tab_count() -> int:
|
||||
var count = 0
|
||||
var count := 0
|
||||
for i in get_child_count():
|
||||
var child = get_child(i)
|
||||
var child := get_child(i)
|
||||
if _is_managed_node(child):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _can_handle_drag_data(data):
|
||||
func _can_handle_drag_data(data) -> bool:
|
||||
if data is Dictionary and data.get("type") == "tabc_element":
|
||||
var tabc = get_node_or_null(data.get("from_path"))
|
||||
var tabc := get_node_or_null(data.get("from_path"))
|
||||
return (
|
||||
tabc
|
||||
and tabc.has_method("get_tabs_rearrange_group")
|
||||
|
@ -249,15 +250,15 @@ func _is_managed_node(node: Node) -> bool:
|
|||
and node != _panel_container
|
||||
and node != _drag_n_drop_panel
|
||||
and node is Control
|
||||
and not node.is_set_as_toplevel()
|
||||
and not node.top_level
|
||||
)
|
||||
|
||||
|
||||
func _update_layout_with_children() -> void:
|
||||
var names = PoolStringArray()
|
||||
var names := PackedStringArray()
|
||||
_children_names.clear()
|
||||
for i in range(1, get_child_count() - 1):
|
||||
var c = get_child(i)
|
||||
var c := get_child(i)
|
||||
if _track_node(c):
|
||||
names.append(c.name)
|
||||
_layout.update_nodes(names)
|
||||
|
@ -269,10 +270,10 @@ func _track_node(node: Node) -> bool:
|
|||
return false
|
||||
_children_names[node] = node.name
|
||||
_children_names[node.name] = node
|
||||
if not node.is_connected("renamed", self, "_on_child_renamed"):
|
||||
node.connect("renamed", self, "_on_child_renamed", [node])
|
||||
if not node.is_connected("tree_exiting", self, "_untrack_node"):
|
||||
node.connect("tree_exiting", self, "_untrack_node", [node])
|
||||
if not node.renamed.is_connected(_on_child_renamed):
|
||||
node.renamed.connect(_on_child_renamed.bind(node))
|
||||
if not node.tree_exiting.is_connected(_untrack_node):
|
||||
node.tree_exiting.connect(_untrack_node.bind(node))
|
||||
return true
|
||||
|
||||
|
||||
|
@ -288,31 +289,31 @@ func _track_and_add_node(node: Node) -> void:
|
|||
func _untrack_node(node: Node) -> void:
|
||||
_children_names.erase(node)
|
||||
_children_names.erase(node.name)
|
||||
if node.is_connected("renamed", self, "_on_child_renamed"):
|
||||
node.disconnect("renamed", self, "_on_child_renamed")
|
||||
if node.is_connected("tree_exiting", self, "_untrack_node"):
|
||||
node.disconnect("tree_exiting", self, "_untrack_node")
|
||||
if node.renamed.is_connected(_on_child_renamed):
|
||||
node.renamed.disconnect(_on_child_renamed)
|
||||
if node.tree_exiting.is_connected(_untrack_node):
|
||||
node.tree_exiting.disconnect(_untrack_node)
|
||||
_layout_dirty = true
|
||||
|
||||
|
||||
func _resort() -> void:
|
||||
assert(_panel_container, "FIXME: resorting without _panel_container")
|
||||
if _panel_container.get_position_in_parent() != 0:
|
||||
if _panel_container.get_index() != 0:
|
||||
move_child(_panel_container, 0)
|
||||
if _drag_n_drop_panel.get_position_in_parent() < get_child_count() - 1:
|
||||
_drag_n_drop_panel.raise()
|
||||
if _drag_n_drop_panel.get_index() < get_child_count() - 1:
|
||||
_drag_n_drop_panel.move_to_front()
|
||||
|
||||
if _layout_dirty:
|
||||
_update_layout_with_children()
|
||||
|
||||
var rect = Rect2(Vector2.ZERO, rect_size)
|
||||
var rect := Rect2(Vector2.ZERO, size)
|
||||
fit_child_in_rect(_panel_container, rect)
|
||||
_panel_container.fit_child_in_rect(_split_container, rect)
|
||||
|
||||
_current_panel_index = 1
|
||||
_current_split_index = 0
|
||||
|
||||
var children_list = []
|
||||
var children_list := []
|
||||
_calculate_panel_and_split_list(children_list, _layout.root)
|
||||
_fit_panel_and_split_list_to_rect(children_list, rect)
|
||||
|
||||
|
@ -320,17 +321,17 @@ func _resort() -> void:
|
|||
_untrack_children_after(_split_container, _current_split_index)
|
||||
|
||||
|
||||
# Calculate DockablePanel and SplitHandle minimum sizes, skipping empty
|
||||
# branches.
|
||||
#
|
||||
# Returns a DockablePanel on non-empty leaves, a SplitHandle on non-empty
|
||||
# splits, `null` if the whole branch is empty and no space should be used.
|
||||
#
|
||||
# `result` will be filled with the non-empty nodes in this post-order tree
|
||||
# traversal.
|
||||
func _calculate_panel_and_split_list(result: Array, layout_node: Layout.LayoutNode):
|
||||
if layout_node is Layout.LayoutPanel:
|
||||
var nodes = []
|
||||
## Calculate DockablePanel and SplitHandle minimum sizes, skipping empty
|
||||
## branches.
|
||||
##
|
||||
## Returns a DockablePanel checked non-empty leaves, a SplitHandle checked non-empty
|
||||
## splits, `null` if the whole branch is empty and no space should be used.
|
||||
##
|
||||
## `result` will be filled with the non-empty nodes in this post-order tree
|
||||
## traversal.
|
||||
func _calculate_panel_and_split_list(result: Array, layout_node: DockableLayoutNode):
|
||||
if layout_node is DockableLayoutPanel:
|
||||
var nodes: Array[Control] = []
|
||||
for n in layout_node.names:
|
||||
var node: Control = _children_names.get(n)
|
||||
if node:
|
||||
|
@ -343,21 +344,21 @@ func _calculate_panel_and_split_list(result: Array, layout_node: Layout.LayoutNo
|
|||
node.visible = false
|
||||
else:
|
||||
nodes.append(node)
|
||||
if nodes.empty():
|
||||
if nodes.is_empty():
|
||||
return null
|
||||
else:
|
||||
var panel = _get_panel(_current_panel_index)
|
||||
var panel := _get_panel(_current_panel_index)
|
||||
_current_panel_index += 1
|
||||
panel.track_nodes(nodes, layout_node)
|
||||
result.append(panel)
|
||||
return panel
|
||||
elif layout_node is Layout.LayoutSplit:
|
||||
elif layout_node is DockableLayoutSplit:
|
||||
# by processing `second` before `first`, traversing `result` from back
|
||||
# to front yields a nice pre-order tree traversal
|
||||
var second_result = _calculate_panel_and_split_list(result, layout_node.second)
|
||||
var first_result = _calculate_panel_and_split_list(result, layout_node.first)
|
||||
if first_result and second_result:
|
||||
var split = _get_split(_current_split_index)
|
||||
var split := _get_split(_current_split_index)
|
||||
_current_split_index += 1
|
||||
split.layout_split = layout_node
|
||||
split.first_minimum_size = first_result.get_layout_minimum_size()
|
||||
|
@ -372,58 +373,58 @@ func _calculate_panel_and_split_list(result: Array, layout_node: Layout.LayoutNo
|
|||
push_warning("FIXME: invalid Resource, should be branch or leaf, found %s" % layout_node)
|
||||
|
||||
|
||||
# Traverse list from back to front fitting controls where they belong.
|
||||
#
|
||||
# Be sure to call this with the result from `_calculate_split_minimum_sizes`.
|
||||
## Traverse list from back to front fitting controls where they belong.
|
||||
##
|
||||
## Be sure to call this with the result from `_calculate_split_minimum_sizes`.
|
||||
func _fit_panel_and_split_list_to_rect(panel_and_split_list: Array, rect: Rect2) -> void:
|
||||
var control = panel_and_split_list.pop_back()
|
||||
if control is DockablePanel:
|
||||
_panel_container.fit_child_in_rect(control, rect)
|
||||
elif control is SplitHandle:
|
||||
var split_rects = control.get_split_rects(rect)
|
||||
_split_container.fit_child_in_rect(control, split_rects.self)
|
||||
_fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects.first)
|
||||
_fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects.second)
|
||||
_split_container.fit_child_in_rect(control, split_rects["self"])
|
||||
_fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects["first"])
|
||||
_fit_panel_and_split_list_to_rect(panel_and_split_list, split_rects["second"])
|
||||
|
||||
|
||||
## Get the idx'th DockablePanel, reusing an instanced one if possible
|
||||
func _get_panel(idx: int) -> DockablePanel:
|
||||
"""Get the idx'th DockablePanel, reusing an instanced one if possible"""
|
||||
assert(_panel_container, "FIXME: creating panel without _panel_container")
|
||||
if idx < _panel_container.get_child_count():
|
||||
return _panel_container.get_child(idx)
|
||||
var panel = DockablePanel.new()
|
||||
panel.tab_align = _tab_align
|
||||
var panel := DockablePanel.new()
|
||||
panel.tab_alignment = _tab_align
|
||||
panel.tabs_visible = _tabs_visible
|
||||
panel.use_hidden_tabs_for_min_size = _use_hidden_tabs_for_min_size
|
||||
panel.set_tabs_rearrange_group(max(0, rearrange_group))
|
||||
panel.set_tabs_rearrange_group(maxi(0, rearrange_group))
|
||||
_panel_container.add_child(panel)
|
||||
panel.connect("tab_layout_changed", self, "_on_panel_tab_layout_changed", [panel])
|
||||
panel.tab_layout_changed.connect(_on_panel_tab_layout_changed.bind(panel))
|
||||
return panel
|
||||
|
||||
|
||||
## Get the idx'th SplitHandle, reusing an instanced one if possible
|
||||
func _get_split(idx: int) -> SplitHandle:
|
||||
"""Get the idx'th SplitHandle, reusing an instanced one if possible"""
|
||||
assert(_split_container, "FIXME: creating split without _split_container")
|
||||
if idx < _split_container.get_child_count():
|
||||
return _split_container.get_child(idx)
|
||||
var split = SplitHandle.new()
|
||||
var split := SplitHandle.new()
|
||||
_split_container.add_child(split)
|
||||
return split
|
||||
|
||||
|
||||
static func _untrack_children_after(node, idx: int) -> void:
|
||||
"""Helper for removing and freeing all remaining children from node"""
|
||||
## Helper for removing and freeing all remaining children from node
|
||||
func _untrack_children_after(node: Control, idx: int) -> void:
|
||||
for i in range(idx, node.get_child_count()):
|
||||
var child = node.get_child(idx)
|
||||
var child := node.get_child(idx)
|
||||
node.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
|
||||
## Handler for `DockablePanel.tab_layout_changed`, update its DockableLayoutPanel
|
||||
func _on_panel_tab_layout_changed(tab: int, panel: DockablePanel) -> void:
|
||||
"""Handler for `DockablePanel.tab_layout_changed`, update its LayoutPanel"""
|
||||
_layout_dirty = true
|
||||
var control = panel.get_tab_control(tab)
|
||||
if control is ReferenceControl:
|
||||
var control := panel.get_tab_control(tab)
|
||||
if control is DockableReferenceControl:
|
||||
control = control.reference_to
|
||||
if not _is_managed_node(control):
|
||||
control.get_parent().remove_child(control)
|
||||
|
@ -432,10 +433,10 @@ func _on_panel_tab_layout_changed(tab: int, panel: DockablePanel) -> void:
|
|||
queue_sort()
|
||||
|
||||
|
||||
## Handler for `Node.renamed` signal, updates tracked name for node
|
||||
func _on_child_renamed(child: Node) -> void:
|
||||
"""Handler for `Node.renamed` signal, updates tracked name for node"""
|
||||
var old_name = _children_names.get(child)
|
||||
if not old_name:
|
||||
var old_name: String = _children_names.get(child)
|
||||
if old_name == str(child.name):
|
||||
return
|
||||
_children_names.erase(old_name)
|
||||
_children_names[child] = child.name
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
tool
|
||||
@tool
|
||||
extends TabContainer
|
||||
|
||||
signal tab_layout_changed(tab)
|
||||
|
||||
const ReferenceControl = preload("dockable_panel_reference_control.gd")
|
||||
const Layout = preload("layout.gd")
|
||||
var leaf: DockableLayoutPanel:
|
||||
get:
|
||||
return get_leaf()
|
||||
set(value):
|
||||
set_leaf(value)
|
||||
|
||||
var leaf: Layout.LayoutPanel setget set_leaf, get_leaf
|
||||
|
||||
var _leaf: Layout.LayoutPanel
|
||||
var _leaf: DockableLayoutPanel
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
@ -16,49 +17,51 @@ func _ready() -> void:
|
|||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
connect("tab_selected", self, "_on_tab_selected")
|
||||
connect("tab_changed", self, "_on_tab_changed")
|
||||
active_tab_rearranged.connect(_on_tab_changed)
|
||||
tab_selected.connect(_on_tab_selected)
|
||||
tab_changed.connect(_on_tab_changed)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
disconnect("tab_selected", self, "_on_tab_selected")
|
||||
disconnect("tab_changed", self, "_on_tab_changed")
|
||||
active_tab_rearranged.disconnect(_on_tab_changed)
|
||||
tab_selected.disconnect(_on_tab_selected)
|
||||
tab_changed.disconnect(_on_tab_changed)
|
||||
|
||||
|
||||
func track_nodes(nodes: Array, new_leaf: Layout.LayoutPanel) -> void:
|
||||
func track_nodes(nodes: Array[Control], new_leaf: DockableLayoutPanel) -> void:
|
||||
_leaf = null # avoid using previous leaf in tab_changed signals
|
||||
var min_size = min(nodes.size(), get_child_count())
|
||||
var min_size := mini(nodes.size(), get_child_count())
|
||||
# remove spare children
|
||||
for i in range(min_size, get_child_count()):
|
||||
var child = get_child(min_size)
|
||||
var child := get_child(min_size) as DockableReferenceControl
|
||||
child.reference_to = null
|
||||
remove_child(child)
|
||||
child.queue_free()
|
||||
# add missing children
|
||||
for i in range(min_size, nodes.size()):
|
||||
var ref_control = ReferenceControl.new()
|
||||
var ref_control := DockableReferenceControl.new()
|
||||
add_child(ref_control)
|
||||
assert(nodes.size() == get_child_count(), "FIXME")
|
||||
# setup children
|
||||
for i in nodes.size():
|
||||
var ref_control: ReferenceControl = get_child(i)
|
||||
var ref_control := get_child(i) as DockableReferenceControl
|
||||
ref_control.reference_to = nodes[i]
|
||||
set_tab_title(i, nodes[i].name)
|
||||
set_leaf(new_leaf)
|
||||
|
||||
|
||||
func get_child_rect() -> Rect2:
|
||||
var control = get_current_tab_control()
|
||||
return Rect2(rect_position + control.rect_position, control.rect_size)
|
||||
var control := get_current_tab_control()
|
||||
return Rect2(position + control.position, control.size)
|
||||
|
||||
|
||||
func set_leaf(value: Layout.LayoutPanel) -> void:
|
||||
func set_leaf(value: DockableLayoutPanel) -> void:
|
||||
if get_tab_count() > 0 and value:
|
||||
current_tab = clamp(value.current_tab, 0, get_tab_count() - 1)
|
||||
current_tab = clampi(value.current_tab, 0, get_tab_count() - 1)
|
||||
_leaf = value
|
||||
|
||||
|
||||
func get_leaf() -> Layout.LayoutPanel:
|
||||
func get_leaf() -> DockableLayoutPanel:
|
||||
return _leaf
|
||||
|
||||
|
||||
|
@ -74,10 +77,10 @@ func _on_tab_selected(tab: int) -> void:
|
|||
func _on_tab_changed(tab: int) -> void:
|
||||
if not _leaf:
|
||||
return
|
||||
var control = get_tab_control(tab)
|
||||
var control := get_tab_control(tab)
|
||||
if not control:
|
||||
return
|
||||
var tab_name = control.name
|
||||
var name_index_in_leaf = _leaf.find_name(tab_name)
|
||||
var tab_name := control.name
|
||||
var name_index_in_leaf := _leaf.find_name(tab_name)
|
||||
if name_index_in_leaf != tab: # NOTE: this handles added tabs (index == -1)
|
||||
emit_signal("tab_layout_changed", tab)
|
||||
tab_layout_changed.emit(tab)
|
||||
|
|
|
@ -1,20 +1,38 @@
|
|||
tool
|
||||
@tool
|
||||
class_name DockableReferenceControl
|
||||
extends Container
|
||||
# Control that mimics its own visibility and rect into another Control.
|
||||
## Control that mimics its own visibility and rect into another Control.
|
||||
|
||||
var reference_to: Control setget set_reference_to, get_reference_to
|
||||
var reference_to: Control:
|
||||
get:
|
||||
return _reference_to
|
||||
set(control):
|
||||
if _reference_to != control:
|
||||
if is_instance_valid(_reference_to):
|
||||
_reference_to.renamed.disconnect(_on_reference_to_renamed)
|
||||
_reference_to.minimum_size_changed.disconnect(update_minimum_size)
|
||||
_reference_to = control
|
||||
|
||||
minimum_size_changed.emit()
|
||||
if not is_instance_valid(_reference_to):
|
||||
return
|
||||
_reference_to.renamed.connect(_on_reference_to_renamed)
|
||||
_reference_to.minimum_size_changed.connect(update_minimum_size)
|
||||
_reference_to.visible = visible
|
||||
_reposition_reference()
|
||||
|
||||
var _reference_to: Control = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = MOUSE_FILTER_IGNORE
|
||||
set_notify_transform(true)
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED and _reference_to:
|
||||
_reference_to.visible = visible
|
||||
elif what == NOTIFICATION_SORT_CHILDREN and _reference_to:
|
||||
elif what == NOTIFICATION_TRANSFORM_CHANGED and _reference_to:
|
||||
_reposition_reference()
|
||||
|
||||
|
||||
|
@ -22,27 +40,9 @@ func _get_minimum_size() -> Vector2:
|
|||
return _reference_to.get_combined_minimum_size() if _reference_to else Vector2.ZERO
|
||||
|
||||
|
||||
func set_reference_to(control: Control) -> void:
|
||||
if _reference_to != control:
|
||||
if _reference_to:
|
||||
_reference_to.disconnect("renamed", self, "_on_reference_to_renamed")
|
||||
_reference_to.disconnect("minimum_size_changed", self, "minimum_size_changed")
|
||||
_reference_to = control
|
||||
minimum_size_changed()
|
||||
if not _reference_to:
|
||||
return
|
||||
_reference_to.connect("renamed", self, "_on_reference_to_renamed")
|
||||
_reference_to.connect("minimum_size_changed", self, "minimum_size_changed")
|
||||
_reference_to.visible = visible
|
||||
|
||||
|
||||
func get_reference_to() -> Control:
|
||||
return _reference_to
|
||||
|
||||
|
||||
func _reposition_reference() -> void:
|
||||
_reference_to.rect_global_position = rect_global_position
|
||||
_reference_to.rect_size = rect_size
|
||||
_reference_to.global_position = global_position
|
||||
_reference_to.size = size
|
||||
|
||||
|
||||
func _on_reference_to_renamed() -> void:
|
||||
|
|
|
@ -1,45 +1,48 @@
|
|||
tool
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const DRAW_NOTHING = -1
|
||||
const DRAW_CENTERED = -2
|
||||
enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
|
||||
|
||||
var _draw_margin = DRAW_NOTHING
|
||||
var _should_split = false
|
||||
const DRAW_NOTHING := -1
|
||||
const DRAW_CENTERED := -2
|
||||
const MARGIN_NONE := -1
|
||||
|
||||
var _draw_margin := DRAW_NOTHING
|
||||
var _should_split := false
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_MOUSE_EXIT:
|
||||
_draw_margin = DRAW_NOTHING
|
||||
update()
|
||||
queue_redraw()
|
||||
elif what == NOTIFICATION_MOUSE_ENTER and not _should_split:
|
||||
_draw_margin = DRAW_CENTERED
|
||||
update()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if _should_split and event is InputEventMouseMotion:
|
||||
_draw_margin = _find_hover_margin(event.position)
|
||||
update()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var rect
|
||||
var rect: Rect2
|
||||
if _draw_margin == DRAW_NOTHING:
|
||||
return
|
||||
elif _draw_margin == DRAW_CENTERED:
|
||||
rect = Rect2(Vector2.ZERO, rect_size)
|
||||
rect = Rect2(Vector2.ZERO, size)
|
||||
elif _draw_margin == MARGIN_LEFT:
|
||||
rect = Rect2(0, 0, rect_size.x * 0.5, rect_size.y)
|
||||
rect = Rect2(0, 0, size.x * 0.5, size.y)
|
||||
elif _draw_margin == MARGIN_TOP:
|
||||
rect = Rect2(0, 0, rect_size.x, rect_size.y * 0.5)
|
||||
rect = Rect2(0, 0, size.x, size.y * 0.5)
|
||||
elif _draw_margin == MARGIN_RIGHT:
|
||||
var half_width = rect_size.x * 0.5
|
||||
rect = Rect2(half_width, 0, half_width, rect_size.y)
|
||||
var half_width = size.x * 0.5
|
||||
rect = Rect2(half_width, 0, half_width, size.y)
|
||||
elif _draw_margin == MARGIN_BOTTOM:
|
||||
var half_height = rect_size.y * 0.5
|
||||
rect = Rect2(0, half_height, rect_size.x, half_height)
|
||||
var stylebox = get_stylebox("panel", "TooltipPanel")
|
||||
var half_height = size.y * 0.5
|
||||
rect = Rect2(0, half_height, size.x, half_height)
|
||||
var stylebox := get_theme_stylebox("panel", "TooltipPanel")
|
||||
draw_style_box(stylebox, rect)
|
||||
|
||||
|
||||
|
@ -48,7 +51,7 @@ func set_enabled(enabled: bool, should_split: bool = true) -> void:
|
|||
_should_split = should_split
|
||||
if enabled:
|
||||
_draw_margin = DRAW_NOTHING
|
||||
update()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func get_hover_margin() -> int:
|
||||
|
@ -56,23 +59,23 @@ func get_hover_margin() -> int:
|
|||
|
||||
|
||||
func _find_hover_margin(point: Vector2) -> int:
|
||||
var half_size = rect_size * 0.5
|
||||
var half_size := size * 0.5
|
||||
|
||||
var left = point.distance_squared_to(Vector2(0, half_size.y))
|
||||
var lesser = left
|
||||
var lesser_margin = MARGIN_LEFT
|
||||
var left := point.distance_squared_to(Vector2(0, half_size.y))
|
||||
var lesser := left
|
||||
var lesser_margin := MARGIN_LEFT
|
||||
|
||||
var top = point.distance_squared_to(Vector2(half_size.x, 0))
|
||||
var top := point.distance_squared_to(Vector2(half_size.x, 0))
|
||||
if lesser > top:
|
||||
lesser = top
|
||||
lesser_margin = MARGIN_TOP
|
||||
|
||||
var right = point.distance_squared_to(Vector2(rect_size.x, half_size.y))
|
||||
var right := point.distance_squared_to(Vector2(size.x, half_size.y))
|
||||
if lesser > right:
|
||||
lesser = right
|
||||
lesser_margin = MARGIN_RIGHT
|
||||
|
||||
var bottom = point.distance_squared_to(Vector2(half_size.x, rect_size.y))
|
||||
var bottom := point.distance_squared_to(Vector2(half_size.x, size.y))
|
||||
if lesser > bottom:
|
||||
#lesser = bottom # unused result
|
||||
lesser_margin = MARGIN_BOTTOM
|
||||
|
|
10
addons/dockable_container/icon.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="4.001" y="4.016" width="4.005" height="5.004" rx="1" ry="1"/>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="8.999" y="7.016" width="3.006" height="5.004" rx="1" ry="1"/>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="4.004" y="10.023" width="4.005" height="1.99" rx="1" ry="1"/>
|
||||
<rect style="fill-rule: nonzero; fill: rgb(142, 239, 152);" x="9" y="3.991" width="3.006" height="2.031" rx="1" ry="1"/>
|
||||
</g>
|
||||
<path d="M 13 1 C 14.097 1 15 1.903 15 3 L 15 13 C 15 14.097 14.097 15 13 15 L 3 15 C 1.903 15 1 14.097 1 13 L 1 3 C 1 1.903 1.903 1 3 1 L 13 1 Z M 3 13 L 13 13 L 13 3 L 3 3 L 3 13 Z" fill-rule="nonzero" style="fill-rule: nonzero; fill: rgb(142, 239, 152);"/>
|
||||
</svg>
|
After Width: | Height: | Size: 979 B |
37
addons/dockable_container/icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dy25danh2am23"
|
||||
path="res://.godot/imported/icon.svg-35635e7bbda4487d4b2942da1d987df8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dockable_container/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-35635e7bbda4487d4b2942da1d987df8.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
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
|
@ -1,17 +1,22 @@
|
|||
extends EditorInspectorPlugin
|
||||
|
||||
const DockableContainer = preload("../dockable_container.gd")
|
||||
const LayoutEditorProperty = preload("layout_editor_property.gd")
|
||||
const LayoutEditorProperty := preload("layout_editor_property.gd")
|
||||
|
||||
|
||||
func can_handle(object: Object) -> bool:
|
||||
func _can_handle(object: Object) -> bool:
|
||||
return object is DockableContainer
|
||||
|
||||
|
||||
func parse_property(
|
||||
_object: Object, _type: int, path: String, _hint: int, _hint_text: String, _usage: int
|
||||
func _parse_property(
|
||||
_object: Object,
|
||||
_type: Variant.Type,
|
||||
name: String,
|
||||
_hint: PropertyHint,
|
||||
_hint_text: String,
|
||||
_usage: int,
|
||||
_wide: bool
|
||||
) -> bool:
|
||||
if path == "layout":
|
||||
var editor_property = LayoutEditorProperty.new()
|
||||
if name == "layout":
|
||||
var editor_property := LayoutEditorProperty.new()
|
||||
add_property_editor("layout", editor_property)
|
||||
return false
|
||||
|
|
|
@ -1,71 +1,71 @@
|
|||
extends EditorProperty
|
||||
|
||||
const DockableContainer = preload("../dockable_container.gd")
|
||||
const Layout = preload("../layout.gd")
|
||||
|
||||
var _container = DockableContainer.new()
|
||||
var _hidden_menu_button = MenuButton.new()
|
||||
var _container := DockableContainer.new()
|
||||
var _hidden_menu_button := MenuButton.new()
|
||||
var _hidden_menu_popup: PopupMenu
|
||||
var _hidden_menu_list: PoolStringArray
|
||||
var _hidden_menu_list: PackedStringArray
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
rect_min_size = Vector2(128, 256)
|
||||
custom_minimum_size = Vector2(128, 256)
|
||||
|
||||
_hidden_menu_button.text = "Visible nodes"
|
||||
add_child(_hidden_menu_button)
|
||||
_hidden_menu_popup = _hidden_menu_button.get_popup()
|
||||
_hidden_menu_popup.hide_on_checkable_item_selection = false
|
||||
_hidden_menu_popup.connect("about_to_show", self, "_on_hidden_menu_popup_about_to_show")
|
||||
_hidden_menu_popup.connect("id_pressed", self, "_on_hidden_menu_popup_id_pressed")
|
||||
_hidden_menu_popup.about_to_popup.connect(_on_hidden_menu_popup_about_to_show)
|
||||
_hidden_menu_popup.id_pressed.connect(_on_hidden_menu_popup_id_pressed)
|
||||
|
||||
_container.clone_layout_on_ready = false
|
||||
_container.rect_min_size = rect_min_size
|
||||
_container.custom_minimum_size = custom_minimum_size
|
||||
|
||||
var original_container: DockableContainer = get_edited_object()
|
||||
var value = original_container.get(get_edited_property())
|
||||
_container.set(get_edited_property(), value)
|
||||
var value := _get_layout().clone() # The layout gets reset when selecting it without clone
|
||||
for n in value.get_names():
|
||||
var child = _create_child_control(n)
|
||||
var child := _create_child_control(n)
|
||||
_container.add_child(child)
|
||||
_container.set(get_edited_property(), value)
|
||||
add_child(_container)
|
||||
set_bottom_editor(_container)
|
||||
|
||||
|
||||
func update_property() -> void:
|
||||
var value = _get_layout()
|
||||
func _exit_tree() -> void: # Not sure if this is needed, but just to be sure
|
||||
queue_free()
|
||||
|
||||
|
||||
func _update_property() -> void:
|
||||
var value := _get_layout()
|
||||
_container.set(get_edited_property(), value)
|
||||
|
||||
|
||||
func _get_layout() -> Layout:
|
||||
var original_container: DockableContainer = get_edited_object()
|
||||
func _get_layout() -> DockableLayout:
|
||||
var original_container := get_edited_object() as DockableContainer
|
||||
return original_container.get(get_edited_property())
|
||||
|
||||
|
||||
func _create_child_control(named: String) -> Control:
|
||||
var new_control = Label.new()
|
||||
func _create_child_control(named: String) -> Label:
|
||||
var new_control := Label.new()
|
||||
new_control.name = named
|
||||
new_control.align = Label.ALIGN_CENTER
|
||||
new_control.valign = Label.VALIGN_CENTER
|
||||
new_control.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
new_control.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
new_control.clip_text = true
|
||||
new_control.text = named
|
||||
return new_control
|
||||
|
||||
|
||||
func _on_hidden_menu_popup_about_to_show() -> void:
|
||||
var layout = _get_layout()
|
||||
var layout := _get_layout().clone()
|
||||
_hidden_menu_popup.clear()
|
||||
_hidden_menu_list = layout.get_names()
|
||||
for i in _hidden_menu_list.size():
|
||||
var tab_name = _hidden_menu_list[i]
|
||||
var tab_name := _hidden_menu_list[i]
|
||||
_hidden_menu_popup.add_check_item(tab_name, i)
|
||||
_hidden_menu_popup.set_item_checked(i, not layout.is_tab_hidden(tab_name))
|
||||
|
||||
|
||||
func _on_hidden_menu_popup_id_pressed(id: int) -> void:
|
||||
var layout = _get_layout()
|
||||
var tab_name = _hidden_menu_list[id]
|
||||
var new_hidden = not layout.is_tab_hidden(tab_name)
|
||||
layout.set_tab_hidden(tab_name, new_hidden)
|
||||
var layout := _get_layout().clone()
|
||||
var tab_name := _hidden_menu_list[id]
|
||||
var new_hidden := not layout.is_tab_hidden(tab_name)
|
||||
_get_layout().set_tab_hidden(tab_name, new_hidden)
|
||||
_hidden_menu_popup.set_item_checked(id, not new_hidden)
|
||||
emit_changed(get_edited_property(), layout)
|
||||
emit_changed(get_edited_property(), _get_layout()) # This line may not be needed
|
||||
|
|
|
@ -1,84 +1,80 @@
|
|||
tool
|
||||
@tool
|
||||
class_name DockableLayout
|
||||
extends Resource
|
||||
# Layout Resource definition, holding the root LayoutNode and hidden tabs.
|
||||
#
|
||||
# LayoutSplit are binary trees with nested LayoutSplit subtrees and LayoutPanel
|
||||
# leaves. Both of them inherit from LayoutNode to help with type annotation and
|
||||
# define common funcionality.
|
||||
#
|
||||
# Hidden tabs are marked in the `hidden_tabs` Dictionary by name.
|
||||
## DockableLayout Resource definition, holding the root DockableLayoutNode and hidden tabs.
|
||||
##
|
||||
## DockableLayoutSplit are binary trees with nested DockableLayoutSplit subtrees
|
||||
## and DockableLayoutPanel leaves. Both of them inherit from DockableLayoutNode to help with
|
||||
## type annotation and define common functionality.
|
||||
##
|
||||
## Hidden tabs are marked in the `hidden_tabs` Dictionary by name.
|
||||
|
||||
const LayoutNode = preload("layout_node.gd")
|
||||
const LayoutPanel = preload("layout_panel.gd")
|
||||
const LayoutSplit = preload("layout_split.gd")
|
||||
enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
|
||||
|
||||
export(Resource) var root = LayoutPanel.new() setget set_root, get_root
|
||||
export(Dictionary) var hidden_tabs = {} setget set_hidden_tabs, get_hidden_tabs
|
||||
@export var root: DockableLayoutNode = DockableLayoutPanel.new():
|
||||
get:
|
||||
return _root
|
||||
set(value):
|
||||
set_root(value)
|
||||
@export var hidden_tabs := {}:
|
||||
get:
|
||||
return _hidden_tabs
|
||||
set(value):
|
||||
if value != _hidden_tabs:
|
||||
_hidden_tabs = value
|
||||
changed.emit()
|
||||
|
||||
var _changed_signal_queued = false
|
||||
var _first_leaf: LayoutPanel
|
||||
var _changed_signal_queued := false
|
||||
var _first_leaf: DockableLayoutPanel
|
||||
var _hidden_tabs: Dictionary
|
||||
var _leaf_by_node_name: Dictionary
|
||||
var _root: LayoutNode = LayoutPanel.new()
|
||||
var _root: DockableLayoutNode = DockableLayoutPanel.new()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
resource_name = "Layout"
|
||||
|
||||
|
||||
func set_root(value: LayoutNode, should_emit_changed = true) -> void:
|
||||
func set_root(value: DockableLayoutNode, should_emit_changed := true) -> void:
|
||||
if not value:
|
||||
value = LayoutPanel.new()
|
||||
value = DockableLayoutPanel.new()
|
||||
if _root == value:
|
||||
return
|
||||
if _root and _root.is_connected("changed", self, "_on_root_changed"):
|
||||
_root.disconnect("changed", self, "_on_root_changed")
|
||||
if _root and _root.changed.is_connected(_on_root_changed):
|
||||
_root.changed.disconnect(_on_root_changed)
|
||||
_root = value
|
||||
_root.parent = null
|
||||
_root.connect("changed", self, "_on_root_changed")
|
||||
_root.changed.connect(_on_root_changed)
|
||||
if should_emit_changed:
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func get_root() -> LayoutNode:
|
||||
func get_root() -> DockableLayoutNode:
|
||||
return _root
|
||||
|
||||
|
||||
func set_hidden_tabs(value: Dictionary) -> void:
|
||||
if value != _hidden_tabs:
|
||||
_hidden_tabs = value
|
||||
emit_signal("changed")
|
||||
func clone() -> DockableLayout:
|
||||
return duplicate(true)
|
||||
|
||||
|
||||
func get_hidden_tabs() -> Dictionary:
|
||||
return _hidden_tabs
|
||||
|
||||
|
||||
func clone():
|
||||
var new_layout = get_script().new()
|
||||
new_layout.root = _root.clone()
|
||||
new_layout._hidden_tabs = _hidden_tabs.duplicate()
|
||||
return new_layout
|
||||
|
||||
|
||||
func get_names() -> PoolStringArray:
|
||||
func get_names() -> PackedStringArray:
|
||||
return _root.get_names()
|
||||
|
||||
|
||||
# Add missing nodes on first leaf and remove nodes outside indices from leaves.
|
||||
#
|
||||
# _leaf_by_node_name = {
|
||||
# (string keys) = respective Leaf that holds the node name,
|
||||
# }
|
||||
func update_nodes(names: PoolStringArray) -> void:
|
||||
## Add missing nodes on first leaf and remove nodes outside indices from leaves.
|
||||
##
|
||||
## _leaf_by_node_name = {
|
||||
## (string keys) = respective Leaf that holds the node name,
|
||||
## }
|
||||
func update_nodes(names: PackedStringArray) -> void:
|
||||
_leaf_by_node_name.clear()
|
||||
_first_leaf = null
|
||||
var empty_leaves = []
|
||||
_ensure_names_in_node(_root, names, empty_leaves)
|
||||
var empty_leaves: Array[DockableLayoutPanel] = []
|
||||
_ensure_names_in_node(_root, names, empty_leaves) # Changes _leaf_by_node_name and empty_leaves
|
||||
for l in empty_leaves:
|
||||
_remove_leaf(l)
|
||||
if not _first_leaf:
|
||||
_first_leaf = LayoutPanel.new()
|
||||
_first_leaf = DockableLayoutPanel.new()
|
||||
set_root(_first_leaf)
|
||||
for n in names:
|
||||
if not _leaf_by_node_name.has(n):
|
||||
|
@ -87,12 +83,12 @@ func update_nodes(names: PoolStringArray) -> void:
|
|||
_on_root_changed()
|
||||
|
||||
|
||||
func move_node_to_leaf(node: Node, leaf: LayoutPanel, relative_position: int) -> void:
|
||||
var node_name = node.name
|
||||
var previous_leaf = _leaf_by_node_name.get(node_name)
|
||||
func move_node_to_leaf(node: Node, leaf: DockableLayoutPanel, relative_position: int) -> void:
|
||||
var node_name := node.name
|
||||
var previous_leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name)
|
||||
if previous_leaf:
|
||||
previous_leaf.remove_node(node)
|
||||
if previous_leaf.empty():
|
||||
if previous_leaf.is_empty():
|
||||
_remove_leaf(previous_leaf)
|
||||
|
||||
leaf.insert_node(relative_position, node)
|
||||
|
@ -100,18 +96,18 @@ func move_node_to_leaf(node: Node, leaf: LayoutPanel, relative_position: int) ->
|
|||
_on_root_changed()
|
||||
|
||||
|
||||
func get_leaf_for_node(node: Node) -> LayoutPanel:
|
||||
func get_leaf_for_node(node: Node) -> DockableLayoutPanel:
|
||||
return _leaf_by_node_name.get(node.name)
|
||||
|
||||
|
||||
func split_leaf_with_node(leaf, node: Node, margin: int) -> void:
|
||||
var root_branch = leaf.parent
|
||||
var new_leaf = LayoutPanel.new()
|
||||
var new_branch = LayoutSplit.new()
|
||||
func split_leaf_with_node(leaf: DockableLayoutPanel, node: Node, margin: int) -> void:
|
||||
var root_branch := leaf.parent
|
||||
var new_leaf := DockableLayoutPanel.new()
|
||||
var new_branch := DockableLayoutSplit.new()
|
||||
if margin == MARGIN_LEFT or margin == MARGIN_RIGHT:
|
||||
new_branch.direction = LayoutSplit.Direction.HORIZONTAL
|
||||
new_branch.direction = DockableLayoutSplit.Direction.HORIZONTAL
|
||||
else:
|
||||
new_branch.direction = LayoutSplit.Direction.VERTICAL
|
||||
new_branch.direction = DockableLayoutSplit.Direction.VERTICAL
|
||||
if margin == MARGIN_LEFT or margin == MARGIN_TOP:
|
||||
new_branch.first = new_leaf
|
||||
new_branch.second = leaf
|
||||
|
@ -130,7 +126,7 @@ func split_leaf_with_node(leaf, node: Node, margin: int) -> void:
|
|||
|
||||
|
||||
func add_node(node: Node) -> void:
|
||||
var node_name = node.name
|
||||
var node_name := node.name
|
||||
if _leaf_by_node_name.has(node_name):
|
||||
return
|
||||
_first_leaf.push_name(node_name)
|
||||
|
@ -139,19 +135,19 @@ func add_node(node: Node) -> void:
|
|||
|
||||
|
||||
func remove_node(node: Node) -> void:
|
||||
var node_name = node.name
|
||||
var leaf: LayoutPanel = _leaf_by_node_name.get(node_name)
|
||||
var node_name := node.name
|
||||
var leaf: DockableLayoutPanel = _leaf_by_node_name.get(node_name)
|
||||
if not leaf:
|
||||
return
|
||||
leaf.remove_node(node)
|
||||
_leaf_by_node_name.erase(node_name)
|
||||
if leaf.empty():
|
||||
if leaf.is_empty():
|
||||
_remove_leaf(leaf)
|
||||
_on_root_changed()
|
||||
|
||||
|
||||
func rename_node(previous_name: String, new_name: String) -> void:
|
||||
var leaf = _leaf_by_node_name.get(previous_name)
|
||||
var leaf: DockableLayoutPanel = _leaf_by_node_name.get(previous_name)
|
||||
if not leaf:
|
||||
return
|
||||
leaf.rename_node(previous_name, new_name)
|
||||
|
@ -187,35 +183,35 @@ func _on_root_changed() -> void:
|
|||
return
|
||||
_changed_signal_queued = true
|
||||
set_deferred("_changed_signal_queued", false)
|
||||
call_deferred("emit_signal", "changed")
|
||||
emit_changed.call_deferred()
|
||||
|
||||
|
||||
func _ensure_names_in_node(node: LayoutNode, names: PoolStringArray, empty_leaves: Array) -> void:
|
||||
if node is LayoutPanel:
|
||||
node.update_nodes(names, _leaf_by_node_name)
|
||||
if node.empty():
|
||||
func _ensure_names_in_node(
|
||||
node: DockableLayoutNode, names: PackedStringArray, empty_leaves: Array[DockableLayoutPanel]
|
||||
) -> void:
|
||||
if node is DockableLayoutPanel:
|
||||
node.update_nodes(names, _leaf_by_node_name) # This changes _leaf_by_node_name
|
||||
if node.is_empty():
|
||||
empty_leaves.append(node)
|
||||
if not _first_leaf:
|
||||
_first_leaf = node
|
||||
elif node is LayoutSplit:
|
||||
elif node is DockableLayoutSplit:
|
||||
_ensure_names_in_node(node.first, names, empty_leaves)
|
||||
_ensure_names_in_node(node.second, names, empty_leaves)
|
||||
else:
|
||||
assert(false, "Invalid Resource, should be branch or leaf, found %s" % node)
|
||||
|
||||
|
||||
func _remove_leaf(leaf: LayoutPanel) -> void:
|
||||
assert(leaf.empty(), "FIXME: trying to remove a leaf with nodes")
|
||||
func _remove_leaf(leaf: DockableLayoutPanel) -> void:
|
||||
assert(leaf.is_empty(), "FIXME: trying to remove_at a leaf with nodes")
|
||||
if _root == leaf:
|
||||
return
|
||||
var collapsed_branch = leaf.parent
|
||||
assert(collapsed_branch is LayoutSplit, "FIXME: leaf is not a child of branch")
|
||||
var kept_branch = (
|
||||
collapsed_branch.first
|
||||
if leaf == collapsed_branch.second
|
||||
else collapsed_branch.second
|
||||
var collapsed_branch := leaf.parent
|
||||
assert(collapsed_branch is DockableLayoutSplit, "FIXME: leaf is not a child of branch")
|
||||
var kept_branch: DockableLayoutNode = (
|
||||
collapsed_branch.first if leaf == collapsed_branch.second else collapsed_branch.second
|
||||
)
|
||||
var root_branch = collapsed_branch.parent
|
||||
var root_branch := collapsed_branch.parent #HERE
|
||||
if collapsed_branch == _root:
|
||||
set_root(kept_branch, true)
|
||||
elif root_branch:
|
||||
|
@ -231,10 +227,10 @@ func _print_tree() -> void:
|
|||
print("")
|
||||
|
||||
|
||||
func _print_tree_step(tree_or_leaf, level, idx) -> void:
|
||||
if tree_or_leaf is LayoutPanel:
|
||||
func _print_tree_step(tree_or_leaf: DockableLayoutNode, level: int, idx: int) -> void:
|
||||
if tree_or_leaf is DockableLayoutPanel:
|
||||
print(" |".repeat(level), "- (%d) = " % idx, tree_or_leaf.names)
|
||||
else:
|
||||
elif tree_or_leaf is DockableLayoutSplit:
|
||||
print(
|
||||
" |".repeat(level),
|
||||
"-+ (%d) = " % idx,
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
tool
|
||||
@tool
|
||||
class_name DockableLayoutNode
|
||||
extends Resource
|
||||
# Base class for Layout tree nodes
|
||||
## Base class for DockableLayout tree nodes
|
||||
|
||||
var parent = null
|
||||
var parent: DockableLayoutSplit = null
|
||||
|
||||
|
||||
func emit_tree_changed() -> void:
|
||||
var node = self
|
||||
var node := self
|
||||
while node:
|
||||
node.emit_signal("changed")
|
||||
node.emit_changed()
|
||||
node = node.parent
|
||||
|
||||
|
||||
# Returns a deep copy of the layout.
|
||||
#
|
||||
# Use this instead of `Resource.duplicate(true)` to ensure objects have the
|
||||
# right script and parenting is correctly set for each node.
|
||||
func clone():
|
||||
assert(false, "FIXME: implement on child")
|
||||
|
||||
|
||||
# Returns whether there are any nodes
|
||||
func empty() -> bool:
|
||||
## Returns whether there are any nodes
|
||||
func is_empty() -> bool:
|
||||
return true
|
||||
|
||||
|
||||
# Returns all tab names in this node
|
||||
func get_names() -> PoolStringArray:
|
||||
return PoolStringArray()
|
||||
## Returns all tab names in this node
|
||||
func get_names() -> PackedStringArray:
|
||||
return PackedStringArray()
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
tool
|
||||
extends "layout_node.gd"
|
||||
# Layout leaf nodes, defining tabs
|
||||
@tool
|
||||
class_name DockableLayoutPanel
|
||||
extends DockableLayoutNode
|
||||
## DockableLayout leaf nodes, defining tabs
|
||||
|
||||
export(PoolStringArray) var names: PoolStringArray setget set_names, get_names
|
||||
export(int) var current_tab: int setget set_current_tab, get_current_tab
|
||||
@export var names: PackedStringArray:
|
||||
get:
|
||||
return get_names()
|
||||
set(value):
|
||||
_names = value
|
||||
emit_tree_changed()
|
||||
@export var current_tab: int:
|
||||
get:
|
||||
return int(clamp(_current_tab, 0, _names.size() - 1))
|
||||
set(value):
|
||||
if value != _current_tab:
|
||||
_current_tab = value
|
||||
emit_tree_changed()
|
||||
|
||||
var _names := PoolStringArray()
|
||||
var _names := PackedStringArray()
|
||||
var _current_tab := 0
|
||||
|
||||
|
||||
|
@ -13,29 +25,8 @@ func _init() -> void:
|
|||
resource_name = "Tabs"
|
||||
|
||||
|
||||
func clone():
|
||||
var new_panel = get_script().new()
|
||||
new_panel._names = _names
|
||||
new_panel._current_tab = _current_tab
|
||||
return new_panel
|
||||
|
||||
|
||||
func set_current_tab(value: int) -> void:
|
||||
if value != _current_tab:
|
||||
_current_tab = value
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_current_tab() -> int:
|
||||
return int(clamp(_current_tab, 0, _names.size() - 1))
|
||||
|
||||
|
||||
func set_names(value: PoolStringArray) -> void:
|
||||
_names = value
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_names() -> PoolStringArray:
|
||||
## Returns all tab names in this node
|
||||
func get_names() -> PackedStringArray:
|
||||
return _names
|
||||
|
||||
|
||||
|
@ -56,21 +47,21 @@ func find_name(node_name: String) -> int:
|
|||
return -1
|
||||
|
||||
|
||||
func find_node(node: Node):
|
||||
func find_child(node: Node) -> int:
|
||||
return find_name(node.name)
|
||||
|
||||
|
||||
func remove_node(node: Node) -> void:
|
||||
var i = find_node(node)
|
||||
var i := find_child(node)
|
||||
if i >= 0:
|
||||
_names.remove(i)
|
||||
_names.remove_at(i)
|
||||
emit_tree_changed()
|
||||
else:
|
||||
push_warning("Remove failed, node '%s' was not found" % node)
|
||||
|
||||
|
||||
func rename_node(previous_name: String, new_name: String) -> void:
|
||||
var i = find_name(previous_name)
|
||||
var i := find_name(previous_name)
|
||||
if i >= 0:
|
||||
_names.set(i, new_name)
|
||||
emit_tree_changed()
|
||||
|
@ -78,17 +69,18 @@ func rename_node(previous_name: String, new_name: String) -> void:
|
|||
push_warning("Rename failed, name '%s' was not found" % previous_name)
|
||||
|
||||
|
||||
func empty() -> bool:
|
||||
return _names.empty()
|
||||
## Returns whether there are any nodes
|
||||
func is_empty() -> bool:
|
||||
return _names.is_empty()
|
||||
|
||||
|
||||
func update_nodes(node_names: PoolStringArray, data: Dictionary):
|
||||
var i = 0
|
||||
var removed_any = false
|
||||
func update_nodes(node_names: PackedStringArray, data: Dictionary) -> void:
|
||||
var i := 0
|
||||
var removed_any := false
|
||||
while i < _names.size():
|
||||
var current = _names[i]
|
||||
var current := _names[i]
|
||||
if not current in node_names or data.has(current):
|
||||
_names.remove(i)
|
||||
_names.remove_at(i)
|
||||
removed_any = true
|
||||
else:
|
||||
data[current] = self
|
||||
|
|
|
@ -1,76 +1,77 @@
|
|||
tool
|
||||
extends "layout_node.gd"
|
||||
# Layout binary tree nodes, defining subtrees and leaf panels
|
||||
@tool
|
||||
class_name DockableLayoutSplit
|
||||
extends DockableLayoutNode
|
||||
## DockableLayout binary tree nodes, defining subtrees and leaf panels
|
||||
|
||||
enum Direction {
|
||||
HORIZONTAL,
|
||||
VERTICAL,
|
||||
}
|
||||
enum Direction { HORIZONTAL, VERTICAL }
|
||||
|
||||
const LayoutPanel = preload("layout_panel.gd")
|
||||
@export var direction := Direction.HORIZONTAL:
|
||||
get:
|
||||
return get_direction()
|
||||
set(value):
|
||||
set_direction(value)
|
||||
@export_range(0, 1) var percent := 0.5:
|
||||
get = get_percent,
|
||||
set = set_percent
|
||||
@export var first: DockableLayoutNode = DockableLayoutPanel.new():
|
||||
get:
|
||||
return get_first()
|
||||
set(value):
|
||||
set_first(value)
|
||||
@export var second: DockableLayoutNode = DockableLayoutPanel.new():
|
||||
get:
|
||||
return get_second()
|
||||
set(value):
|
||||
set_second(value)
|
||||
|
||||
export(Direction) var direction = Direction.HORIZONTAL setget set_direction, get_direction
|
||||
export(float, 0, 1) var percent = 0.5 setget set_percent, get_percent
|
||||
export(Resource) var first = LayoutPanel.new() setget set_first, get_first
|
||||
export(Resource) var second = LayoutPanel.new() setget set_second, get_second
|
||||
|
||||
var _direction = Direction.HORIZONTAL
|
||||
var _percent = 0.5
|
||||
var _first
|
||||
var _second
|
||||
var _direction := Direction.HORIZONTAL
|
||||
var _percent := 0.5
|
||||
var _first: DockableLayoutNode
|
||||
var _second: DockableLayoutNode
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
resource_name = "Split"
|
||||
|
||||
|
||||
func clone():
|
||||
var new_split = get_script().new()
|
||||
new_split._direction = _direction
|
||||
new_split._percent = _percent
|
||||
new_split.first = _first.clone()
|
||||
new_split.second = _second.clone()
|
||||
return new_split
|
||||
|
||||
|
||||
func set_first(value) -> void:
|
||||
func set_first(value: DockableLayoutNode) -> void:
|
||||
if value == null:
|
||||
_first = LayoutPanel.new()
|
||||
_first = DockableLayoutPanel.new()
|
||||
else:
|
||||
_first = value
|
||||
_first.parent = self
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_first():
|
||||
func get_first() -> DockableLayoutNode:
|
||||
return _first
|
||||
|
||||
|
||||
func set_second(value) -> void:
|
||||
func set_second(value: DockableLayoutNode) -> void:
|
||||
if value == null:
|
||||
_second = LayoutPanel.new()
|
||||
_second = DockableLayoutPanel.new()
|
||||
else:
|
||||
_second = value
|
||||
_second.parent = self
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_second():
|
||||
func get_second() -> DockableLayoutNode:
|
||||
return _second
|
||||
|
||||
|
||||
func set_direction(value: int) -> void:
|
||||
func set_direction(value: Direction) -> void:
|
||||
if value != _direction:
|
||||
_direction = value
|
||||
emit_tree_changed()
|
||||
|
||||
|
||||
func get_direction() -> int:
|
||||
func get_direction() -> Direction:
|
||||
return _direction
|
||||
|
||||
|
||||
func set_percent(value: float) -> void:
|
||||
var clamped_value = clamp(value, 0, 1)
|
||||
var clamped_value := clampf(value, 0, 1)
|
||||
if not is_equal_approx(_percent, clamped_value):
|
||||
_percent = clamped_value
|
||||
emit_tree_changed()
|
||||
|
@ -80,14 +81,15 @@ func get_percent() -> float:
|
|||
return _percent
|
||||
|
||||
|
||||
func get_names() -> PoolStringArray:
|
||||
var names = _first.get_names()
|
||||
func get_names() -> PackedStringArray:
|
||||
var names := _first.get_names()
|
||||
names.append_array(_second.get_names())
|
||||
return names
|
||||
|
||||
|
||||
func empty() -> bool:
|
||||
return _first.empty() and _second.empty()
|
||||
## Returns whether there are any nodes
|
||||
func is_empty() -> bool:
|
||||
return _first.is_empty() and _second.is_empty()
|
||||
|
||||
|
||||
func is_horizontal() -> bool:
|
||||
|
|
|
@ -9,5 +9,5 @@ Layout information is stored in Resource objects, so they can be saved/loaded fr
|
|||
|
||||
This plugin also offers a replica of the Container layout to be edited directly in the inspector."
|
||||
author="gilzoide"
|
||||
version="1.1.1"
|
||||
version="1.1.2"
|
||||
script="plugin.gd"
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
tool
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const DockableContainer = preload("dockable_container.gd")
|
||||
const LayoutInspectorPlugin = preload("inspector_plugin/editor_inspector_plugin.gd")
|
||||
const LayoutInspectorPlugin := preload("inspector_plugin/editor_inspector_plugin.gd")
|
||||
const Icon := preload("icon.svg")
|
||||
|
||||
var _layout_inspector_plugin
|
||||
var _layout_inspector_plugin: LayoutInspectorPlugin
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_layout_inspector_plugin = LayoutInspectorPlugin.new()
|
||||
add_custom_type("DockableContainer", "Container", DockableContainer, null)
|
||||
add_custom_type("DockableContainer", "Container", DockableContainer, Icon)
|
||||
add_inspector_plugin(_layout_inspector_plugin)
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
extends VBoxContainer
|
||||
|
||||
const SAVED_LAYOUT_PATH = "user://layout.tres"
|
||||
const SAVED_LAYOUT_PATH := "user://layout.tres"
|
||||
|
||||
onready var _container = $DockableContainers/DockableContainer
|
||||
onready var _clone_control = $HBoxContainer/ControlPrefab
|
||||
onready var _checkbox_container = $HBoxContainer
|
||||
@onready var _container := $DockableContainers/DockableContainer as DockableContainer
|
||||
@onready var _clone_control := $HBoxContainer/ControlPrefab as ColorRect
|
||||
@onready var _checkbox_container := $HBoxContainer as HBoxContainer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
@ -12,33 +12,33 @@ func _ready() -> void:
|
|||
$HBoxContainer/SaveLayoutButton.visible = false
|
||||
$HBoxContainer/LoadLayoutButton.visible = false
|
||||
|
||||
var tabs = _container.get_tabs()
|
||||
var tabs := _container.get_tabs()
|
||||
for i in tabs.size():
|
||||
var checkbox = CheckBox.new()
|
||||
var checkbox := CheckBox.new()
|
||||
checkbox.text = str(i)
|
||||
checkbox.pressed = not _container.is_control_hidden(tabs[i])
|
||||
checkbox.connect("toggled", self, "_on_CheckButton_toggled", [tabs[i]])
|
||||
checkbox.button_pressed = not _container.is_control_hidden(tabs[i])
|
||||
checkbox.toggled.connect(_on_CheckButton_toggled.bind(tabs[i]))
|
||||
_checkbox_container.add_child(checkbox)
|
||||
|
||||
|
||||
func _on_add_pressed() -> void:
|
||||
var control = _clone_control.duplicate()
|
||||
control.get_node("Buttons/Rename").connect(
|
||||
"pressed", self, "_on_control_rename_button_pressed", [control]
|
||||
var control := _clone_control.duplicate()
|
||||
control.get_node("Buttons/Rename").pressed.connect(
|
||||
_on_control_rename_button_pressed.bind(control)
|
||||
)
|
||||
control.get_node("Buttons/Remove").connect(
|
||||
"pressed", self, "_on_control_remove_button_pressed", [control]
|
||||
control.get_node("Buttons/Remove").pressed.connect(
|
||||
_on_control_remove_button_pressed.bind(control)
|
||||
)
|
||||
control.color = Color(randf(), randf(), randf())
|
||||
control.name = "Control0"
|
||||
|
||||
_container.add_child(control, true)
|
||||
yield(_container, "sort_children")
|
||||
await _container.sort_children
|
||||
_container.set_control_as_current_tab(control)
|
||||
|
||||
|
||||
func _on_save_pressed() -> void:
|
||||
if ResourceSaver.save(SAVED_LAYOUT_PATH, _container.get_layout()) != OK:
|
||||
if ResourceSaver.save(_container.layout, SAVED_LAYOUT_PATH) != OK:
|
||||
print("ERROR")
|
||||
|
||||
|
||||
|
@ -51,11 +51,11 @@ func _on_load_pressed() -> void:
|
|||
|
||||
|
||||
func _on_control_rename_button_pressed(control: Control) -> void:
|
||||
control.name += " =D"
|
||||
control.name = StringName(str(control.name) + " =D")
|
||||
|
||||
|
||||
func _on_control_remove_button_pressed(control: Control) -> void:
|
||||
_container.remove_child(control)
|
||||
control.get_parent().remove_child(control)
|
||||
control.queue_free()
|
||||
|
||||
|
||||
|
|
|
@ -1,234 +1,175 @@
|
|||
[gd_scene load_steps=16 format=2]
|
||||
[gd_scene load_steps=16 format=3 uid="uid://drlvhuchtk6if"]
|
||||
|
||||
[ext_resource path="res://addons/dockable_container/dockable_container.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/dockable_container/layout.gd" type="Script" id=2]
|
||||
[ext_resource path="res://addons/dockable_container/layout_split.gd" type="Script" id=3]
|
||||
[ext_resource path="res://addons/dockable_container/samples/TestScene.gd" type="Script" id=4]
|
||||
[ext_resource path="res://addons/dockable_container/layout_panel.gd" type="Script" id=5]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/dockable_container.gd" id="1"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout.gd" id="2"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/samples/TestScene.gd" id="4"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout_split.gd" id="4_yhgfb"]
|
||||
[ext_resource type="Script" path="res://addons/dockable_container/layout_panel.gd" id="5"]
|
||||
|
||||
[sub_resource type="Resource" id=5]
|
||||
[sub_resource type="Resource" id="Resource_8aoc2"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource( 5 )
|
||||
names = PoolStringArray( "Control0" )
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control0")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id=1]
|
||||
[sub_resource type="Resource" id="Resource_6kjom"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource( 5 )
|
||||
names = PoolStringArray( "Control1", "Control2" )
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control1", "Control2")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id=6]
|
||||
[sub_resource type="Resource" id="Resource_hl8y1"]
|
||||
resource_name = "Split"
|
||||
script = ExtResource( 3 )
|
||||
script = ExtResource("4_yhgfb")
|
||||
direction = 1
|
||||
percent = 0.5
|
||||
first = SubResource( 5 )
|
||||
second = SubResource( 1 )
|
||||
first = SubResource("Resource_8aoc2")
|
||||
second = SubResource("Resource_6kjom")
|
||||
|
||||
[sub_resource type="Resource" id=2]
|
||||
[sub_resource type="Resource" id="Resource_ybwqe"]
|
||||
resource_name = "Layout"
|
||||
script = ExtResource( 2 )
|
||||
root = SubResource( 6 )
|
||||
hidden_tabs = {
|
||||
}
|
||||
script = ExtResource("2")
|
||||
root = SubResource("Resource_hl8y1")
|
||||
hidden_tabs = {}
|
||||
|
||||
[sub_resource type="Resource" id=3]
|
||||
[sub_resource type="Resource" id="Resource_ntwfj"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource( 5 )
|
||||
names = PoolStringArray( "Control3" )
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control3")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id=7]
|
||||
[sub_resource type="Resource" id="Resource_dmyvf"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource( 5 )
|
||||
names = PoolStringArray( "Control4" )
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control4")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id=8]
|
||||
[sub_resource type="Resource" id="Resource_vag66"]
|
||||
resource_name = "Split"
|
||||
script = ExtResource( 3 )
|
||||
script = ExtResource("4_yhgfb")
|
||||
direction = 1
|
||||
percent = 0.28125
|
||||
first = SubResource( 3 )
|
||||
second = SubResource( 7 )
|
||||
percent = 0.281
|
||||
first = SubResource("Resource_ntwfj")
|
||||
second = SubResource("Resource_dmyvf")
|
||||
|
||||
[sub_resource type="Resource" id=9]
|
||||
[sub_resource type="Resource" id="Resource_4q660"]
|
||||
resource_name = "Tabs"
|
||||
script = ExtResource( 5 )
|
||||
names = PoolStringArray( "Control5" )
|
||||
script = ExtResource("5")
|
||||
names = PackedStringArray("Control5")
|
||||
current_tab = 0
|
||||
|
||||
[sub_resource type="Resource" id=10]
|
||||
[sub_resource type="Resource" id="Resource_jhibs"]
|
||||
resource_name = "Split"
|
||||
script = ExtResource( 3 )
|
||||
script = ExtResource("4_yhgfb")
|
||||
direction = 0
|
||||
percent = 0.5
|
||||
first = SubResource( 8 )
|
||||
second = SubResource( 9 )
|
||||
first = SubResource("Resource_vag66")
|
||||
second = SubResource("Resource_4q660")
|
||||
|
||||
[sub_resource type="Resource" id=4]
|
||||
[sub_resource type="Resource" id="Resource_xhxpg"]
|
||||
resource_name = "Layout"
|
||||
script = ExtResource( 2 )
|
||||
root = SubResource( 10 )
|
||||
hidden_tabs = {
|
||||
}
|
||||
script = ExtResource("2")
|
||||
root = SubResource("Resource_jhibs")
|
||||
hidden_tabs = {}
|
||||
|
||||
[node name="SampleScene" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource( 4 )
|
||||
script = ExtResource("4")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
margin_right = 1024.0
|
||||
margin_bottom = 20.0
|
||||
custom_constants/separation = 16
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="AddControlButton" type="Button" parent="HBoxContainer"]
|
||||
margin_left = 345.0
|
||||
margin_right = 472.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
text = "(+) ADD CONTROL"
|
||||
|
||||
[node name="SaveLayoutButton" type="Button" parent="HBoxContainer"]
|
||||
margin_left = 488.0
|
||||
margin_right = 575.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
text = "Save Layout"
|
||||
|
||||
[node name="LoadLayoutButton" type="Button" parent="HBoxContainer"]
|
||||
margin_left = 591.0
|
||||
margin_right = 679.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
text = "Load Layout"
|
||||
|
||||
[node name="ControlPrefab" type="ColorRect" parent="HBoxContainer"]
|
||||
visible = false
|
||||
margin_left = 677.0
|
||||
margin_right = 697.0
|
||||
margin_bottom = 20.0
|
||||
rect_min_size = Vector2( 20, 20 )
|
||||
layout_mode = 2
|
||||
color = Color(0.129412, 0.121569, 0.121569, 1)
|
||||
|
||||
[node name="Buttons" type="VBoxContainer" parent="HBoxContainer/ControlPrefab"]
|
||||
layout_mode = 0
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
margin_left = -65.5
|
||||
margin_top = -22.0
|
||||
margin_right = 65.5
|
||||
margin_bottom = 22.0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
offset_left = -65.5
|
||||
offset_top = -22.0
|
||||
offset_right = 65.5
|
||||
offset_bottom = 22.0
|
||||
|
||||
[node name="Rename" type="Button" parent="HBoxContainer/ControlPrefab/Buttons"]
|
||||
margin_right = 131.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
text = "Rename"
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Remove" type="Button" parent="HBoxContainer/ControlPrefab/Buttons"]
|
||||
margin_top = 24.0
|
||||
margin_right = 131.0
|
||||
margin_bottom = 44.0
|
||||
layout_mode = 2
|
||||
text = "REMOVE"
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="DockableContainers" type="HBoxContainer" parent="."]
|
||||
margin_top = 24.0
|
||||
margin_right = 1024.0
|
||||
margin_bottom = 600.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="DockableContainer" type="Container" parent="DockableContainers"]
|
||||
margin_right = 483.0
|
||||
margin_bottom = 576.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
layout = SubResource( 2 )
|
||||
script = ExtResource("1")
|
||||
layout = SubResource("Resource_ybwqe")
|
||||
|
||||
[node name="Control0" type="ColorRect" parent="DockableContainers/DockableContainer"]
|
||||
margin_left = 4.0
|
||||
margin_top = 32.0
|
||||
margin_right = 479.0
|
||||
margin_bottom = 278.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Control1" type="ColorRect" parent="DockableContainers/DockableContainer"]
|
||||
margin_left = 4.0
|
||||
margin_top = 326.0
|
||||
margin_right = 479.0
|
||||
margin_bottom = 572.0
|
||||
rect_min_size = Vector2( 128, 128 )
|
||||
layout_mode = 2
|
||||
color = Color(0.141176, 0.0745098, 0.603922, 1)
|
||||
|
||||
[node name="Control2" type="ColorRect" parent="DockableContainers/DockableContainer"]
|
||||
visible = false
|
||||
margin_top = 294.0
|
||||
margin_right = 64.0
|
||||
margin_bottom = 358.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
layout_mode = 2
|
||||
color = Color(0.533333, 0.380392, 0.380392, 1)
|
||||
|
||||
[node name="Separator" type="ColorRect" parent="DockableContainers"]
|
||||
margin_left = 487.0
|
||||
margin_right = 537.0
|
||||
margin_bottom = 576.0
|
||||
rect_min_size = Vector2( 50, 0 )
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="DockableContainer2" type="Container" parent="DockableContainers"]
|
||||
margin_left = 541.0
|
||||
margin_right = 1024.0
|
||||
margin_bottom = 576.0
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
layout = SubResource( 4 )
|
||||
script = ExtResource("1")
|
||||
layout = SubResource("Resource_xhxpg")
|
||||
|
||||
[node name="Control3" type="ColorRect" parent="DockableContainers/DockableContainer2"]
|
||||
margin_left = 4.0
|
||||
margin_top = 32.0
|
||||
margin_right = 231.5
|
||||
margin_bottom = 152.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
layout_mode = 2
|
||||
color = Color(0, 1, 0.905882, 1)
|
||||
|
||||
[node name="Control4" type="ColorRect" parent="DockableContainers/DockableContainer2"]
|
||||
margin_left = 4.0
|
||||
margin_top = 200.0
|
||||
margin_right = 231.5
|
||||
margin_bottom = 572.0
|
||||
rect_min_size = Vector2( 128, 128 )
|
||||
layout_mode = 2
|
||||
color = Color(0, 0.698039, 0.0588235, 1)
|
||||
|
||||
[node name="Control5" type="ColorRect" parent="DockableContainers/DockableContainer2"]
|
||||
margin_left = 251.5
|
||||
margin_top = 32.0
|
||||
margin_right = 479.0
|
||||
margin_bottom = 572.0
|
||||
rect_min_size = Vector2( 64, 64 )
|
||||
layout_mode = 2
|
||||
color = Color(1, 0.937255, 0, 1)
|
||||
|
||||
[connection signal="pressed" from="HBoxContainer/AddControlButton" to="." method="_on_add_pressed"]
|
||||
|
|
|
@ -1,53 +1,49 @@
|
|||
tool
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
const Layout = preload("layout.gd")
|
||||
|
||||
const SPLIT_THEME_CLASS = [
|
||||
"HSplitContainer", # SPLIT_THEME_CLASS[LayoutSplit.Direction.HORIZONTAL]
|
||||
"VSplitContainer", # SPLIT_THEME_CLASS[LayoutSplit.Direction.VERTICAL]
|
||||
const SPLIT_THEME_CLASS: PackedStringArray = [
|
||||
"HSplitContainer", # SPLIT_THEME_CLASS[DockableLayoutSplit.Direction.HORIZONTAL]
|
||||
"VSplitContainer", # SPLIT_THEME_CLASS[DockableLayoutSplit.Direction.VERTICAL]
|
||||
]
|
||||
|
||||
const SPLIT_MOUSE_CURSOR_SHAPE = [
|
||||
Control.CURSOR_HSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[LayoutSplit.Direction.HORIZONTAL]
|
||||
Control.CURSOR_VSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[LayoutSplit.Direction.VERTICAL]
|
||||
const SPLIT_MOUSE_CURSOR_SHAPE: Array[Control.CursorShape] = [
|
||||
Control.CURSOR_HSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[DockableLayoutSplit.Direction.HORIZONTAL]
|
||||
Control.CURSOR_VSPLIT, # SPLIT_MOUSE_CURSOR_SHAPE[DockableLayoutSplit.Direction.VERTICAL]
|
||||
]
|
||||
|
||||
var layout_split: Layout.LayoutSplit
|
||||
var layout_split: DockableLayoutSplit
|
||||
var first_minimum_size: Vector2
|
||||
var second_minimum_size: Vector2
|
||||
|
||||
var _parent_rect
|
||||
var _mouse_hovering = false
|
||||
var _dragging = false
|
||||
var _parent_rect: Rect2
|
||||
var _mouse_hovering := false
|
||||
var _dragging := false
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var theme_class = SPLIT_THEME_CLASS[layout_split.direction]
|
||||
var icon = get_icon("grabber", theme_class)
|
||||
var autohide = bool(get_constant("autohide", theme_class))
|
||||
var theme_class := SPLIT_THEME_CLASS[layout_split.direction]
|
||||
var icon := get_theme_icon("grabber", theme_class)
|
||||
var autohide := bool(get_theme_constant("autohide", theme_class))
|
||||
if not icon or (autohide and not _mouse_hovering):
|
||||
return
|
||||
|
||||
draw_texture(icon, (rect_size - icon.get_size()) * 0.5)
|
||||
draw_texture(icon, (size - icon.get_size()) * 0.5)
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == BUTTON_LEFT:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_dragging = event.is_pressed()
|
||||
if event.doubleclick:
|
||||
if event.double_click:
|
||||
layout_split.percent = 0.5
|
||||
elif _dragging and event is InputEventMouseMotion:
|
||||
var mouse_in_parent = get_parent_control().get_local_mouse_position()
|
||||
var mouse_in_parent := get_parent_control().get_local_mouse_position()
|
||||
if layout_split.is_horizontal():
|
||||
layout_split.percent = (
|
||||
(mouse_in_parent.x - _parent_rect.position.x)
|
||||
/ _parent_rect.size.x
|
||||
(mouse_in_parent.x - _parent_rect.position.x) / _parent_rect.size.x
|
||||
)
|
||||
else:
|
||||
layout_split.percent = (
|
||||
(mouse_in_parent.y - _parent_rect.position.y)
|
||||
/ _parent_rect.size.y
|
||||
(mouse_in_parent.y - _parent_rect.position.y) / _parent_rect.size.y
|
||||
)
|
||||
|
||||
|
||||
|
@ -55,13 +51,13 @@ func _notification(what: int) -> void:
|
|||
if what == NOTIFICATION_MOUSE_ENTER:
|
||||
_mouse_hovering = true
|
||||
set_split_cursor(true)
|
||||
if bool(get_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])):
|
||||
update()
|
||||
if bool(get_theme_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])):
|
||||
queue_redraw()
|
||||
elif what == NOTIFICATION_MOUSE_EXIT:
|
||||
_mouse_hovering = false
|
||||
set_split_cursor(false)
|
||||
if bool(get_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])):
|
||||
update()
|
||||
if bool(get_theme_constant("autohide", SPLIT_THEME_CLASS[layout_split.direction])):
|
||||
queue_redraw()
|
||||
elif what == NOTIFICATION_FOCUS_EXIT:
|
||||
_dragging = false
|
||||
|
||||
|
@ -69,15 +65,15 @@ func _notification(what: int) -> void:
|
|||
func get_layout_minimum_size() -> Vector2:
|
||||
if not layout_split:
|
||||
return Vector2.ZERO
|
||||
var separation = get_constant("separation", SPLIT_THEME_CLASS[layout_split.direction])
|
||||
var separation := get_theme_constant("separation", SPLIT_THEME_CLASS[layout_split.direction])
|
||||
if layout_split.is_horizontal():
|
||||
return Vector2(
|
||||
first_minimum_size.x + separation + second_minimum_size.x,
|
||||
max(first_minimum_size.y, second_minimum_size.y)
|
||||
maxf(first_minimum_size.y, second_minimum_size.y)
|
||||
)
|
||||
else:
|
||||
return Vector2(
|
||||
max(first_minimum_size.x, second_minimum_size.x),
|
||||
maxf(first_minimum_size.x, second_minimum_size.x),
|
||||
first_minimum_size.y + separation + second_minimum_size.y
|
||||
)
|
||||
|
||||
|
@ -91,41 +87,34 @@ func set_split_cursor(value: bool) -> void:
|
|||
|
||||
func get_split_rects(rect: Rect2) -> Dictionary:
|
||||
_parent_rect = rect
|
||||
var separation = get_constant("separation", SPLIT_THEME_CLASS[layout_split.direction])
|
||||
var origin = rect.position
|
||||
var size = rect.size
|
||||
var percent = layout_split.percent
|
||||
var separation := get_theme_constant("separation", SPLIT_THEME_CLASS[layout_split.direction])
|
||||
var origin := rect.position
|
||||
var percent := layout_split.percent
|
||||
if layout_split.is_horizontal():
|
||||
var first_width = max((size.x - separation) * percent, first_minimum_size.x)
|
||||
var split_offset = clamp(
|
||||
size.x * percent - separation * 0.5,
|
||||
var split_offset := clampf(
|
||||
rect.size.x * percent - separation * 0.5,
|
||||
first_minimum_size.x,
|
||||
size.x - second_minimum_size.x - separation
|
||||
rect.size.x - second_minimum_size.x - separation
|
||||
)
|
||||
var second_width = size.x - split_offset - separation
|
||||
var second_width := rect.size.x - split_offset - separation
|
||||
|
||||
return {
|
||||
"first": Rect2(origin.x, origin.y, split_offset, size.y),
|
||||
"self": Rect2(origin.x + split_offset, origin.y, separation, size.y),
|
||||
"second": Rect2(origin.x + split_offset + separation, origin.y, second_width, size.y),
|
||||
"first": Rect2(origin.x, origin.y, split_offset, rect.size.y),
|
||||
"self": Rect2(origin.x + split_offset, origin.y, separation, rect.size.y),
|
||||
"second":
|
||||
Rect2(origin.x + split_offset + separation, origin.y, second_width, rect.size.y),
|
||||
}
|
||||
else:
|
||||
var first_height = max((size.y - separation) * percent, first_minimum_size.y)
|
||||
var split_offset = clamp(
|
||||
size.y * percent - separation * 0.5,
|
||||
var split_offset := clampf(
|
||||
rect.size.y * percent - separation * 0.5,
|
||||
first_minimum_size.y,
|
||||
size.y - second_minimum_size.y - separation
|
||||
rect.size.y - second_minimum_size.y - separation
|
||||
)
|
||||
var second_height = size.y - split_offset - separation
|
||||
var second_height := rect.size.y - split_offset - separation
|
||||
|
||||
return {
|
||||
"first": Rect2(origin.x, origin.y, size.x, split_offset),
|
||||
"self": Rect2(origin.x, origin.y + split_offset, size.x, separation),
|
||||
"second": Rect2(origin.x, origin.y + split_offset + separation, size.x, second_height),
|
||||
"first": Rect2(origin.x, origin.y, rect.size.x, split_offset),
|
||||
"self": Rect2(origin.x, origin.y + split_offset, rect.size.x, separation),
|
||||
"second":
|
||||
Rect2(origin.x, origin.y + split_offset + separation, rect.size.x, second_height),
|
||||
}
|
||||
|
||||
|
||||
static func get_separation_with_control(control: Control) -> Vector2:
|
||||
var hseparation = control.get_constant("separation", "HSplitContainer")
|
||||
var vseparation = control.get_constant("separation", "VSplitContainer")
|
||||
return Vector2(hseparation, vseparation)
|
||||
|
|
|
@ -1,59 +1,53 @@
|
|||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
var _shader: Shader
|
||||
|
||||
|
||||
func get_indexed_datas(image: Image, colors: Array) -> PoolByteArray:
|
||||
_shader = preload("./lookup_color.shader")
|
||||
func get_indexed_datas(image: Image, colors: Array) -> PackedByteArray:
|
||||
_shader = preload("./lookup_color.gdshader")
|
||||
return _convert(image, colors)
|
||||
|
||||
|
||||
func get_similar_indexed_datas(image: Image, colors: Array) -> PoolByteArray:
|
||||
_shader = preload("./lookup_similar.shader")
|
||||
func get_similar_indexed_datas(image: Image, colors: Array) -> PackedByteArray:
|
||||
_shader = preload("./lookup_similar.gdshader")
|
||||
return _convert(image, colors)
|
||||
|
||||
|
||||
func _convert(image: Image, colors: Array) -> PoolByteArray:
|
||||
var vp = VisualServer.viewport_create()
|
||||
var canvas = VisualServer.canvas_create()
|
||||
VisualServer.viewport_attach_canvas(vp, canvas)
|
||||
VisualServer.viewport_set_size(vp, image.get_width(), image.get_height())
|
||||
VisualServer.viewport_set_disable_3d(vp, true)
|
||||
VisualServer.viewport_set_usage(vp, VisualServer.VIEWPORT_USAGE_2D)
|
||||
VisualServer.viewport_set_hdr(vp, true)
|
||||
VisualServer.viewport_set_active(vp, true)
|
||||
func _convert(image: Image, colors: Array) -> PackedByteArray:
|
||||
var vp := RenderingServer.viewport_create()
|
||||
var canvas := RenderingServer.canvas_create()
|
||||
RenderingServer.viewport_attach_canvas(vp, canvas)
|
||||
RenderingServer.viewport_set_size(vp, image.get_width(), image.get_height())
|
||||
RenderingServer.viewport_set_disable_3d(vp, true)
|
||||
RenderingServer.viewport_set_active(vp, true)
|
||||
|
||||
var ci_rid = VisualServer.canvas_item_create()
|
||||
VisualServer.viewport_set_canvas_transform(vp, canvas, Transform())
|
||||
VisualServer.canvas_item_set_parent(ci_rid, canvas)
|
||||
var texture = ImageTexture.new()
|
||||
texture.create_from_image(image)
|
||||
VisualServer.canvas_item_add_texture_rect(
|
||||
ci_rid, Rect2(Vector2(0, 0), image.get_size()), texture
|
||||
var ci_rid := RenderingServer.canvas_item_create()
|
||||
RenderingServer.viewport_set_canvas_transform(vp, canvas, Transform3D())
|
||||
RenderingServer.canvas_item_set_parent(ci_rid, canvas)
|
||||
var texture := ImageTexture.create_from_image(image)
|
||||
RenderingServer.canvas_item_add_texture_rect(
|
||||
ci_rid, Rect2(Vector2.ZERO, image.get_size()), texture
|
||||
)
|
||||
|
||||
var mat_rid = VisualServer.material_create()
|
||||
VisualServer.material_set_shader(mat_rid, _shader.get_rid())
|
||||
var lut = Image.new()
|
||||
lut.create(256, 1, false, Image.FORMAT_RGB8)
|
||||
var mat_rid := RenderingServer.material_create()
|
||||
RenderingServer.material_set_shader(mat_rid, _shader.get_rid())
|
||||
var lut := Image.create(256, 1, false, Image.FORMAT_RGB8)
|
||||
lut.fill(Color8(colors[0][0], colors[0][1], colors[0][2]))
|
||||
lut.lock()
|
||||
for i in colors.size():
|
||||
lut.set_pixel(i, 0, Color8(colors[i][0], colors[i][1], colors[i][2]))
|
||||
var lut_tex = ImageTexture.new()
|
||||
lut_tex.create_from_image(lut)
|
||||
VisualServer.material_set_param(mat_rid, "lut", lut_tex)
|
||||
VisualServer.canvas_item_set_material(ci_rid, mat_rid)
|
||||
var lut_tex := ImageTexture.create_from_image(lut)
|
||||
# Not sure why putting lut_tex is an array is needed, but without it, it doesn't work
|
||||
RenderingServer.material_set_param(mat_rid, "lut", [lut_tex])
|
||||
RenderingServer.canvas_item_set_material(ci_rid, mat_rid)
|
||||
|
||||
VisualServer.viewport_set_update_mode(vp, VisualServer.VIEWPORT_UPDATE_ONCE)
|
||||
VisualServer.viewport_set_vflip(vp, true)
|
||||
VisualServer.force_draw(false)
|
||||
image = VisualServer.texture_get_data(VisualServer.viewport_get_texture(vp))
|
||||
RenderingServer.viewport_set_update_mode(vp, RenderingServer.VIEWPORT_UPDATE_ONCE)
|
||||
RenderingServer.force_draw(false)
|
||||
image = RenderingServer.texture_2d_get(RenderingServer.viewport_get_texture(vp))
|
||||
|
||||
VisualServer.free_rid(vp)
|
||||
VisualServer.free_rid(canvas)
|
||||
VisualServer.free_rid(ci_rid)
|
||||
VisualServer.free_rid(mat_rid)
|
||||
RenderingServer.free_rid(vp)
|
||||
RenderingServer.free_rid(canvas)
|
||||
RenderingServer.free_rid(ci_rid)
|
||||
RenderingServer.free_rid(mat_rid)
|
||||
|
||||
image.convert(Image.FORMAT_R8)
|
||||
return image.get_data()
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
enum Error { OK = 0, EMPTY_IMAGE = 1, BAD_IMAGE_FORMAT = 2 }
|
||||
|
||||
var little_endian = preload("./little_endian.gd").new()
|
||||
var lzw = preload("./gif-lzw/lzw.gd").new()
|
||||
var converter = preload("./converter.gd")
|
||||
var little_endian := preload("./little_endian.gd").new()
|
||||
var lzw := preload("./gif-lzw/lzw.gd").new()
|
||||
var converter := preload("./converter.gd")
|
||||
|
||||
var last_color_table := []
|
||||
var last_transparency_index := -1
|
||||
|
||||
# File data and Header
|
||||
var data := PoolByteArray([])
|
||||
var data := PackedByteArray([])
|
||||
|
||||
|
||||
func _init(_width: int, _height: int):
|
||||
|
@ -19,12 +19,12 @@ func _init(_width: int, _height: int):
|
|||
add_application_ext("NETSCAPE", "2.0", [1, 0, 0])
|
||||
|
||||
|
||||
func export_file_data() -> PoolByteArray:
|
||||
return data + PoolByteArray([0x3b])
|
||||
func export_file_data() -> PackedByteArray:
|
||||
return data + PackedByteArray([0x3b])
|
||||
|
||||
|
||||
func add_header() -> void:
|
||||
data += "GIF".to_ascii() + "89a".to_ascii()
|
||||
data += "GIF".to_ascii_buffer() + "89a".to_ascii_buffer()
|
||||
|
||||
|
||||
func add_logical_screen_descriptor(width: int, height: int) -> void:
|
||||
|
@ -53,18 +53,17 @@ func add_application_ext(app_iden: String, app_auth_code: String, _data: Array)
|
|||
data.append(extension_introducer)
|
||||
data.append(extension_label)
|
||||
data.append(block_size)
|
||||
data += app_iden.to_ascii()
|
||||
data += app_auth_code.to_ascii()
|
||||
data += app_iden.to_ascii_buffer()
|
||||
data += app_auth_code.to_ascii_buffer()
|
||||
data.append(_data.size())
|
||||
data += PoolByteArray(_data)
|
||||
data += PackedByteArray(_data)
|
||||
data.append(0)
|
||||
|
||||
|
||||
# finds the image color table. Stops if the size gets larger than 256.
|
||||
func find_color_table(image: Image) -> Dictionary:
|
||||
image.lock()
|
||||
var result: Dictionary = {}
|
||||
var image_data: PoolByteArray = image.get_data()
|
||||
var image_data: PackedByteArray = image.get_data()
|
||||
|
||||
for i in range(0, image_data.size(), 4):
|
||||
var color: Array = [
|
||||
|
@ -77,8 +76,6 @@ func find_color_table(image: Image) -> Dictionary:
|
|||
result[color] = result.size()
|
||||
if result.size() > 256:
|
||||
break
|
||||
|
||||
image.unlock()
|
||||
return result
|
||||
|
||||
|
||||
|
@ -89,10 +86,11 @@ func find_transparency_color_index(color_table: Dictionary) -> int:
|
|||
return -1
|
||||
|
||||
|
||||
func colors_to_codes(img: Image, col_palette: Dictionary, transp_color_index: int) -> PoolByteArray:
|
||||
img.lock()
|
||||
var image_data: PoolByteArray = img.get_data()
|
||||
var result: PoolByteArray = PoolByteArray([])
|
||||
func colors_to_codes(
|
||||
img: Image, col_palette: Dictionary, transp_color_index: int
|
||||
) -> PackedByteArray:
|
||||
var image_data: PackedByteArray = img.get_data()
|
||||
var result: PackedByteArray = PackedByteArray([])
|
||||
|
||||
for i in range(0, image_data.size(), 4):
|
||||
var color: Array = [image_data[i], image_data[i + 1], image_data[i + 2], image_data[i + 3]]
|
||||
|
@ -106,7 +104,6 @@ func colors_to_codes(img: Image, col_palette: Dictionary, transp_color_index: in
|
|||
result.append(0)
|
||||
push_warning("colors_to_codes: color not found! [%d, %d, %d, %d]" % color)
|
||||
|
||||
img.unlock()
|
||||
return result
|
||||
|
||||
|
||||
|
@ -120,11 +117,11 @@ func make_proper_size(color_table: Array) -> Array:
|
|||
|
||||
|
||||
func calc_delay_time(frame_delay: float) -> int:
|
||||
return int(ceil(frame_delay / 0.01))
|
||||
return int(ceili(frame_delay / 0.01))
|
||||
|
||||
|
||||
func color_table_to_indexes(colors: Array) -> PoolByteArray:
|
||||
var result: PoolByteArray = PoolByteArray([])
|
||||
func color_table_to_indexes(colors: Array) -> PackedByteArray:
|
||||
var result: PackedByteArray = PackedByteArray([])
|
||||
for i in range(colors.size()):
|
||||
result.append(i)
|
||||
return result
|
||||
|
@ -141,13 +138,13 @@ func add_frame(image: Image, frame_delay: float, quantizator: Script) -> int:
|
|||
|
||||
var found_color_table: Dictionary = find_color_table(image)
|
||||
|
||||
var image_converted_to_codes: PoolByteArray
|
||||
var image_converted_to_codes: PackedByteArray
|
||||
var transparency_color_index: int = -1
|
||||
var color_table: Array
|
||||
if found_color_table.size() <= 256: # we don't need to quantize the image.
|
||||
# try to find transparency color index.
|
||||
transparency_color_index = find_transparency_color_index(found_color_table)
|
||||
# if didn't found transparency color index but there is atleast one
|
||||
# if didn't find transparency color index but there is at least one
|
||||
# place for this color then add it artificially.
|
||||
if transparency_color_index == -1 and found_color_table.size() <= 255:
|
||||
found_color_table[[0, 0, 0, 0]] = found_color_table.size()
|
||||
|
@ -172,7 +169,7 @@ func add_frame(image: Image, frame_delay: float, quantizator: Script) -> int:
|
|||
var compressed_image_result: Array = lzw.compress_lzw(
|
||||
image_converted_to_codes, color_table_indexes
|
||||
)
|
||||
var compressed_image_data: PoolByteArray = compressed_image_result[0]
|
||||
var compressed_image_data: PackedByteArray = compressed_image_result[0]
|
||||
var lzw_min_code_size: int = compressed_image_result[1]
|
||||
|
||||
add_graphic_constrol_ext(delay_time, transparency_color_index)
|
||||
|
@ -183,7 +180,7 @@ func add_frame(image: Image, frame_delay: float, quantizator: Script) -> int:
|
|||
return Error.OK
|
||||
|
||||
|
||||
# adds frame with last color informations
|
||||
## Adds frame with last color information
|
||||
func add_frame_with_lci(image: Image, frame_delay: float) -> int:
|
||||
# check if image is of good format
|
||||
if image.get_format() != Image.FORMAT_RGBA8:
|
||||
|
@ -193,7 +190,7 @@ func add_frame_with_lci(image: Image, frame_delay: float) -> int:
|
|||
if image.is_empty():
|
||||
return Error.EMPTY_IMAGE
|
||||
|
||||
var image_converted_to_codes: PoolByteArray = converter.new().get_similar_indexed_datas(
|
||||
var image_converted_to_codes: PackedByteArray = converter.new().get_similar_indexed_datas(
|
||||
image, last_color_table
|
||||
)
|
||||
|
||||
|
@ -201,7 +198,7 @@ func add_frame_with_lci(image: Image, frame_delay: float) -> int:
|
|||
var compressed_image_result: Array = lzw.compress_lzw(
|
||||
image_converted_to_codes, color_table_indexes
|
||||
)
|
||||
var compressed_image_data: PoolByteArray = compressed_image_result[0]
|
||||
var compressed_image_data: PackedByteArray = compressed_image_result[0]
|
||||
var lzw_min_code_size: int = compressed_image_result[1]
|
||||
|
||||
var delay_time := calc_delay_time(frame_delay)
|
||||
|
@ -258,38 +255,34 @@ func color_table_bit_size(color_table: Array) -> int:
|
|||
|
||||
func add_local_color_table(color_table: Array) -> void:
|
||||
for color in color_table:
|
||||
data.append_array([color[0], color[1], color[2]])
|
||||
data.append(color[0])
|
||||
data.append(color[1])
|
||||
data.append(color[2])
|
||||
|
||||
var size := color_table_bit_size(color_table)
|
||||
var proper_size := int(pow(2, size + 1))
|
||||
|
||||
if color_table.size() != proper_size:
|
||||
for i in range(proper_size - color_table.size()):
|
||||
data.append_array([0, 0, 0])
|
||||
data += PackedByteArray([0, 0, 0])
|
||||
|
||||
|
||||
func add_image_data_block(lzw_min_code_size: int, frame_data: PoolByteArray) -> void:
|
||||
var max_block_size = 254
|
||||
func add_image_data_block(lzw_min_code_size: int, _data: PackedByteArray) -> void:
|
||||
data.append(lzw_min_code_size)
|
||||
# the amount of blocks which will be stored
|
||||
# ceiled because the last block doesn't have to be exactly max_block_size
|
||||
var block_count = ceil(frame_data.size() / float(max_block_size))
|
||||
|
||||
for i in range(block_count):
|
||||
var start_block_index = i * max_block_size
|
||||
var end_block_index = (i * max_block_size) + max_block_size - 1
|
||||
# final block can be smaller than max block size
|
||||
end_block_index = (
|
||||
end_block_index
|
||||
if end_block_index < frame_data.size() - 1
|
||||
else frame_data.size() - 1
|
||||
)
|
||||
var block_size = end_block_index - start_block_index + 1
|
||||
var block = frame_data.subarray(start_block_index, end_block_index)
|
||||
|
||||
# store block size and it's data
|
||||
data.append(block_size)
|
||||
data.append_array(block)
|
||||
|
||||
if not frame_data.empty():
|
||||
var block_size_index: int = 0
|
||||
var i: int = 0
|
||||
var data_index: int = 0
|
||||
while data_index < _data.size():
|
||||
if i == 0:
|
||||
data.append(0)
|
||||
block_size_index = data.size() - 1
|
||||
data.append(_data[data_index])
|
||||
data[block_size_index] += 1
|
||||
data_index += 1
|
||||
i += 1
|
||||
if i == 254:
|
||||
i = 0
|
||||
|
||||
if not _data.is_empty():
|
||||
data.append(0)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
|
||||
class LSBLZWBitPacker:
|
||||
var bit_index: int = 0
|
||||
var stream: int = 0
|
||||
|
||||
var chunks: PoolByteArray = PoolByteArray([])
|
||||
var chunks: PackedByteArray = PackedByteArray([])
|
||||
|
||||
func put_byte():
|
||||
chunks.append(stream & 0xff)
|
||||
|
@ -20,7 +20,7 @@ class LSBLZWBitPacker:
|
|||
while bit_index >= 8:
|
||||
self.put_byte()
|
||||
|
||||
func pack() -> PoolByteArray:
|
||||
func pack() -> PackedByteArray:
|
||||
if bit_index != 0:
|
||||
self.put_byte()
|
||||
return chunks
|
||||
|
@ -28,4 +28,4 @@ class LSBLZWBitPacker:
|
|||
func reset() -> void:
|
||||
bit_index = 0
|
||||
stream = 0
|
||||
chunks = PoolByteArray([])
|
||||
chunks = PackedByteArray([])
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
|
||||
class LSBLZWBitUnpacker:
|
||||
var chunk_stream: PoolByteArray
|
||||
var chunk_stream: PackedByteArray
|
||||
var bit_index: int = 0
|
||||
var byte: int
|
||||
var byte_index: int = 0
|
||||
|
||||
func _init(_chunk_stream: PoolByteArray):
|
||||
func _init(_chunk_stream: PackedByteArray):
|
||||
chunk_stream = _chunk_stream
|
||||
self.get_byte()
|
||||
|
||||
|
|
|
@ -1,38 +1,84 @@
|
|||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
var lsbbitpacker = preload("./lsbbitpacker.gd")
|
||||
var lsbbitunpacker = preload("./lsbbitunpacker.gd")
|
||||
|
||||
var code_table := {}
|
||||
var entries_counter := 0
|
||||
var lsbbitpacker := preload("./lsbbitpacker.gd")
|
||||
var lsbbitunpacker := preload("./lsbbitunpacker.gd")
|
||||
|
||||
|
||||
func get_bit_length(value: int):
|
||||
# bitwise or on value does ensure that the function works with value 0
|
||||
# long number at the end is log(2.0)
|
||||
return ceil(log(value | 0x1 + 1) / 0.6931471805599453)
|
||||
class CodeEntry:
|
||||
var sequence: PackedByteArray
|
||||
var raw_array: Array
|
||||
|
||||
func _init(_sequence):
|
||||
raw_array = _sequence
|
||||
sequence = _sequence
|
||||
|
||||
func add(other: CodeEntry) -> CodeEntry:
|
||||
return CodeEntry.new(self.raw_array + other.raw_array)
|
||||
|
||||
func _to_string():
|
||||
var result: String = ""
|
||||
for element in self.sequence:
|
||||
result += str(element) + ", "
|
||||
return result.substr(0, result.length() - 2)
|
||||
|
||||
|
||||
func initialize_color_code_table(colors: PoolByteArray) -> void:
|
||||
code_table.clear()
|
||||
entries_counter = 0
|
||||
class CodeTable:
|
||||
var entries: Dictionary = {}
|
||||
var counter: int = 0
|
||||
var lookup: Dictionary = {}
|
||||
|
||||
func add(entry) -> int:
|
||||
self.entries[self.counter] = entry
|
||||
self.lookup[entry.raw_array] = self.counter
|
||||
counter += 1
|
||||
return counter
|
||||
|
||||
func find(entry) -> int:
|
||||
return self.lookup.get(entry.raw_array, -1)
|
||||
|
||||
func has(entry) -> bool:
|
||||
return self.find(entry) != -1
|
||||
|
||||
func get_entry(index: int) -> CodeEntry:
|
||||
return self.entries.get(index, null)
|
||||
|
||||
func _to_string() -> String:
|
||||
var result: String = "CodeTable:\n"
|
||||
for id in self.entries:
|
||||
result += str(id) + ": " + self.entries[id].to_string() + "\n"
|
||||
result += "Counter: " + str(self.counter) + "\n"
|
||||
return result
|
||||
|
||||
|
||||
func log2(value: float) -> float:
|
||||
return log(value) / log(2.0)
|
||||
|
||||
|
||||
func get_bits_number_for(value: int) -> int:
|
||||
if value == 0:
|
||||
return 1
|
||||
return int(ceili(log2(value + 1)))
|
||||
|
||||
|
||||
func initialize_color_code_table(colors: PackedByteArray) -> CodeTable:
|
||||
var result_code_table: CodeTable = CodeTable.new()
|
||||
for color_id in colors:
|
||||
# warning-ignore:return_value_discarded
|
||||
code_table[PoolByteArray([color_id])] = entries_counter
|
||||
entries_counter += 1
|
||||
result_code_table.add(CodeEntry.new([color_id]))
|
||||
# move counter to the first available compression code index
|
||||
var last_color_index: int = colors.size() - 1
|
||||
var clear_code_index: int = pow(2, get_bit_length(last_color_index))
|
||||
entries_counter = clear_code_index + 2
|
||||
var clear_code_index: int = pow(2, get_bits_number_for(last_color_index))
|
||||
result_code_table.counter = clear_code_index + 2
|
||||
return result_code_table
|
||||
|
||||
|
||||
# compression and decompression done with source:
|
||||
# http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp
|
||||
|
||||
|
||||
func compress_lzw(index_stream: PoolByteArray, colors: PoolByteArray) -> Array:
|
||||
func compress_lzw(image: PackedByteArray, colors: PackedByteArray) -> Array:
|
||||
# Initialize code table
|
||||
initialize_color_code_table(colors)
|
||||
var code_table: CodeTable = initialize_color_code_table(colors)
|
||||
# Clear Code index is 2**<code size>
|
||||
# <code size> is the amount of bits needed to write down all colors
|
||||
# from color table. We use last color index because we can write
|
||||
|
@ -40,63 +86,127 @@ func compress_lzw(index_stream: PoolByteArray, colors: PoolByteArray) -> Array:
|
|||
# Number 15 is in binary 0b1111, so we'll need 4 bits to write all
|
||||
# colors down.
|
||||
var last_color_index: int = colors.size() - 1
|
||||
var clear_code_index: int = pow(2, get_bit_length(last_color_index))
|
||||
var current_code_size: int = get_bit_length(clear_code_index)
|
||||
var binary_code_stream = lsbbitpacker.LSBLZWBitPacker.new()
|
||||
var clear_code_index: int = pow(2, get_bits_number_for(last_color_index))
|
||||
var index_stream: PackedByteArray = image
|
||||
var current_code_size: int = get_bits_number_for(clear_code_index)
|
||||
var binary_code_stream := lsbbitpacker.LSBLZWBitPacker.new()
|
||||
|
||||
# initialize with Clear Code
|
||||
binary_code_stream.write_bits(clear_code_index, current_code_size)
|
||||
|
||||
# Read first index from index stream.
|
||||
var index_buffer := PoolByteArray([index_stream[0]])
|
||||
var index_buffer := CodeEntry.new([index_stream[0]])
|
||||
var data_index: int = 1
|
||||
# <LOOP POINT>
|
||||
while data_index < index_stream.size():
|
||||
# Get the next index from the index stream.
|
||||
var k := index_stream[data_index]
|
||||
var k := CodeEntry.new([index_stream[data_index]])
|
||||
data_index += 1
|
||||
# Is index buffer + k in our code table?
|
||||
var new_index_buffer := PoolByteArray(index_buffer)
|
||||
new_index_buffer.push_back(k)
|
||||
var new_index_buffer := index_buffer.add(k)
|
||||
if code_table.has(new_index_buffer): # if YES
|
||||
# Add k to the end of the index buffer
|
||||
index_buffer = new_index_buffer
|
||||
else: # if NO
|
||||
# Add a row for index buffer + k into our code table
|
||||
binary_code_stream.write_bits(code_table.get(index_buffer, -1), current_code_size)
|
||||
binary_code_stream.write_bits(code_table.find(index_buffer), current_code_size)
|
||||
|
||||
# We don't want to add new code to code table if we've exceeded 4095
|
||||
# index.
|
||||
var last_entry_index: int = entries_counter - 1
|
||||
var last_entry_index: int = code_table.counter - 1
|
||||
if last_entry_index != 4095:
|
||||
# Output the code for just the index buffer to our code stream
|
||||
# warning-ignore:return_value_discarded
|
||||
code_table[new_index_buffer] = entries_counter
|
||||
entries_counter += 1
|
||||
code_table.add(new_index_buffer)
|
||||
else:
|
||||
# if we exceeded 4095 index (code table is full), we should
|
||||
# output Clear Code and reset everything.
|
||||
binary_code_stream.write_bits(clear_code_index, current_code_size)
|
||||
initialize_color_code_table(colors)
|
||||
code_table = initialize_color_code_table(colors)
|
||||
# get_bits_number_for(clear_code_index) is the same as
|
||||
# LZW code size + 1
|
||||
current_code_size = get_bit_length(clear_code_index)
|
||||
current_code_size = get_bits_number_for(clear_code_index)
|
||||
|
||||
# Detect when you have to save new codes in bigger bits boxes
|
||||
# change current code size when it happens because we want to save
|
||||
# flexible code sized codes
|
||||
var new_code_size_candidate: int = get_bit_length(entries_counter - 1)
|
||||
var new_code_size_candidate: int = get_bits_number_for(code_table.counter - 1)
|
||||
if new_code_size_candidate > current_code_size:
|
||||
current_code_size = new_code_size_candidate
|
||||
|
||||
# Index buffer is set to k
|
||||
index_buffer = PoolByteArray([k])
|
||||
index_buffer = k
|
||||
# Output code for contents of index buffer
|
||||
binary_code_stream.write_bits(code_table.get(index_buffer, -1), current_code_size)
|
||||
binary_code_stream.write_bits(code_table.find(index_buffer), current_code_size)
|
||||
|
||||
# output end with End Of Information Code
|
||||
binary_code_stream.write_bits(clear_code_index + 1, current_code_size)
|
||||
|
||||
var min_code_size: int = get_bit_length(clear_code_index) - 1
|
||||
var min_code_size: int = get_bits_number_for(clear_code_index) - 1
|
||||
|
||||
return [binary_code_stream.pack(), min_code_size]
|
||||
|
||||
|
||||
# gdlint: ignore=max-line-length
|
||||
func decompress_lzw(
|
||||
code_stream_data: PackedByteArray, min_code_size: int, colors: PackedByteArray
|
||||
) -> PackedByteArray:
|
||||
var code_table: CodeTable = initialize_color_code_table(colors)
|
||||
var index_stream := PackedByteArray([])
|
||||
var binary_code_stream = lsbbitunpacker.LSBLZWBitUnpacker.new(code_stream_data)
|
||||
var current_code_size: int = min_code_size + 1
|
||||
var clear_code_index: int = pow(2, min_code_size)
|
||||
|
||||
# CODE is an index of code table, {CODE} is sequence inside
|
||||
# code table with index CODE. The same goes for PREVCODE.
|
||||
|
||||
# Remove first Clear Code from stream. We don't need it.
|
||||
binary_code_stream.remove_bits(current_code_size)
|
||||
|
||||
# let CODE be the first code in the code stream
|
||||
var code: int = binary_code_stream.read_bits(current_code_size)
|
||||
# output {CODE} to index stream
|
||||
index_stream.append_array(code_table.get_entry(code).sequence)
|
||||
# set PREVCODE = CODE
|
||||
var prevcode: int = code
|
||||
# <LOOP POINT>
|
||||
while true:
|
||||
# let CODE be the next code in the code stream
|
||||
code = binary_code_stream.read_bits(current_code_size)
|
||||
# Detect Clear Code. When detected reset everything and get next code.
|
||||
if code == clear_code_index:
|
||||
code_table = initialize_color_code_table(colors)
|
||||
current_code_size = min_code_size + 1
|
||||
code = binary_code_stream.read_bits(current_code_size)
|
||||
elif code == clear_code_index + 1: # Stop when detected EOI Code.
|
||||
break
|
||||
# is CODE in the code table?
|
||||
var code_entry: CodeEntry = code_table.get_entry(code)
|
||||
if code_entry != null: # if YES
|
||||
# output {CODE} to index stream
|
||||
index_stream.append_array(code_entry.sequence)
|
||||
# let k be the first index in {CODE}
|
||||
var k: CodeEntry = CodeEntry.new([code_entry.sequence[0]])
|
||||
# warning-ignore:return_value_discarded
|
||||
# add {PREVCODE} + k to the code table
|
||||
code_table.add(code_table.get_entry(prevcode).add(k))
|
||||
# set PREVCODE = CODE
|
||||
prevcode = code
|
||||
else: # if NO
|
||||
# let k be the first index of {PREVCODE}
|
||||
var prevcode_entry: CodeEntry = code_table.get_entry(prevcode)
|
||||
var k: CodeEntry = CodeEntry.new([prevcode_entry.sequence[0]])
|
||||
# output {PREVCODE} + k to index stream
|
||||
index_stream.append_array(prevcode_entry.add(k).sequence)
|
||||
# add {PREVCODE} + k to code table
|
||||
# warning-ignore:return_value_discarded
|
||||
code_table.add(prevcode_entry.add(k))
|
||||
# set PREVCODE = CODE
|
||||
prevcode = code
|
||||
|
||||
# Detect when we should increase current code size and increase it.
|
||||
var new_code_size_candidate: int = get_bits_number_for(code_table.counter)
|
||||
if new_code_size_candidate > current_code_size:
|
||||
current_code_size = new_code_size_candidate
|
||||
|
||||
return index_stream
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
|
||||
func int_to_2bytes(value: int) -> PoolByteArray:
|
||||
return PoolByteArray([value & 255, (value >> 8) & 255])
|
||||
func int_to_2bytes(value: int) -> PackedByteArray:
|
||||
return PackedByteArray([value & 255, (value >> 8) & 255])
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
extends Reference
|
||||
extends RefCounted
|
||||
|
||||
var converter = preload("../converter.gd").new()
|
||||
var converter := preload("../converter.gd").new()
|
||||
var transparency := false
|
||||
|
||||
|
||||
func longest_axis(colors: Array) -> int:
|
||||
var start := [255, 255, 255]
|
||||
var end := [0, 0, 0]
|
||||
var start: PackedInt32Array = [255, 255, 255]
|
||||
var end: PackedInt32Array = [0, 0, 0]
|
||||
for color in colors:
|
||||
for i in 3:
|
||||
start[i] = min(color[i], start[i])
|
||||
end[i] = max(color[i], end[i])
|
||||
start[i] = mini(color[i], start[i])
|
||||
end[i] = maxi(color[i], end[i])
|
||||
|
||||
var max_r = end[0] - start[0]
|
||||
var max_g = end[1] - start[1]
|
||||
var max_b = end[2] - start[2]
|
||||
var max_r := end[0] - start[0]
|
||||
var max_g := end[1] - start[1]
|
||||
var max_b := end[2] - start[2]
|
||||
|
||||
if max_r > max_g:
|
||||
if max_r > max_b:
|
||||
|
@ -73,16 +73,14 @@ func average_colors(buckets: Array) -> Dictionary:
|
|||
|
||||
|
||||
func pixels_to_colors(image: Image) -> Array:
|
||||
image.lock()
|
||||
var result := []
|
||||
var data: PoolByteArray = image.get_data()
|
||||
var data: PackedByteArray = image.get_data()
|
||||
|
||||
for i in range(0, data.size(), 4):
|
||||
if data[i + 3] == 0:
|
||||
transparency = true
|
||||
continue
|
||||
result.append([data[i], data[i + 1], data[i + 2]])
|
||||
image.unlock()
|
||||
return result
|
||||
|
||||
|
||||
|
@ -93,7 +91,7 @@ func remove_smallest_bucket(buckets: Array) -> Array:
|
|||
for i in range(buckets.size()):
|
||||
if buckets[i].size() < buckets[i_of_smallest_bucket].size():
|
||||
i_of_smallest_bucket = i
|
||||
buckets.remove(i_of_smallest_bucket)
|
||||
buckets.remove_at(i_of_smallest_bucket)
|
||||
return buckets
|
||||
|
||||
|
||||
|
@ -103,15 +101,15 @@ func remove_empty_buckets(buckets: Array) -> Array:
|
|||
|
||||
var i := buckets.find([])
|
||||
while i != -1:
|
||||
buckets.remove(i)
|
||||
buckets.remove_at(i)
|
||||
i = buckets.find([])
|
||||
|
||||
return buckets
|
||||
|
||||
|
||||
# quantizes to gif ready codes
|
||||
## Quantizes to gif ready codes
|
||||
func quantize(image: Image) -> Array:
|
||||
var pixels = pixels_to_colors(image)
|
||||
var pixels := pixels_to_colors(image)
|
||||
if pixels.size() == 0:
|
||||
return pixels
|
||||
|
||||
|
@ -158,6 +156,6 @@ func quantize(image: Image) -> Array:
|
|||
if transparency:
|
||||
color_array = [[0, 0, 0]] + color_array
|
||||
|
||||
var data: PoolByteArray = converter.get_similar_indexed_datas(image, color_array)
|
||||
var data: PackedByteArray = converter.get_similar_indexed_datas(image, color_array)
|
||||
|
||||
return [data, color_array, transparency]
|
||||
|
|
82
addons/gdgifexporter/quantization/uniform.gd
Normal file
|
@ -0,0 +1,82 @@
|
|||
extends RefCounted
|
||||
|
||||
var converter := preload("../converter.gd").new()
|
||||
var transparency := false
|
||||
|
||||
|
||||
func how_many_divisions(colors_count: int) -> int:
|
||||
return int(ceili(pow(colors_count, 1.0 / 4.0)))
|
||||
|
||||
|
||||
func generate_colors(colors_count: int) -> Array:
|
||||
var divisions_count: int = how_many_divisions(colors_count)
|
||||
var colors: Array = []
|
||||
|
||||
for a in range(divisions_count):
|
||||
for b in range(divisions_count):
|
||||
for g in range(divisions_count):
|
||||
for r in range(divisions_count):
|
||||
colors.append(
|
||||
[
|
||||
Vector3(
|
||||
(255.0 / divisions_count) * r,
|
||||
(255.0 / divisions_count) * g,
|
||||
(255.0 / divisions_count) * b
|
||||
),
|
||||
(255.0 / divisions_count) * a
|
||||
]
|
||||
)
|
||||
|
||||
return colors
|
||||
|
||||
|
||||
func find_nearest_color(palette_color: Vector3, image_data: PackedByteArray) -> Array:
|
||||
var nearest_color = null
|
||||
var nearest_alpha = null
|
||||
for i in range(0, image_data.size(), 4):
|
||||
var color := Vector3(image_data[i], image_data[i + 1], image_data[i + 2])
|
||||
# detect transparency
|
||||
if image_data[3] == 0:
|
||||
transparency = true
|
||||
if (
|
||||
(nearest_color == null)
|
||||
or (
|
||||
palette_color.distance_squared_to(color)
|
||||
< palette_color.distance_squared_to(nearest_color)
|
||||
)
|
||||
):
|
||||
nearest_color = color
|
||||
nearest_alpha = image_data[i + 3]
|
||||
return [nearest_color, nearest_alpha]
|
||||
|
||||
|
||||
## Moves every color from palette colors to the nearest found color in image
|
||||
func enhance_colors(image: Image, palette_colors: Array) -> Array:
|
||||
var data := image.get_data()
|
||||
|
||||
for i in range(palette_colors.size()):
|
||||
var nearest_color := find_nearest_color(palette_colors[i][0], data)
|
||||
palette_colors[i] = nearest_color
|
||||
|
||||
return palette_colors
|
||||
|
||||
|
||||
func to_color_array(colors: Array) -> Array:
|
||||
var result := []
|
||||
for v in colors:
|
||||
result.append([v[0].x, v[0].y, v[0].z])
|
||||
return result
|
||||
|
||||
|
||||
## Quantizes to gif ready codes
|
||||
func quantize(image: Image) -> Array:
|
||||
var colors: Array = generate_colors(256)
|
||||
var tmp_image := Image.new()
|
||||
tmp_image.copy_from(image)
|
||||
tmp_image.resize(32, 32)
|
||||
colors = enhance_colors(tmp_image, colors)
|
||||
colors = to_color_array(colors)
|
||||
|
||||
var data: PackedByteArray = converter.get_similar_indexed_datas(image, colors)
|
||||
|
||||
return [data, colors, transparency]
|
|
@ -1,21 +1,19 @@
|
|||
extends Node
|
||||
|
||||
const TRANSLATIONS_PATH := "res://addons/keychain/translations"
|
||||
const PROFILES_PATH := "user://shortcut_profiles"
|
||||
|
||||
# Change these settings
|
||||
var profiles := [preload("profiles/default.tres")]
|
||||
var selected_profile: ShortcutProfile = profiles[0]
|
||||
## Change these settings
|
||||
var profiles: Array[ShortcutProfile] = [preload("profiles/default.tres")]
|
||||
var selected_profile := profiles[0]
|
||||
var profile_index := 0
|
||||
# Syntax: "action_name": InputAction.new("Action Display Name", "Group", true)
|
||||
# Note that "action_name" must already exist in the Project's Input Map.
|
||||
## Syntax: "action_name": InputAction.new("Action Display Name", "Group", true)
|
||||
## Note that "action_name" must already exist in the Project's Input Map.
|
||||
var actions := {}
|
||||
# Syntax: "Group Name": InputGroup.new("Parent Group Name")
|
||||
## Syntax: "Group Name": InputGroup.new("Parent Group Name")
|
||||
var groups := {}
|
||||
var ignore_actions := []
|
||||
var ignore_ui_actions := true
|
||||
var changeable_types := [true, true, true, true]
|
||||
var multiple_menu_accelerators := false
|
||||
var config_path := "user://cache.ini"
|
||||
var config_file: ConfigFile
|
||||
|
||||
|
@ -25,79 +23,11 @@ class InputAction:
|
|||
var group := ""
|
||||
var global := true
|
||||
|
||||
func _init(_display_name := "", _group := "", _global := true) -> void:
|
||||
func _init(_display_name := "", _group := "", _global := true):
|
||||
display_name = _display_name
|
||||
group = _group
|
||||
global = _global
|
||||
|
||||
func update_node(_action: String) -> void:
|
||||
pass
|
||||
|
||||
func handle_input(_event: InputEvent, _action: String) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
# This class is useful for the accelerators of PopupMenu items
|
||||
# It's possible for PopupMenu items to have multiple shortcuts by using
|
||||
# set_item_shortcut(), but we have no control over the accelerator text that appears.
|
||||
# Thus, we are stuck with using accelerators instead of shortcuts.
|
||||
# If Godot ever receives the ability to change the accelerator text of the items,
|
||||
# we could in theory remove this class.
|
||||
# If you don't care about PopupMenus in the same scene as ShortcutEdit
|
||||
# such as projects like Pixelorama where everything is in the same scene,
|
||||
# then you can ignore this class.
|
||||
class MenuInputAction:
|
||||
extends InputAction
|
||||
var node_path := ""
|
||||
var node: PopupMenu
|
||||
var menu_item_id := 0
|
||||
var echo := false
|
||||
|
||||
func _init(
|
||||
_display_name := "",
|
||||
_group := "",
|
||||
_global := true,
|
||||
_node_path := "",
|
||||
_menu_item_id := 0,
|
||||
_echo := false
|
||||
) -> void:
|
||||
._init(_display_name, _group, _global)
|
||||
node_path = _node_path
|
||||
menu_item_id = _menu_item_id
|
||||
echo = _echo
|
||||
|
||||
func get_node(root: Node) -> void:
|
||||
var temp_node = root.get_node(node_path)
|
||||
if temp_node is PopupMenu:
|
||||
node = node
|
||||
elif temp_node is MenuButton:
|
||||
node = temp_node.get_popup()
|
||||
|
||||
func update_node(action: String) -> void:
|
||||
if !node:
|
||||
return
|
||||
var first_key: InputEventKey = Keychain.action_get_first_key(action)
|
||||
var accel := first_key.get_scancode_with_modifiers() if first_key else 0
|
||||
node.set_item_accelerator(menu_item_id, accel)
|
||||
|
||||
func handle_input(event: InputEvent, action: String) -> bool:
|
||||
if not node:
|
||||
return false
|
||||
if event.is_action_pressed(action, false, true):
|
||||
if event is InputEventKey:
|
||||
var acc: int = node.get_item_accelerator(menu_item_id)
|
||||
# If the event is the same as the menu item's accelerator, skip
|
||||
if acc == event.get_scancode_with_modifiers():
|
||||
return true
|
||||
node.emit_signal("id_pressed", menu_item_id)
|
||||
return true
|
||||
if event.is_action(action, true) and echo:
|
||||
if event.is_echo():
|
||||
node.emit_signal("id_pressed", menu_item_id)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
class InputGroup:
|
||||
var parent_group := ""
|
||||
|
@ -112,21 +42,18 @@ class InputGroup:
|
|||
func _ready() -> void:
|
||||
if !config_file:
|
||||
config_file = ConfigFile.new()
|
||||
if !config_path.empty():
|
||||
if !config_path.is_empty():
|
||||
config_file.load(config_path)
|
||||
|
||||
set_process_input(multiple_menu_accelerators)
|
||||
|
||||
# Load shortcut profiles
|
||||
var profile_dir := Directory.new()
|
||||
profile_dir.make_dir(PROFILES_PATH)
|
||||
profile_dir.open(PROFILES_PATH)
|
||||
DirAccess.make_dir_recursive_absolute(PROFILES_PATH)
|
||||
var profile_dir := DirAccess.open(PROFILES_PATH)
|
||||
profile_dir.list_dir_begin()
|
||||
var file_name = profile_dir.get_next()
|
||||
while file_name != "":
|
||||
if !profile_dir.current_is_dir():
|
||||
if file_name.get_extension() == "tres":
|
||||
var file = load(PROFILES_PATH.plus_file(file_name))
|
||||
var file = load(PROFILES_PATH.path_join(file_name))
|
||||
if file is ShortcutProfile:
|
||||
profiles.append(file)
|
||||
file_name = profile_dir.get_next()
|
||||
|
@ -135,7 +62,7 @@ func _ready() -> void:
|
|||
if profiles.size() == 1:
|
||||
var profile := ShortcutProfile.new()
|
||||
profile.name = "Custom"
|
||||
profile.resource_path = PROFILES_PATH.plus_file("custom.tres")
|
||||
profile.resource_path = PROFILES_PATH.path_join("custom.tres")
|
||||
var saved := profile.save()
|
||||
if saved:
|
||||
profiles.append(profile)
|
||||
|
@ -143,37 +70,9 @@ func _ready() -> void:
|
|||
for profile in profiles:
|
||||
profile.fill_bindings()
|
||||
|
||||
var l18n_dir := Directory.new()
|
||||
l18n_dir.open(TRANSLATIONS_PATH)
|
||||
l18n_dir.list_dir_begin()
|
||||
file_name = l18n_dir.get_next()
|
||||
while file_name != "":
|
||||
if !l18n_dir.current_is_dir():
|
||||
if file_name.get_extension() == "po":
|
||||
var t: Translation = load(TRANSLATIONS_PATH.plus_file(file_name))
|
||||
TranslationServer.add_translation(t)
|
||||
file_name = l18n_dir.get_next()
|
||||
|
||||
profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0)
|
||||
change_profile(profile_index)
|
||||
|
||||
for action in actions:
|
||||
var input_action: InputAction = actions[action]
|
||||
if input_action is MenuInputAction:
|
||||
# Below line has been modified
|
||||
input_action.get_node(Global.top_menu_container.get_node("MenuItems"))
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion:
|
||||
return
|
||||
|
||||
for action in actions:
|
||||
var input_action: InputAction = actions[action]
|
||||
var done: bool = input_action.handle_input(event, action)
|
||||
if done:
|
||||
return
|
||||
|
||||
|
||||
func change_profile(index: int) -> void:
|
||||
if index >= profiles.size():
|
||||
|
@ -184,33 +83,21 @@ func change_profile(index: int) -> void:
|
|||
action_erase_events(action)
|
||||
for event in selected_profile.bindings[action]:
|
||||
action_add_event(action, event)
|
||||
# NOTE: Following line not present in the plugin itself, be careful not to overwrite
|
||||
Global.update_hint_tooltips()
|
||||
|
||||
|
||||
func action_add_event(action: String, event: InputEvent) -> void:
|
||||
InputMap.action_add_event(action, event)
|
||||
if action in actions:
|
||||
actions[action].update_node(action)
|
||||
|
||||
|
||||
func action_erase_event(action: String, event: InputEvent) -> void:
|
||||
InputMap.action_erase_event(action, event)
|
||||
if action in actions:
|
||||
actions[action].update_node(action)
|
||||
|
||||
|
||||
func action_erase_events(action: String) -> void:
|
||||
InputMap.action_erase_events(action)
|
||||
if action in actions:
|
||||
actions[action].update_node(action)
|
||||
|
||||
|
||||
func action_get_first_key(action: String) -> InputEventKey:
|
||||
var first_key: InputEventKey = null
|
||||
var events := InputMap.get_action_list(action)
|
||||
for event in events:
|
||||
if event is InputEventKey:
|
||||
first_key = event
|
||||
break
|
||||
return first_key
|
||||
func load_translation(locale: String) -> void:
|
||||
var translation = load("res://addons/keychain/translations".path_join(locale + ".po"))
|
||||
if is_instance_valid(translation) and translation is Translation:
|
||||
TranslationServer.add_translation(translation)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Orama Interactive
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -2,7 +2,7 @@ extends Control
|
|||
|
||||
enum { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS }
|
||||
|
||||
const MOUSE_BUTTON_NAMES := [
|
||||
const MOUSE_BUTTON_NAMES: PackedStringArray = [
|
||||
"Left Button",
|
||||
"Right Button",
|
||||
"Middle Button",
|
||||
|
@ -14,7 +14,7 @@ const MOUSE_BUTTON_NAMES := [
|
|||
"X Button 2",
|
||||
]
|
||||
|
||||
const JOY_BUTTON_NAMES := [
|
||||
const JOY_BUTTON_NAMES: PackedStringArray = [
|
||||
"DualShock Cross, Xbox A, Nintendo B",
|
||||
"DualShock Circle, Xbox B, Nintendo A",
|
||||
"DualShock Square, Xbox X, Nintendo Y",
|
||||
|
@ -40,7 +40,7 @@ const JOY_BUTTON_NAMES := [
|
|||
"PS4/5 Touchpad",
|
||||
]
|
||||
|
||||
const JOY_AXIS_NAMES := [
|
||||
const JOY_AXIS_NAMES: PackedStringArray = [
|
||||
"(Left Stick Left)",
|
||||
"(Left Stick Right)",
|
||||
"(Left Stick Up)",
|
||||
|
@ -66,29 +66,29 @@ const JOY_AXIS_NAMES := [
|
|||
var currently_editing_tree_item: TreeItem
|
||||
var is_editing := false
|
||||
# Textures taken from Godot https://github.com/godotengine/godot/tree/master/editor/icons
|
||||
var add_tex: Texture = preload("assets/add.svg")
|
||||
var edit_tex: Texture = preload("assets/edit.svg")
|
||||
var delete_tex: Texture = preload("assets/close.svg")
|
||||
var joy_axis_tex: Texture = preload("assets/joy_axis.svg")
|
||||
var joy_button_tex: Texture = preload("assets/joy_button.svg")
|
||||
var key_tex: Texture = preload("assets/keyboard.svg")
|
||||
var key_phys_tex: Texture = preload("assets/keyboard_physical.svg")
|
||||
var mouse_tex: Texture = preload("assets/mouse.svg")
|
||||
var shortcut_tex: Texture = preload("assets/shortcut.svg")
|
||||
var folder_tex: Texture = preload("assets/folder.svg")
|
||||
var add_tex: Texture2D = preload("assets/add.svg")
|
||||
var edit_tex: Texture2D = preload("assets/edit.svg")
|
||||
var delete_tex: Texture2D = preload("assets/close.svg")
|
||||
var joy_axis_tex: Texture2D = preload("assets/joy_axis.svg")
|
||||
var joy_button_tex: Texture2D = preload("assets/joy_button.svg")
|
||||
var key_tex: Texture2D = preload("assets/keyboard.svg")
|
||||
var key_phys_tex: Texture2D = preload("assets/keyboard_physical.svg")
|
||||
var mouse_tex: Texture2D = preload("assets/mouse.svg")
|
||||
var shortcut_tex: Texture2D = preload("assets/shortcut.svg")
|
||||
var folder_tex: Texture2D = preload("assets/folder.svg")
|
||||
|
||||
onready var tree: Tree = $VBoxContainer/ShortcutTree
|
||||
onready var profile_option_button: OptionButton = find_node("ProfileOptionButton")
|
||||
onready var rename_profile_button: Button = find_node("RenameProfile")
|
||||
onready var delete_profile_button: Button = find_node("DeleteProfile")
|
||||
onready var shortcut_type_menu: PopupMenu = $ShortcutTypeMenu
|
||||
onready var keyboard_shortcut_selector: ConfirmationDialog = $KeyboardShortcutSelectorDialog
|
||||
onready var mouse_shortcut_selector: ConfirmationDialog = $MouseShortcutSelectorDialog
|
||||
onready var joy_key_shortcut_selector: ConfirmationDialog = $JoyKeyShortcutSelectorDialog
|
||||
onready var joy_axis_shortcut_selector: ConfirmationDialog = $JoyAxisShortcutSelectorDialog
|
||||
onready var profile_settings: ConfirmationDialog = $ProfileSettings
|
||||
onready var profile_name: LineEdit = $ProfileSettings/ProfileName
|
||||
onready var delete_confirmation: ConfirmationDialog = $DeleteConfirmation
|
||||
@onready var tree: Tree = $VBoxContainer/ShortcutTree
|
||||
@onready var profile_option_button: OptionButton = find_child("ProfileOptionButton")
|
||||
@onready var rename_profile_button: Button = find_child("RenameProfile")
|
||||
@onready var delete_profile_button: Button = find_child("DeleteProfile")
|
||||
@onready var shortcut_type_menu: PopupMenu = $ShortcutTypeMenu
|
||||
@onready var keyboard_shortcut_selector: ConfirmationDialog = $KeyboardShortcutSelectorDialog
|
||||
@onready var mouse_shortcut_selector: ConfirmationDialog = $MouseShortcutSelectorDialog
|
||||
@onready var joy_key_shortcut_selector: ConfirmationDialog = $JoyKeyShortcutSelectorDialog
|
||||
@onready var joy_axis_shortcut_selector: ConfirmationDialog = $JoyAxisShortcutSelectorDialog
|
||||
@onready var profile_settings: ConfirmationDialog = $ProfileSettings
|
||||
@onready var profile_name: LineEdit = $ProfileSettings/ProfileName
|
||||
@onready var delete_confirmation: ConfirmationDialog = $DeleteConfirmation
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
@ -107,7 +107,7 @@ func _ready() -> void:
|
|||
|
||||
profile_option_button.select(Keychain.profile_index)
|
||||
_on_ProfileOptionButton_item_selected(Keychain.profile_index)
|
||||
if OS.get_name() == "HTML5":
|
||||
if OS.get_name() == "Web":
|
||||
$VBoxContainer/HBoxContainer/OpenProfileFolder.queue_free()
|
||||
|
||||
|
||||
|
@ -121,7 +121,7 @@ func _construct_tree() -> void:
|
|||
for action in InputMap.get_actions(): # Fill the tree with actions and their events
|
||||
if action in Keychain.ignore_actions:
|
||||
continue
|
||||
if Keychain.ignore_ui_actions and action.begins_with("ui_"):
|
||||
if Keychain.ignore_ui_actions and (action as String).begins_with("ui_"):
|
||||
continue
|
||||
|
||||
var display_name := get_action_name(action)
|
||||
|
@ -131,7 +131,7 @@ func _construct_tree() -> void:
|
|||
group_name = input_action.group
|
||||
|
||||
var tree_item: TreeItem
|
||||
if group_name and group_name in Keychain.groups:
|
||||
if not group_name.is_empty() and group_name in Keychain.groups:
|
||||
var input_group: Keychain.InputGroup = Keychain.groups[group_name]
|
||||
var group_root: TreeItem = input_group.tree_item
|
||||
tree_item = tree.create_item(group_root)
|
||||
|
@ -142,7 +142,7 @@ func _construct_tree() -> void:
|
|||
tree_item.set_text(0, display_name)
|
||||
tree_item.set_metadata(0, action)
|
||||
tree_item.set_icon(0, shortcut_tex)
|
||||
for event in InputMap.get_action_list(action):
|
||||
for event in InputMap.action_get_events(action):
|
||||
add_event_tree_item(event, tree_item)
|
||||
|
||||
tree_item.add_button(0, add_tex, 0, buttons_disabled, "Add")
|
||||
|
@ -151,8 +151,6 @@ func _construct_tree() -> void:
|
|||
|
||||
|
||||
func _fill_selector_options() -> void:
|
||||
keyboard_shortcut_selector.entered_shortcut.visible = true
|
||||
keyboard_shortcut_selector.option_button.visible = false
|
||||
mouse_shortcut_selector.input_type_l.text = "Mouse Button Index:"
|
||||
joy_key_shortcut_selector.input_type_l.text = "Joypad Button Index:"
|
||||
joy_axis_shortcut_selector.input_type_l.text = "Joypad Axis Index:"
|
||||
|
@ -171,8 +169,8 @@ func _fill_selector_options() -> void:
|
|||
var joy_axis_option_button: OptionButton = joy_axis_shortcut_selector.option_button
|
||||
var i := 0.0
|
||||
for option in JOY_AXIS_NAMES:
|
||||
var sign_symbol = "+" if floor(i) != i else "-"
|
||||
var text: String = tr("Axis") + " %s %s %s" % [floor(i), sign_symbol, tr(option)]
|
||||
var sign_symbol := "+" if floori(i) != i else "-"
|
||||
var text: String = tr("Axis") + " %s %s %s" % [floori(i), sign_symbol, tr(option)]
|
||||
joy_axis_option_button.add_item(text)
|
||||
i += 0.5
|
||||
|
||||
|
@ -200,7 +198,7 @@ func get_action_name(action: String) -> String:
|
|||
if action in Keychain.actions:
|
||||
display_name = Keychain.actions[action].display_name
|
||||
|
||||
if display_name.empty():
|
||||
if display_name.is_empty():
|
||||
display_name = _humanize_snake_case(action)
|
||||
return display_name
|
||||
|
||||
|
@ -209,7 +207,7 @@ func _humanize_snake_case(text: String) -> String:
|
|||
text = text.replace("_", " ")
|
||||
var first_letter := text.left(1)
|
||||
first_letter = first_letter.capitalize()
|
||||
text.erase(0, 1)
|
||||
text = text.right(-1)
|
||||
text = text.insert(0, first_letter)
|
||||
return text
|
||||
|
||||
|
@ -236,7 +234,7 @@ func add_event_tree_item(event: InputEvent, action_tree_item: TreeItem) -> void:
|
|||
event_tree_item.set_metadata(0, event)
|
||||
match event_class:
|
||||
"InputEventKey":
|
||||
var scancode: int = event.get_scancode_with_modifiers()
|
||||
var scancode: int = event.get_keycode_with_modifiers()
|
||||
if scancode > 0:
|
||||
event_tree_item.set_icon(0, key_tex)
|
||||
else:
|
||||
|
@ -252,19 +250,10 @@ func add_event_tree_item(event: InputEvent, action_tree_item: TreeItem) -> void:
|
|||
|
||||
|
||||
func event_to_str(event: InputEvent) -> String:
|
||||
var output := ""
|
||||
if event is InputEventKey:
|
||||
var scancode: int = event.get_scancode_with_modifiers()
|
||||
var physical_str := ""
|
||||
if scancode == 0:
|
||||
scancode = event.get_physical_scancode_with_modifiers()
|
||||
physical_str = " " + tr("(Physical)")
|
||||
output = OS.get_scancode_string(scancode) + physical_str
|
||||
|
||||
elif event is InputEventMouseButton:
|
||||
output = tr(MOUSE_BUTTON_NAMES[event.button_index - 1])
|
||||
|
||||
elif event is InputEventJoypadButton:
|
||||
var output := event.as_text()
|
||||
# event.as_text() could be used for these event types as well, but this gives more control
|
||||
# to the developer as to what strings will be printed
|
||||
if event is InputEventJoypadButton:
|
||||
var button_index: int = event.button_index
|
||||
output = tr("Button")
|
||||
if button_index >= JOY_BUTTON_NAMES.size():
|
||||
|
@ -281,24 +270,22 @@ func event_to_str(event: InputEvent) -> String:
|
|||
return output
|
||||
|
||||
|
||||
func _on_ShortcutTree_button_pressed(item: TreeItem, _column: int, id: int) -> void:
|
||||
func _on_shortcut_tree_button_clicked(item: TreeItem, _column: int, id: int, _mbi: int) -> void:
|
||||
var action = item.get_metadata(0)
|
||||
currently_editing_tree_item = item
|
||||
if action is String:
|
||||
if action is StringName:
|
||||
if id == 0: # Add
|
||||
var rect: Rect2 = tree.get_item_area_rect(item, 0)
|
||||
rect.position.x = rect.end.x - 42
|
||||
rect.position.y += 42 - tree.get_scroll().y
|
||||
rect.position += rect_global_position
|
||||
rect.position += global_position
|
||||
rect.size = Vector2(110, 23 * shortcut_type_menu.get_item_count())
|
||||
shortcut_type_menu.popup(rect)
|
||||
elif id == 1: # Delete
|
||||
Keychain.action_erase_events(action)
|
||||
Keychain.selected_profile.change_action(action)
|
||||
var child := item.get_children()
|
||||
while child != null:
|
||||
for child in item.get_children():
|
||||
child.free()
|
||||
child = item.get_children()
|
||||
|
||||
elif action is InputEvent:
|
||||
var parent_action = item.get_parent().get_metadata(0)
|
||||
|
@ -312,7 +299,7 @@ func _on_ShortcutTree_button_pressed(item: TreeItem, _column: int, id: int) -> v
|
|||
elif action is InputEventJoypadMotion:
|
||||
joy_axis_shortcut_selector.popup_centered()
|
||||
elif id == 1: # Delete
|
||||
if not parent_action is String:
|
||||
if not parent_action is StringName:
|
||||
return
|
||||
Keychain.action_erase_event(parent_action, action)
|
||||
Keychain.selected_profile.change_action(parent_action)
|
||||
|
@ -322,7 +309,7 @@ func _on_ShortcutTree_button_pressed(item: TreeItem, _column: int, id: int) -> v
|
|||
func _on_ShortcutTree_item_activated() -> void:
|
||||
var selected_item: TreeItem = tree.get_selected()
|
||||
if selected_item.get_button_count(0) > 0 and !selected_item.is_button_disabled(0, 0):
|
||||
_on_ShortcutTree_button_pressed(tree.get_selected(), 0, 0)
|
||||
_on_shortcut_tree_button_clicked(tree.get_selected(), 0, 0, 0)
|
||||
|
||||
|
||||
func _on_ShortcutTypeMenu_id_pressed(id: int) -> void:
|
||||
|
@ -353,14 +340,14 @@ func _on_ProfileOptionButton_item_selected(index: int) -> void:
|
|||
func _on_NewProfile_pressed() -> void:
|
||||
is_editing = false
|
||||
profile_name.text = "New Shortcut Profile"
|
||||
profile_settings.window_title = "New Shortcut Profile"
|
||||
profile_settings.title = "New Shortcut Profile"
|
||||
profile_settings.popup_centered()
|
||||
|
||||
|
||||
func _on_RenameProfile_pressed() -> void:
|
||||
is_editing = true
|
||||
profile_name.text = Keychain.selected_profile.name
|
||||
profile_settings.window_title = "Rename Shortcut Profile"
|
||||
profile_settings.title = "Rename Shortcut Profile"
|
||||
profile_settings.popup_centered()
|
||||
|
||||
|
||||
|
@ -376,7 +363,7 @@ func _on_ProfileSettings_confirmed() -> void:
|
|||
var file_name := profile_name.text + ".tres"
|
||||
var profile := ShortcutProfile.new()
|
||||
profile.name = profile_name.text
|
||||
profile.resource_path = Keychain.PROFILES_PATH.plus_file(file_name)
|
||||
profile.resource_path = Keychain.PROFILES_PATH.path_join(file_name)
|
||||
profile.fill_bindings()
|
||||
var saved := profile.save()
|
||||
if not saved:
|
||||
|
@ -397,14 +384,18 @@ func _on_ProfileSettings_confirmed() -> void:
|
|||
|
||||
|
||||
func _delete_profile_file(file_name: String) -> void:
|
||||
var dir := Directory.new()
|
||||
var dir := DirAccess.open(file_name.get_base_dir())
|
||||
var err := dir.get_open_error()
|
||||
if err != OK:
|
||||
print("Error deleting shortcut profile %s. Error code: %s" % [file_name, err])
|
||||
return
|
||||
dir.remove(file_name)
|
||||
|
||||
|
||||
func _on_DeleteConfirmation_confirmed() -> void:
|
||||
_delete_profile_file(Keychain.selected_profile.resource_path)
|
||||
profile_option_button.remove_item(Keychain.profile_index)
|
||||
Keychain.profiles.remove(Keychain.profile_index)
|
||||
Keychain.profiles.remove_at(Keychain.profile_index)
|
||||
Keychain.profile_index -= 1
|
||||
if Keychain.profile_index < 0:
|
||||
Keychain.profile_index = 0
|
||||
|
|
|
@ -1,105 +1,108 @@
|
|||
[gd_scene load_steps=7 format=2]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://bq7ibhm0txl5p"]
|
||||
|
||||
[ext_resource path="res://addons/keychain/ShortcutEdit.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/keychain/assets/joy_button.svg" type="Texture" id=2]
|
||||
[ext_resource path="res://addons/keychain/assets/keyboard.svg" type="Texture" id=3]
|
||||
[ext_resource path="res://addons/keychain/assets/joy_axis.svg" type="Texture" id=4]
|
||||
[ext_resource path="res://addons/keychain/assets/mouse.svg" type="Texture" id=5]
|
||||
[ext_resource path="res://addons/keychain/ShortcutSelectorDialog.tscn" type="PackedScene" id=6]
|
||||
[ext_resource type="Script" path="res://addons/keychain/ShortcutEdit.gd" id="1"]
|
||||
[ext_resource type="Texture2D" uid="uid://ca58ufal2ufd8" path="res://addons/keychain/assets/joy_button.svg" id="2"]
|
||||
[ext_resource type="Texture2D" uid="uid://c2s5rm4nec5yh" path="res://addons/keychain/assets/keyboard.svg" id="3"]
|
||||
[ext_resource type="Texture2D" uid="uid://bb6q6om3d08cm" path="res://addons/keychain/assets/joy_axis.svg" id="4"]
|
||||
[ext_resource type="Texture2D" uid="uid://bma7xj2rqqcr8" path="res://addons/keychain/assets/mouse.svg" id="5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfjcafe2kvx7n" path="res://addons/keychain/ShortcutSelectorDialog.tscn" id="6"]
|
||||
|
||||
[node name="ShortcutEdit" type="VBoxContainer"]
|
||||
[node name="ShortcutEdit" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource( 1 )
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
margin_right = 1280.0
|
||||
margin_bottom = 720.0
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
margin_right = 1280.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ProfileLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_top = 3.0
|
||||
margin_right = 102.0
|
||||
margin_bottom = 17.0
|
||||
layout_mode = 2
|
||||
text = "Shortcut profile:"
|
||||
|
||||
[node name="ProfileOptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_left = 106.0
|
||||
margin_right = 135.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
|
||||
[node name="NewProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_left = 139.0
|
||||
margin_right = 179.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "New"
|
||||
|
||||
[node name="RenameProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_left = 183.0
|
||||
margin_right = 247.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Rename"
|
||||
|
||||
[node name="DeleteProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_left = 251.0
|
||||
margin_right = 306.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Delete"
|
||||
|
||||
[node name="OpenProfileFolder" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
margin_left = 310.0
|
||||
margin_right = 401.0
|
||||
margin_bottom = 20.0
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Open Folder"
|
||||
|
||||
[node name="ShortcutTree" type="Tree" parent="VBoxContainer"]
|
||||
margin_top = 24.0
|
||||
margin_right = 1280.0
|
||||
margin_bottom = 720.0
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
hide_root = true
|
||||
|
||||
[node name="ShortcutTypeMenu" type="PopupMenu" parent="."]
|
||||
margin_right = 20.0
|
||||
margin_bottom = 20.0
|
||||
items = [ "Key", ExtResource( 3 ), 0, false, false, 0, 0, null, "", false, "Mouse Button", ExtResource( 5 ), 0, false, false, 1, 0, null, "", false, "Joy Button", ExtResource( 2 ), 0, false, false, 2, 0, null, "", false, "Joy Axis", ExtResource( 4 ), 0, false, false, 3, 0, null, "", false ]
|
||||
size = Vector2i(154, 116)
|
||||
item_count = 4
|
||||
item_0/text = "Key"
|
||||
item_0/icon = ExtResource("3")
|
||||
item_0/id = 0
|
||||
item_1/text = "Mouse Button"
|
||||
item_1/icon = ExtResource("5")
|
||||
item_1/id = 1
|
||||
item_2/text = "Joy Button"
|
||||
item_2/icon = ExtResource("2")
|
||||
item_2/id = 2
|
||||
item_3/text = "Joy Axis"
|
||||
item_3/icon = ExtResource("4")
|
||||
item_3/id = 3
|
||||
|
||||
[node name="KeyboardShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
|
||||
[node name="KeyboardShortcutSelectorDialog" parent="." instance=ExtResource("6")]
|
||||
size = Vector2i(417, 134)
|
||||
|
||||
[node name="MouseShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
|
||||
[node name="MouseShortcutSelectorDialog" parent="." instance=ExtResource("6")]
|
||||
size = Vector2i(417, 134)
|
||||
input_type = 1
|
||||
|
||||
[node name="JoyKeyShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
|
||||
[node name="JoyKeyShortcutSelectorDialog" parent="." instance=ExtResource("6")]
|
||||
size = Vector2i(417, 134)
|
||||
input_type = 2
|
||||
|
||||
[node name="JoyAxisShortcutSelectorDialog" parent="." instance=ExtResource( 6 )]
|
||||
[node name="JoyAxisShortcutSelectorDialog" parent="." instance=ExtResource("6")]
|
||||
size = Vector2i(417, 134)
|
||||
input_type = 3
|
||||
|
||||
[node name="ProfileSettings" type="ConfirmationDialog" parent="."]
|
||||
margin_right = 200.0
|
||||
margin_bottom = 70.0
|
||||
|
||||
[node name="ProfileName" type="LineEdit" parent="ProfileSettings"]
|
||||
margin_left = 8.0
|
||||
margin_top = 8.0
|
||||
margin_right = 192.0
|
||||
margin_bottom = 34.0
|
||||
caret_blink = true
|
||||
caret_blink_speed = 0.5
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 192.0
|
||||
offset_bottom = 51.0
|
||||
|
||||
[node name="DeleteConfirmation" type="ConfirmationDialog" parent="."]
|
||||
margin_right = 200.0
|
||||
margin_bottom = 70.0
|
||||
size = Vector2i(427, 100)
|
||||
dialog_text = "Are you sure you want to delete this shortcut profile?"
|
||||
|
||||
[connection signal="item_selected" from="VBoxContainer/HBoxContainer/ProfileOptionButton" to="." method="_on_ProfileOptionButton_item_selected"]
|
||||
|
@ -107,7 +110,7 @@ dialog_text = "Are you sure you want to delete this shortcut profile?"
|
|||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/RenameProfile" to="." method="_on_RenameProfile_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/DeleteProfile" to="." method="_on_DeleteProfile_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/OpenProfileFolder" to="." method="_on_OpenProfileFolder_pressed"]
|
||||
[connection signal="button_pressed" from="VBoxContainer/ShortcutTree" to="." method="_on_ShortcutTree_button_pressed"]
|
||||
[connection signal="button_clicked" from="VBoxContainer/ShortcutTree" to="." method="_on_shortcut_tree_button_clicked"]
|
||||
[connection signal="item_activated" from="VBoxContainer/ShortcutTree" to="." method="_on_ShortcutTree_item_activated"]
|
||||
[connection signal="id_pressed" from="ShortcutTypeMenu" to="." method="_on_ShortcutTypeMenu_id_pressed"]
|
||||
[connection signal="confirmed" from="ProfileSettings" to="." method="_on_ProfileSettings_confirmed"]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
class_name ShortcutProfile
|
||||
extends Resource
|
||||
|
||||
export(String) var name := ""
|
||||
export(bool) var customizable := true
|
||||
export(Dictionary) var bindings := {}
|
||||
@export var name := ""
|
||||
@export var customizable := true
|
||||
@export var bindings := {}
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
|
@ -14,7 +14,7 @@ func fill_bindings() -> void:
|
|||
var unnecessary_actions = bindings.duplicate() # Checks if the profile has any unused actions
|
||||
for action in InputMap.get_actions():
|
||||
if not action in bindings:
|
||||
bindings[action] = InputMap.get_action_list(action)
|
||||
bindings[action] = InputMap.action_get_events(action)
|
||||
unnecessary_actions.erase(action)
|
||||
for action in unnecessary_actions:
|
||||
bindings.erase(action)
|
||||
|
@ -24,14 +24,14 @@ func fill_bindings() -> void:
|
|||
func change_action(action: String) -> void:
|
||||
if not customizable:
|
||||
return
|
||||
bindings[action] = InputMap.get_action_list(action)
|
||||
bindings[action] = InputMap.action_get_events(action)
|
||||
save()
|
||||
|
||||
|
||||
func save() -> bool:
|
||||
if !customizable:
|
||||
return false
|
||||
var err := ResourceSaver.save(resource_path, self)
|
||||
var err := ResourceSaver.save(self, resource_path)
|
||||
if err != OK:
|
||||
print("Error saving shortcut profile %s. Error code: %s" % [resource_path, err])
|
||||
return false
|
||||
|
|
|
@ -2,28 +2,39 @@ extends ConfirmationDialog
|
|||
|
||||
enum InputTypes { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS }
|
||||
|
||||
export(InputTypes) var input_type: int = InputTypes.KEYBOARD
|
||||
@export var input_type := InputTypes.KEYBOARD
|
||||
var listened_input: InputEvent
|
||||
|
||||
onready var root: Node = get_parent()
|
||||
onready var input_type_l: Label = $VBoxContainer/InputTypeLabel
|
||||
onready var entered_shortcut: LineEdit = $VBoxContainer/EnteredShortcut
|
||||
onready var option_button: OptionButton = $VBoxContainer/OptionButton
|
||||
onready var already_exists: Label = $VBoxContainer/AlreadyExistsLabel
|
||||
@onready var root := get_parent()
|
||||
@onready var input_type_l := $VBoxContainer/InputTypeLabel as Label
|
||||
@onready var entered_shortcut := $VBoxContainer/EnteredShortcut as LineEdit
|
||||
@onready var option_button := $VBoxContainer/OptionButton as OptionButton
|
||||
@onready var modifier_buttons := $VBoxContainer/ModifierButtons as HBoxContainer
|
||||
@onready var alt_button := $VBoxContainer/ModifierButtons/Alt as CheckBox
|
||||
@onready var shift_button := $VBoxContainer/ModifierButtons/Shift as CheckBox
|
||||
@onready var control_button := $VBoxContainer/ModifierButtons/Control as CheckBox
|
||||
@onready var meta_button := $VBoxContainer/ModifierButtons/Meta as CheckBox
|
||||
@onready var command_control_button := $VBoxContainer/ModifierButtons/CommandOrControl as CheckBox
|
||||
@onready var already_exists := $VBoxContainer/AlreadyExistsLabel as Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_process_input(false)
|
||||
if input_type == InputTypes.KEYBOARD:
|
||||
get_ok().focus_neighbour_top = entered_shortcut.get_path()
|
||||
get_cancel().focus_neighbour_top = entered_shortcut.get_path()
|
||||
entered_shortcut.focus_neighbour_bottom = get_ok().get_path()
|
||||
entered_shortcut.visible = true
|
||||
option_button.visible = false
|
||||
get_ok_button().focus_neighbor_top = entered_shortcut.get_path()
|
||||
get_cancel_button().focus_neighbor_top = entered_shortcut.get_path()
|
||||
entered_shortcut.focus_neighbor_bottom = get_ok_button().get_path()
|
||||
else:
|
||||
get_ok().focus_neighbour_top = option_button.get_path()
|
||||
get_cancel().focus_neighbour_top = option_button.get_path()
|
||||
option_button.focus_neighbour_bottom = get_ok().get_path()
|
||||
if input_type != InputTypes.MOUSE:
|
||||
modifier_buttons.visible = false
|
||||
get_ok_button().focus_neighbor_top = option_button.get_path()
|
||||
get_cancel_button().focus_neighbor_top = option_button.get_path()
|
||||
option_button.focus_neighbor_bottom = get_ok_button().get_path()
|
||||
|
||||
get_close_button().focus_mode = Control.FOCUS_NONE
|
||||
|
||||
# get_close_button().focus_mode = Control.FOCUS_NONE
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
@ -31,7 +42,8 @@ func _input(event: InputEvent) -> void:
|
|||
return
|
||||
if event.pressed:
|
||||
listened_input = event
|
||||
entered_shortcut.text = OS.get_scancode_string(event.get_scancode_with_modifiers())
|
||||
_set_modifier_buttons_state(listened_input)
|
||||
entered_shortcut.text = event.as_text()
|
||||
_show_assigned_state(event)
|
||||
|
||||
|
||||
|
@ -40,7 +52,7 @@ func _show_assigned_state(event: InputEvent) -> void:
|
|||
var action := ""
|
||||
if metadata is InputEvent: # Editing an input event
|
||||
action = root.currently_editing_tree_item.get_parent().get_metadata(0)
|
||||
elif metadata is String: # Adding a new input event to an action
|
||||
elif metadata is StringName: # Adding a new input event to an action
|
||||
action = metadata
|
||||
|
||||
var matching_pair: Array = _find_matching_event_in_map(action, event)
|
||||
|
@ -65,14 +77,14 @@ func _apply_shortcut_change(input_event: InputEvent) -> void:
|
|||
return
|
||||
root.currently_editing_tree_item.set_metadata(0, input_event)
|
||||
root.currently_editing_tree_item.set_text(0, root.event_to_str(input_event))
|
||||
elif metadata is String: # Adding a new input event to an action
|
||||
elif metadata is StringName: # Adding a new input event to an action
|
||||
var changed: bool = _set_shortcut(metadata, null, input_event)
|
||||
if !changed:
|
||||
return
|
||||
root.add_event_tree_item(input_event, root.currently_editing_tree_item)
|
||||
|
||||
|
||||
func _set_shortcut(action: String, old_event: InputEvent, new_event: InputEvent) -> bool:
|
||||
func _set_shortcut(action: StringName, old_event: InputEvent, new_event: InputEvent) -> bool:
|
||||
if InputMap.action_has_event(action, new_event): # If the current action already has that event
|
||||
return false
|
||||
if old_event:
|
||||
|
@ -86,7 +98,7 @@ func _set_shortcut(action: String, old_event: InputEvent, new_event: InputEvent)
|
|||
if action in Keychain.actions:
|
||||
group = Keychain.actions[action].group
|
||||
|
||||
var action_to_replace: String = matching_pair[0]
|
||||
var action_to_replace: StringName = matching_pair[0]
|
||||
var input_to_replace: InputEvent = matching_pair[1]
|
||||
Keychain.action_erase_event(action_to_replace, input_to_replace)
|
||||
Keychain.selected_profile.change_action(action_to_replace)
|
||||
|
@ -95,34 +107,20 @@ func _set_shortcut(action: String, old_event: InputEvent, new_event: InputEvent)
|
|||
while tree_item != null: # Loop through Tree's TreeItems...
|
||||
var metadata = tree_item.get_metadata(0)
|
||||
if metadata is InputEvent:
|
||||
if input_to_replace.shortcut_match(metadata):
|
||||
var map_action: String = tree_item.get_parent().get_metadata(0)
|
||||
if input_to_replace.is_match(metadata):
|
||||
var map_action: StringName = tree_item.get_parent().get_metadata(0)
|
||||
if map_action == action_to_replace:
|
||||
tree_item.free()
|
||||
break
|
||||
|
||||
tree_item = _get_next_tree_item(tree_item)
|
||||
tree_item = tree_item.get_next_in_tree()
|
||||
|
||||
Keychain.action_add_event(action, new_event)
|
||||
Keychain.selected_profile.change_action(action)
|
||||
return true
|
||||
|
||||
|
||||
# Based on https://github.com/godotengine/godot/blob/master/scene/gui/tree.cpp#L685
|
||||
func _get_next_tree_item(current: TreeItem) -> TreeItem:
|
||||
if current.get_children():
|
||||
current = current.get_children()
|
||||
elif current.get_next():
|
||||
current = current.get_next()
|
||||
else:
|
||||
while current and !current.get_next():
|
||||
current = current.get_parent()
|
||||
if current:
|
||||
current = current.get_next()
|
||||
return current
|
||||
|
||||
|
||||
func _find_matching_event_in_map(action: String, event: InputEvent) -> Array:
|
||||
func _find_matching_event_in_map(action: StringName, event: InputEvent) -> Array:
|
||||
var group := ""
|
||||
if action in Keychain.actions:
|
||||
group = Keychain.actions[action].group
|
||||
|
@ -130,10 +128,10 @@ func _find_matching_event_in_map(action: String, event: InputEvent) -> Array:
|
|||
for map_action in InputMap.get_actions():
|
||||
if map_action in Keychain.ignore_actions:
|
||||
continue
|
||||
if Keychain.ignore_ui_actions and map_action.begins_with("ui_"):
|
||||
if Keychain.ignore_ui_actions and (map_action as String).begins_with("ui_"):
|
||||
continue
|
||||
for map_event in InputMap.get_action_list(map_action):
|
||||
if !event.shortcut_match(map_event):
|
||||
for map_event in InputMap.action_get_events(map_action):
|
||||
if !event.is_match(map_event):
|
||||
continue
|
||||
|
||||
if map_action in Keychain.actions:
|
||||
|
@ -148,27 +146,30 @@ func _find_matching_event_in_map(action: String, event: InputEvent) -> Array:
|
|||
|
||||
|
||||
func _on_ShortcutSelectorDialog_about_to_show() -> void:
|
||||
var metadata = root.currently_editing_tree_item.get_metadata(0)
|
||||
if input_type == InputTypes.KEYBOARD:
|
||||
listened_input = null
|
||||
already_exists.text = ""
|
||||
entered_shortcut.text = ""
|
||||
yield(get_tree(), "idle_frame")
|
||||
if metadata is InputEvent:
|
||||
_set_modifier_buttons_state(metadata)
|
||||
await get_tree().process_frame
|
||||
entered_shortcut.grab_focus()
|
||||
else:
|
||||
var metadata = root.currently_editing_tree_item.get_metadata(0)
|
||||
if metadata is InputEvent: # Editing an input event
|
||||
var index := 0
|
||||
if metadata is InputEventMouseButton:
|
||||
index = metadata.button_index - 1
|
||||
_set_modifier_buttons_state(metadata)
|
||||
elif metadata is InputEventJoypadButton:
|
||||
index = metadata.button_index
|
||||
elif metadata is InputEventJoypadMotion:
|
||||
index = metadata.axis * 2
|
||||
index += round(metadata.axis_value) / 2.0 + 0.5
|
||||
index += signi(metadata.axis_value) / 2.0 + 0.5
|
||||
option_button.select(index)
|
||||
_on_OptionButton_item_selected(index)
|
||||
|
||||
elif metadata is String: # Adding a new input event to an action
|
||||
elif metadata is StringName: # Adding a new input event to an action
|
||||
option_button.select(0)
|
||||
_on_OptionButton_item_selected(0)
|
||||
|
||||
|
@ -181,6 +182,11 @@ func _on_OptionButton_item_selected(index: int) -> void:
|
|||
if input_type == InputTypes.MOUSE:
|
||||
listened_input = InputEventMouseButton.new()
|
||||
listened_input.button_index = index + 1
|
||||
listened_input.alt_pressed = alt_button.button_pressed
|
||||
listened_input.shift_pressed = shift_button.button_pressed
|
||||
listened_input.ctrl_pressed = control_button.button_pressed
|
||||
listened_input.meta_pressed = meta_button.button_pressed
|
||||
listened_input.command_or_control_autoremap = command_control_button.button_pressed
|
||||
elif input_type == InputTypes.JOY_BUTTON:
|
||||
listened_input = InputEventJoypadButton.new()
|
||||
listened_input.button_index = index
|
||||
|
@ -197,3 +203,50 @@ func _on_EnteredShortcut_focus_entered() -> void:
|
|||
|
||||
func _on_EnteredShortcut_focus_exited() -> void:
|
||||
set_process_input(false)
|
||||
|
||||
|
||||
func _on_alt_toggled(button_pressed: bool) -> void:
|
||||
if not is_instance_valid(listened_input):
|
||||
return
|
||||
listened_input.alt_pressed = button_pressed
|
||||
entered_shortcut.text = listened_input.as_text()
|
||||
|
||||
|
||||
func _set_modifier_buttons_state(event: InputEventWithModifiers) -> void:
|
||||
alt_button.button_pressed = event.alt_pressed
|
||||
shift_button.button_pressed = event.shift_pressed
|
||||
control_button.button_pressed = event.ctrl_pressed
|
||||
meta_button.button_pressed = event.meta_pressed
|
||||
command_control_button.button_pressed = event.command_or_control_autoremap
|
||||
|
||||
|
||||
func _on_shift_toggled(button_pressed: bool) -> void:
|
||||
if not is_instance_valid(listened_input):
|
||||
return
|
||||
listened_input.shift_pressed = button_pressed
|
||||
entered_shortcut.text = listened_input.as_text()
|
||||
|
||||
|
||||
func _on_control_toggled(button_pressed: bool) -> void:
|
||||
if not is_instance_valid(listened_input):
|
||||
return
|
||||
listened_input.ctrl_pressed = button_pressed
|
||||
entered_shortcut.text = listened_input.as_text()
|
||||
|
||||
|
||||
func _on_meta_toggled(button_pressed: bool) -> void:
|
||||
if not is_instance_valid(listened_input):
|
||||
return
|
||||
listened_input.meta_pressed = button_pressed
|
||||
entered_shortcut.text = listened_input.as_text()
|
||||
|
||||
|
||||
func _on_command_or_control_toggled(button_pressed: bool) -> void:
|
||||
control_button.button_pressed = false
|
||||
meta_button.button_pressed = false
|
||||
control_button.visible = not button_pressed
|
||||
meta_button.visible = not button_pressed
|
||||
if not is_instance_valid(listened_input):
|
||||
return
|
||||
listened_input.command_or_control_autoremap = button_pressed
|
||||
entered_shortcut.text = listened_input.as_text()
|
||||
|
|
|
@ -1,49 +1,69 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bfjcafe2kvx7n"]
|
||||
|
||||
[ext_resource path="res://addons/keychain/ShortcutSelectorDialog.gd" type="Script" id=1]
|
||||
[ext_resource type="Script" path="res://addons/keychain/ShortcutSelectorDialog.gd" id="1"]
|
||||
|
||||
[node name="ShortcutSelectorDialog" type="ConfirmationDialog"]
|
||||
margin_right = 200.0
|
||||
margin_bottom = 70.0
|
||||
popup_exclusive = true
|
||||
window_title = "Set the shortcut"
|
||||
script = ExtResource( 1 )
|
||||
size = Vector2i(417, 169)
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
margin_left = 8.0
|
||||
margin_top = 8.0
|
||||
margin_right = 341.0
|
||||
margin_bottom = 64.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 409.0
|
||||
offset_bottom = 120.0
|
||||
|
||||
[node name="InputTypeLabel" type="Label" parent="VBoxContainer"]
|
||||
margin_right = 333.0
|
||||
margin_bottom = 14.0
|
||||
layout_mode = 2
|
||||
text = "Press a key or a key combination to set the shortcut"
|
||||
|
||||
[node name="EnteredShortcut" type="LineEdit" parent="VBoxContainer"]
|
||||
visible = false
|
||||
margin_top = 18.0
|
||||
margin_right = 333.0
|
||||
margin_bottom = 32.0
|
||||
align = 1
|
||||
layout_mode = 2
|
||||
editable = false
|
||||
virtual_keyboard_enabled = false
|
||||
|
||||
[node name="OptionButton" type="OptionButton" parent="VBoxContainer"]
|
||||
margin_top = 18.0
|
||||
margin_right = 333.0
|
||||
margin_bottom = 38.0
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
|
||||
[node name="AlreadyExistsLabel" type="Label" parent="VBoxContainer"]
|
||||
margin_top = 42.0
|
||||
margin_right = 333.0
|
||||
margin_bottom = 56.0
|
||||
align = 1
|
||||
[node name="ModifierButtons" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="about_to_show" from="." to="." method="_on_ShortcutSelectorDialog_about_to_show"]
|
||||
[node name="Alt" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Alt"
|
||||
|
||||
[node name="Shift" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Shift"
|
||||
|
||||
[node name="Control" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Control"
|
||||
|
||||
[node name="Meta" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Meta"
|
||||
|
||||
[node name="CommandOrControl" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Command / Control (auto)"
|
||||
|
||||
[node name="AlreadyExistsLabel" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="about_to_popup" from="." to="." method="_on_ShortcutSelectorDialog_about_to_show"]
|
||||
[connection signal="confirmed" from="." to="." method="_on_ShortcutSelectorDialog_confirmed"]
|
||||
[connection signal="popup_hide" from="." to="." method="_on_ShortcutSelectorDialog_popup_hide"]
|
||||
[connection signal="focus_entered" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_entered"]
|
||||
[connection signal="focus_exited" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_exited"]
|
||||
[connection signal="item_selected" from="VBoxContainer/OptionButton" to="." method="_on_OptionButton_item_selected"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModifierButtons/Alt" to="." method="_on_alt_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModifierButtons/Shift" to="." method="_on_shift_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModifierButtons/Control" to="." method="_on_control_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModifierButtons/Meta" to="." method="_on_meta_toggled"]
|
||||
[connection signal="toggled" from="VBoxContainer/ModifierButtons/CommandOrControl" to="." method="_on_command_or_control_toggled"]
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#e0e0e0" d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z"/></svg>
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z" fill="#e0e0e0"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 148 B After Width: | Height: | Size: 149 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/add.svg-4084e314648c872072757f9b0f544cf9.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c4433pa25cxp2"
|
||||
path="res://.godot/imported/add.svg-4084e314648c872072757f9b0f544cf9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/add.svg"
|
||||
dest_files=[ "res://.import/add.svg-4084e314648c872072757f9b0f544cf9.stex" ]
|
||||
dest_files=["res://.godot/imported/add.svg-4084e314648c872072757f9b0f544cf9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#e0e0e0" d="m3.7578 2.3438-1.4141 1.4141 4.2422 4.2422-4.2422 4.2422 1.4141 1.4141 4.2422-4.2422 4.2422 4.2422 1.4141-1.4141-4.2422-4.2422 4.2422-4.2422-1.4141-1.4141-4.2422 4.2422z"/></svg>
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.7578 2.3438-1.4141 1.4141 4.2422 4.2422-4.2422 4.2422 1.4141 1.4141 4.2422-4.2422 4.2422 4.2422 1.4141-1.4141-4.2422-4.2422 4.2422-4.2422-1.4141-1.4141-4.2422 4.2422z" fill="#e0e0e0"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 285 B After Width: | Height: | Size: 286 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://f1gcylay6c6f"
|
||||
path="res://.godot/imported/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/close.svg"
|
||||
dest_files=[ "res://.import/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.stex" ]
|
||||
dest_files=["res://.godot/imported/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#e0e0e0" d="m7 1c-.554 0-1 .446-1 1v2h4v-2c0-.554-.446-1-1-1zm-1 4v7l2 3 2-3v-7zm1 1h1v5h-1z"/></svg>
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1c-.554 0-1 .446-1 1v2h4v-2c0-.554-.446-1-1-1zm-1 4v7l2 3 2-3v-7zm1 1h1v5h-1z" fill="#e0e0e0"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 196 B After Width: | Height: | Size: 197 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/edit.svg-cd9834545a8696f1e8611efa12a48f33.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://df8gl3u2nqpl3"
|
||||
path="res://.godot/imported/edit.svg-cd9834545a8696f1e8611efa12a48f33.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/edit.svg"
|
||||
dest_files=[ "res://.import/edit.svg-cd9834545a8696f1e8611efa12a48f33.stex" ]
|
||||
dest_files=["res://.godot/imported/edit.svg-cd9834545a8696f1e8611efa12a48f33.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#e0e0e0" d="m2 2a1 1 0 0 0 -1 1v2 6 2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-7a1 1 0 0 0 -1-1h-4a1 1 0 0 1 -1-1v-1a1 1 0 0 0 -1-1z"/></svg>
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 2a1 1 0 0 0 -1 1v2 6 2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-7a1 1 0 0 0 -1-1h-4a1 1 0 0 1 -1-1v-1a1 1 0 0 0 -1-1z" fill="#e0e0e0"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 227 B After Width: | Height: | Size: 228 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/folder.svg-490bb7e2d2aa4425de998b1d382cf534.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b0gbmkb8xwksb"
|
||||
path="res://.godot/imported/folder.svg-490bb7e2d2aa4425de998b1d382cf534.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/folder.svg"
|
||||
dest_files=[ "res://.import/folder.svg-490bb7e2d2aa4425de998b1d382cf534.stex" ]
|
||||
dest_files=["res://.godot/imported/folder.svg-490bb7e2d2aa4425de998b1d382cf534.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bb6q6om3d08cm"
|
||||
path="res://.godot/imported/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/joy_axis.svg"
|
||||
dest_files=[ "res://.import/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.stex" ]
|
||||
dest_files=["res://.godot/imported/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/joy_button.svg-df5663c6f296cab556e81b9c770699d0.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ca58ufal2ufd8"
|
||||
path="res://.godot/imported/joy_button.svg-df5663c6f296cab556e81b9c770699d0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/joy_button.svg"
|
||||
dest_files=[ "res://.import/joy_button.svg-df5663c6f296cab556e81b9c770699d0.stex" ]
|
||||
dest_files=["res://.godot/imported/joy_button.svg-df5663c6f296cab556e81b9c770699d0.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill-opacity=".996"><path fill="#e0e0e0" d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm1.543 1.139h1.393l1.834 4.199h1.295v.437c.708.052 1.246.239 1.61.559.368.316.55.747.55 1.295 0 .552-.182.99-.55 1.314-.368.32-.906.505-1.61.553v.467h-1.294v-.473c-.708-.06-1.247-.248-1.615-.564-.364-.316-.545-.75-.545-1.297 0-.548.181-.977.545-1.29.368-.315.907-.504 1.615-.564v-.437h-1.464l-.282-.733h-1.595l-.284.733h-1.439l1.836-4.2zm.684 1.39-.409 1.057h.817zm3.84 4.338v1.526c.28-.04.483-.12.607-.24.124-.125.185-.302.185-.53 0-.224-.063-.396-.191-.516-.124-.12-.326-.2-.602-.24zm-1.296.006c-.284.04-.487.12-.61.24-.12.116-.182.288-.182.516 0 .22.065.392.193.512.132.12.331.202.6.246v-1.514z"/><path fill="#fff" d="m27 2h7v14h-7z"/><path fill="#e0e0e0" d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z"/></g></svg>
|
||||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm1.543 1.139h1.393l1.834 4.199h1.295v.437c.708.052 1.246.239 1.61.559.368.316.55.747.55 1.295 0 .552-.182.99-.55 1.314-.368.32-.906.505-1.61.553v.467h-1.294v-.473c-.708-.06-1.247-.248-1.615-.564-.364-.316-.545-.75-.545-1.297 0-.548.181-.977.545-1.29.368-.315.907-.504 1.615-.564v-.437h-1.464l-.282-.733h-1.595l-.284.733h-1.439l1.836-4.2zm.684 1.39-.409 1.057h.817zm3.84 4.338v1.526c.28-.04.483-.12.607-.24.124-.125.185-.302.185-.53 0-.224-.063-.396-.191-.516-.124-.12-.326-.2-.602-.24zm-1.296.006c-.284.04-.487.12-.61.24-.12.116-.182.288-.182.516 0 .22.065.392.193.512.132.12.331.202.6.246v-1.514z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 960 B After Width: | Height: | Size: 961 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2s5rm4nec5yh"
|
||||
path="res://.godot/imported/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/keyboard.svg"
|
||||
dest_files=[ "res://.import/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.stex" ]
|
||||
dest_files=["res://.godot/imported/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill-opacity=".996"><path fill="#e0e0e0" d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm2.762 1.768h2.476l3.264 7.464h-2.604l-.502-1.3h-2.835l-.502 1.3h-2.561zm1.217 2.474-.725 1.878h1.45z"/><path fill="#fff" d="m27 2h7v14h-7z"/><path fill="#e0e0e0" d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z"/></g></svg>
|
||||
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm2.762 1.768h2.476l3.264 7.464h-2.604l-.502-1.3h-2.835l-.502 1.3h-2.561zm1.217 2.474-.725 1.878h1.45z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 465 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cmh8eaibhn5y8"
|
||||
path="res://.godot/imported/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/keyboard_physical.svg"
|
||||
dest_files=[ "res://.import/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.stex" ]
|
||||
dest_files=["res://.godot/imported/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#e0e0e0" d="m7 1.1016a5 5 0 0 0 -4 4.8984h4zm2 .0039063v4.8945h4a5 5 0 0 0 -4-4.8945zm-6 6.8945v2a5 5 0 0 0 5 5 5 5 0 0 0 5-5v-2z"/></svg>
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1.1016a5 5 0 0 0 -4 4.8984h4zm2 .0039063v4.8945h4a5 5 0 0 0 -4-4.8945zm-6 6.8945v2a5 5 0 0 0 5 5 5 5 0 0 0 5-5v-2z" fill="#e0e0e0"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 234 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bma7xj2rqqcr8"
|
||||
path="res://.godot/imported/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/mouse.svg"
|
||||
dest_files=[ "res://.import/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.stex" ]
|
||||
dest_files=["res://.godot/imported/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#e0e0e0" fill-opacity=".996" d="m4 2c-.55228 0-1 .4477-1 1v9.084c.0004015.506.448.91602 1 .91602h8c.552 0 .9996-.41002 1-.91602v-9.084c0-.5523-.44772-1-1-1zm-3 2v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a.99998.99998 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9zm6 0h3l-1 3h2l-4 4 1-3h-2z"/></svg>
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m4 2c-.55228 0-1 .4477-1 1v9.084c.0004015.506.448.91602 1 .91602h8c.552 0 .9996-.41002 1-.91602v-9.084c0-.5523-.44772-1-1-1zm-3 2v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a.99998.99998 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9zm6 0h3l-1 3h2l-4 4 1-3h-2z" fill="#e0e0e0" fill-opacity=".99608"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 382 B |
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/shortcut.svg-401daac18a817142e2b29e5801e00053.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bmi24jp4fqi7k"
|
||||
path="res://.godot/imported/shortcut.svg-401daac18a817142e2b29e5801e00053.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://addons/keychain/assets/shortcut.svg"
|
||||
dest_files=[ "res://.import/shortcut.svg-401daac18a817142e2b29e5801e00053.stex" ]
|
||||
dest_files=["res://.godot/imported/shortcut.svg-401daac18a817142e2b29e5801e00053.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
name="Keychain"
|
||||
description="A plugin for the Godot Engine that aims to give the player full control over the input actions of the game."
|
||||
author="Orama Interactive"
|
||||
version="1.1"
|
||||
version="2.0"
|
||||
script="plugin.gd"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
tool
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
[gd_resource type="Resource" load_steps=2 format=2]
|
||||
[gd_resource type="Resource" script_class="ShortcutProfile" load_steps=2 format=3 uid="uid://df04uev1epmmo"]
|
||||
|
||||
[ext_resource path="res://addons/keychain/ShortcutProfile.gd" type="Script" id=1]
|
||||
[ext_resource type="Script" path="res://addons/keychain/ShortcutProfile.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource( 1 )
|
||||
script = ExtResource("1")
|
||||
name = "Default"
|
||||
customizable = false
|
||||
bindings = {
|
||||
}
|
||||
bindings = {}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/bayer16.png-48f80e5e8f0bdac9e83b3acfa8588cf2.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cdtpmo4pjjjdd"
|
||||
path="res://.godot/imported/bayer16.png-48f80e5e8f0bdac9e83b3acfa8588cf2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/dither-matrices/bayer16.png"
|
||||
dest_files=[ "res://.import/bayer16.png-48f80e5e8f0bdac9e83b3acfa8588cf2.stex" ]
|
||||
dest_files=["res://.godot/imported/bayer16.png-48f80e5e8f0bdac9e83b3acfa8588cf2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=1
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/bayer2.png-c060ec2442edc204b9ec0ab2d99e19bf.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cdoxrolk7qdju"
|
||||
path="res://.godot/imported/bayer2.png-c060ec2442edc204b9ec0ab2d99e19bf.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/dither-matrices/bayer2.png"
|
||||
dest_files=[ "res://.import/bayer2.png-c060ec2442edc204b9ec0ab2d99e19bf.stex" ]
|
||||
dest_files=["res://.godot/imported/bayer2.png-c060ec2442edc204b9ec0ab2d99e19bf.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=1
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/bayer4.png-2008f73a4dd6b2f9f4ba6aac825a7b9b.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://oald6mp1l1h7"
|
||||
path="res://.godot/imported/bayer4.png-2008f73a4dd6b2f9f4ba6aac825a7b9b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/dither-matrices/bayer4.png"
|
||||
dest_files=[ "res://.import/bayer4.png-2008f73a4dd6b2f9f4ba6aac825a7b9b.stex" ]
|
||||
dest_files=["res://.godot/imported/bayer4.png-2008f73a4dd6b2f9f4ba6aac825a7b9b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=1
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/bayer8.png-2c0f9ff189b3d219f822ce84a94f5abd.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bq45j7jxav8nw"
|
||||
path="res://.godot/imported/bayer8.png-2c0f9ff189b3d219f822ce84a94f5abd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/dither-matrices/bayer8.png"
|
||||
dest_files=[ "res://.import/bayer8.png-2c0f9ff189b3d219f822ce84a94f5abd.stex" ]
|
||||
dest_files=["res://.godot/imported/bayer8.png-2c0f9ff189b3d219f822ce84a94f5abd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=1
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
##########
|
||||
|
||||
This directory contains the fonts for the platform. They are licensed
|
||||
under the Apache 2 license.
|
|
@ -1,10 +0,0 @@
|
|||
[gd_resource type="DynamicFont" load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://assets/fonts/Roboto-Regular.ttf" type="DynamicFontData" id=1]
|
||||
[ext_resource path="res://assets/fonts/DroidSansFallback.ttf" type="DynamicFontData" id=2]
|
||||
|
||||
[resource]
|
||||
use_mipmaps = true
|
||||
use_filter = true
|
||||
font_data = ExtResource( 1 )
|
||||
fallback/0 = ExtResource( 2 )
|
|
@ -1,11 +0,0 @@
|
|||
[gd_resource type="DynamicFont" load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://assets/fonts/Roboto-Italic.ttf" type="DynamicFontData" id=1]
|
||||
[ext_resource path="res://assets/fonts/DroidSansFallback.ttf" type="DynamicFontData" id=2]
|
||||
|
||||
[resource]
|
||||
size = 12
|
||||
use_mipmaps = true
|
||||
use_filter = true
|
||||
font_data = ExtResource( 1 )
|
||||
fallback/0 = ExtResource( 2 )
|
33
assets/fonts/Roboto-Italic.ttf.import
Normal file
|
@ -0,0 +1,33 @@
|
|||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cy5lkye8vdr5t"
|
||||
path="res://.godot/imported/Roboto-Italic.ttf-1459ab7510a4240cd642ef0fe9caff7f.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/fonts/Roboto-Italic.ttf"
|
||||
dest_files=["res://.godot/imported/Roboto-Italic.ttf-1459ab7510a4240cd642ef0fe9caff7f.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=1
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
|
@ -1,11 +0,0 @@
|
|||
[gd_resource type="DynamicFont" load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://assets/fonts/Roboto-Regular.ttf" type="DynamicFontData" id=1]
|
||||
[ext_resource path="res://assets/fonts/DroidSansFallback.ttf" type="DynamicFontData" id=2]
|
||||
|
||||
[resource]
|
||||
size = 12
|
||||
use_mipmaps = true
|
||||
use_filter = true
|
||||
font_data = ExtResource( 1 )
|
||||
fallback/0 = ExtResource( 2 )
|
33
assets/fonts/Roboto-Regular.ttf.import
Normal file
|
@ -0,0 +1,33 @@
|
|||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cvc4lelf6hl7x"
|
||||
path="res://.godot/imported/Roboto-Regular.ttf-e67097a08cc051e6b179dbaab401b2bc.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/fonts/Roboto-Regular.ttf"
|
||||
dest_files=["res://.godot/imported/Roboto-Regular.ttf-e67097a08cc051e6b179dbaab401b2bc.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
hinting=1
|
||||
subpixel_positioning=1
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
|
@ -1,10 +0,0 @@
|
|||
[gd_resource type="DynamicFont" load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://assets/fonts/Roboto-Regular.ttf" type="DynamicFontData" id=1]
|
||||
[ext_resource path="res://assets/fonts/DroidSansFallback.ttf" type="DynamicFontData" id=2]
|
||||
|
||||
[resource]
|
||||
size = 10
|
||||
use_mipmaps = true
|
||||
font_data = ExtResource( 1 )
|
||||
fallback/0 = ExtResource( 2 )
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
importer="image"
|
||||
type="Image"
|
||||
path="res://.import/circle_9x9.png-775114b180b1f2b8d06dc42243fc410b.image"
|
||||
uid="uid://bccayivs7v851"
|
||||
path="res://.godot/imported/circle_9x9.png-775114b180b1f2b8d06dc42243fc410b.image"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/circle_9x9.png"
|
||||
dest_files=[ "res://.import/circle_9x9.png-775114b180b1f2b8d06dc42243fc410b.image" ]
|
||||
dest_files=["res://.godot/imported/circle_9x9.png-775114b180b1f2b8d06dc42243fc410b.image"]
|
||||
|
||||
[params]
|
||||
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
importer="image"
|
||||
type="Image"
|
||||
path="res://.import/circle_filled_9x9.png-babef9d7f189abd07273845268222c6c.image"
|
||||
uid="uid://0rkdth7xh8ah"
|
||||
path="res://.godot/imported/circle_filled_9x9.png-babef9d7f189abd07273845268222c6c.image"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/circle_filled_9x9.png"
|
||||
dest_files=[ "res://.import/circle_filled_9x9.png-babef9d7f189abd07273845268222c6c.image" ]
|
||||
dest_files=["res://.godot/imported/circle_filled_9x9.png-babef9d7f189abd07273845268222c6c.image"]
|
||||
|
||||
[params]
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/cursor.png-45ecd5a1e583fe3ca68fe559623aadce.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://busi7lojhqxgq"
|
||||
path="res://.godot/imported/cursor.png-45ecd5a1e583fe3ca68fe559623aadce.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/cursor.png"
|
||||
dest_files=[ "res://.import/cursor.png-45ecd5a1e583fe3ca68fe559623aadce.stex" ]
|
||||
dest_files=["res://.godot/imported/cursor.png-45ecd5a1e583fe3ca68fe559623aadce.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/dotted_line.png-9a94e4e6ebf9c11e53076f53e529d37a.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://xnqvppcpt7jb"
|
||||
path="res://.godot/imported/dotted_line.png-9a94e4e6ebf9c11e53076f53e529d37a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/dotted_line.png"
|
||||
dest_files=[ "res://.import/dotted_line.png-9a94e4e6ebf9c11e53076f53e529d37a.stex" ]
|
||||
dest_files=["res://.godot/imported/dotted_line.png-9a94e4e6ebf9c11e53076f53e529d37a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=1
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/directional_light.svg-093cdb9a72dee590271da014181a4680.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bd25ysikhyhqn"
|
||||
path="res://.godot/imported/directional_light.svg-093cdb9a72dee590271da014181a4680.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/gizmos/directional_light.svg"
|
||||
dest_files=[ "res://.import/directional_light.svg-093cdb9a72dee590271da014181a4680.stex" ]
|
||||
dest_files=["res://.godot/imported/directional_light.svg-093cdb9a72dee590271da014181a4680.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/omni_light.svg-b0faa945d45257c6c9fecd256ed51eee.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cu38hb880ltae"
|
||||
path="res://.godot/imported/omni_light.svg-b0faa945d45257c6c9fecd256ed51eee.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/gizmos/omni_light.svg"
|
||||
dest_files=[ "res://.import/omni_light.svg-b0faa945d45257c6c9fecd256ed51eee.stex" ]
|
||||
dest_files=["res://.godot/imported/omni_light.svg-b0faa945d45257c6c9fecd256ed51eee.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/spot_light.svg-d309a6f5345413a6c8b9afd3cfe72f29.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ded88bscdjle8"
|
||||
path="res://.godot/imported/spot_light.svg-d309a6f5345413a6c8b9afd3cfe72f29.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,27 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/gizmos/spot_light.svg"
|
||||
dest_files=[ "res://.import/spot_light.svg-d309a6f5345413a6c8b9afd3cfe72f29.stex" ]
|
||||
dest_files=["res://.godot/imported/spot_light.svg-d309a6f5345413a6c8b9afd3cfe72f29.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/icon.png-aa34c86f977c2e615fbfe8e823c3b00c.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b47r0c6auaqk6"
|
||||
path="res://.godot/imported/icon.png-aa34c86f977c2e615fbfe8e823c3b00c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/icons/icon.png"
|
||||
dest_files=[ "res://.import/icon.png-aa34c86f977c2e615fbfe8e823c3b00c.stex" ]
|
||||
dest_files=["res://.godot/imported/icon.png-aa34c86f977c2e615fbfe8e823c3b00c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/clone.png-04ad708db9ea2b00606e50fe99f4ca29.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3gx4phcox58s"
|
||||
path="res://.godot/imported/clone.png-04ad708db9ea2b00606e50fe99f4ca29.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/layers/clone.png"
|
||||
dest_files=[ "res://.import/clone.png-04ad708db9ea2b00606e50fe99f4ca29.stex" ]
|
||||
dest_files=["res://.godot/imported/clone.png-04ad708db9ea2b00606e50fe99f4ca29.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/delete.png-78cc7779425d13dd019b2b6b51aca5b2.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://x2k652y15v04"
|
||||
path="res://.godot/imported/delete.png-78cc7779425d13dd019b2b6b51aca5b2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/layers/delete.png"
|
||||
dest_files=[ "res://.import/delete.png-78cc7779425d13dd019b2b6b51aca5b2.stex" ]
|
||||
dest_files=["res://.godot/imported/delete.png-78cc7779425d13dd019b2b6b51aca5b2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/group_collapsed.png-9d08fac1c2f635c754860111d024aa0f.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d11kh3e3nq1l5"
|
||||
path="res://.godot/imported/group_collapsed.png-9d08fac1c2f635c754860111d024aa0f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/layers/group_collapsed.png"
|
||||
dest_files=[ "res://.import/group_collapsed.png-9d08fac1c2f635c754860111d024aa0f.stex" ]
|
||||
dest_files=["res://.godot/imported/group_collapsed.png-9d08fac1c2f635c754860111d024aa0f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/group_expanded.png-f3cd620185a4989737d6d9a36f4b57f3.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dndlglvqc7v6a"
|
||||
path="res://.godot/imported/group_expanded.png-f3cd620185a4989737d6d9a36f4b57f3.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/layers/group_expanded.png"
|
||||
dest_files=[ "res://.import/group_expanded.png-f3cd620185a4989737d6d9a36f4b57f3.stex" ]
|
||||
dest_files=["res://.godot/imported/group_expanded.png-f3cd620185a4989737d6d9a36f4b57f3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/group_new.png-4ebdc7dd84d8c8a7b7979f50f4471543.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cmgnc2emsbxni"
|
||||
path="res://.godot/imported/group_new.png-4ebdc7dd84d8c8a7b7979f50f4471543.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/layers/group_new.png"
|
||||
dest_files=[ "res://.import/group_new.png-4ebdc7dd84d8c8a7b7979f50f4471543.stex" ]
|
||||
dest_files=["res://.godot/imported/group_new.png-4ebdc7dd84d8c8a7b7979f50f4471543.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=false
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/layer_invisible.png-b8a35d26d7a70c1d9e228e24fcffb0d2.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dwqdm6pllnipj"
|
||||
path="res://.godot/imported/layer_invisible.png-b8a35d26d7a70c1d9e228e24fcffb0d2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/layers/layer_invisible.png"
|
||||
dest_files=[ "res://.import/layer_invisible.png-b8a35d26d7a70c1d9e228e24fcffb0d2.stex" ]
|
||||
dest_files=["res://.godot/imported/layer_invisible.png-b8a35d26d7a70c1d9e228e24fcffb0d2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/layer_visible.png-28b5ac4566a2e156d8e7d4caecc5f889.stex"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2b3htff5yox8"
|
||||
path="res://.godot/imported/layer_visible.png-28b5ac4566a2e156d8e7d4caecc5f889.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
@ -10,26 +11,24 @@ metadata={
|
|||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/layers/layer_visible.png"
|
||||
dest_files=[ "res://.import/layer_visible.png-28b5ac4566a2e156d8e7d4caecc5f889.stex" ]
|
||||
dest_files=["res://.godot/imported/layer_visible.png-28b5ac4566a2e156d8e7d4caecc5f889.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=false
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
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/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|