This commit is contained in:
Rowan 2025-06-17 01:26:30 -04:00
parent c56b89db84
commit 282b6377f5
98 changed files with 9039 additions and 44 deletions

201
godot/LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatableControl.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="45.254834"
inkscape:cx="6.6843688"
inkscape:cy="8.0322911"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect92"
style="fill:#8eef97;stroke-width:1.77191;stroke-linecap:round;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="M 8.0820312 1.5 L 8.0820312 8.0605469 L 14.642578 8.0605469 L 14.642578 1.5 L 8.0820312 1.5 z M 7.6875 1.8945312 L 1.3574219 14.5 L 14.25 8.4550781 L 7.6875 8.4550781 L 7.6875 1.8945312 z " />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b61f2s0sdeivn"
path="res://.godot/imported/AnimatableControl.svg-ea6d8910d2b3058bfac46c0758163c04.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg"
dest_files=["res://.godot/imported/AnimatableControl.svg-ea6d8910d2b3058bfac46c0758163c04.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

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatableMount.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="6.703125"
inkscape:cy="8.265625"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="path76"
style="color:#000000;fill:#8eef97;stroke-linecap:square;stroke-miterlimit:3.9;stroke-dasharray:18.0028, 18.0028;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 0 0 L 0 10.75 L 1.5 10.75 L 1.5 1.5 L 10.251953 1.5 L 10.251953 0 L 0 0 z M 8 3 L 8 8 L 13 8 L 13 3 L 8 3 z M 7.6992188 3.3007812 L 2.875 12.90625 L 12.699219 8.3007812 L 7.6992188 8.3007812 L 7.6992188 3.3007812 z M 14.5 5.25 L 14.5 14.5 L 5.7460938 14.5 L 5.7460938 16 L 16 16 L 16 5.25 L 14.5 5.25 z " />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://td5qpky5w1jp"
path="res://.godot/imported/AnimatableMount.svg-c55e9d75134e187a953286234a72f1d8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg"
dest_files=["res://.godot/imported/AnimatableMount.svg-c55e9d75134e187a953286234a72f1d8.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

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatableScrollControl.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="12.578125"
inkscape:cy="5.328125"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect92"
style="fill:#8eef97;stroke-width:1.29486;stroke-linecap:round;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="M 5.09375 1 L 3.09375 3 L 7.09375 3 L 5.09375 1 z M 7.09375 3 L 7.09375 7 L 9.09375 5 L 7.09375 3 z M 7.09375 7 L 3.09375 7 L 5.09375 9 L 7.09375 7 z M 3.09375 7 L 3.09375 3 L 1.09375 5 L 3.09375 7 z M 9.8476562 5 L 9.8476562 9.7949219 L 14.642578 9.7949219 L 14.642578 5 L 9.8476562 5 z M 9.5605469 5.2871094 L 4.9335938 14.5 L 14.355469 10.082031 L 9.5605469 10.082031 L 9.5605469 5.2871094 z " />
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d0wty6o4l0n16"
path="res://.godot/imported/AnimatableScrollControl.svg-82ff252057e45e07beda388858928afc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg"
dest_files=["res://.godot/imported/AnimatableScrollControl.svg-82ff252057e45e07beda388858928afc.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

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatableTransformationMount.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="7.828125"
inkscape:cy="10.046875"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="path76"
style="color:#000000;fill:#8eef97;stroke-linecap:square;stroke-miterlimit:3.9;stroke-dasharray:18.0028, 18.0028;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 0 0 L 0 3 L 1.5 3 L 1.5 1.5 L 3 1.5 L 3 0 L 0 0 z M 3 1.5 L 3 2.5 L 8 2.5 L 8 1.5 L 3 1.5 z M 8 1.5 L 10.251953 1.5 L 10.251953 0 L 8 0 L 8 1.5 z M 1.5 3 L 1.5 8 L 2.5 8 L 2.5 3 L 1.5 3 z M 1.5 8 L 0 8 L 0 10.75 L 1.5 10.75 L 1.5 8 z M 8 3 L 8 8 L 13 8 L 13 3 L 8 3 z M 7.6992188 3.3007812 L 2.875 12.90625 L 12.699219 8.3007812 L 7.6992188 8.3007812 L 7.6992188 3.3007812 z M 14.5 5.25 L 14.5 8 L 16 8 L 16 5.25 L 14.5 5.25 z M 14.5 8 L 13.5 8 L 13.5 13 L 14.5 13 L 14.5 8 z M 14.5 13 L 14.5 14.5 L 13 14.5 L 13 16 L 16 16 L 16 13 L 14.5 13 z M 13 14.5 L 13 13.5 L 8 13.5 L 8 14.5 L 13 14.5 z M 8 14.5 L 5.7460938 14.5 L 5.7460938 16 L 8 16 L 8 14.5 z " />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c05srll0xjnus"
path="res://.godot/imported/AnimatableTransformationMount.svg-3384cfe8cf7ff650173fe5f295bf32bd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg"
dest_files=["res://.godot/imported/AnimatableTransformationMount.svg-3384cfe8cf7ff650173fe5f295bf32bd.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

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatableVisibleControl.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="12.578125"
inkscape:cy="5.515625"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect92"
style="fill:#8eef97;stroke-width:1.29486;stroke-linecap:round;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="M 4.7441406 0.83203125 C 3.1236476 0.83203125 1.075573 2.0662754 0.33789062 4.4472656 A 0.63374836 0.63374836 0 0 0 0.33789062 4.796875 C 1.0464205 7.3039809 3.1597713 8.4375 4.7441406 8.4375 C 6.3285098 8.4375 8.4432838 7.3030246 9.15625 4.8085938 A 0.63374836 0.63374836 0 0 0 9.15625 4.4609375 C 8.4591276 2.0590336 6.3627323 0.83203125 4.7441406 0.83203125 z M 4.7441406 2.0996094 A 2.5351562 2.5351562 0 0 1 4.7441406 7.1699219 A 2.5351562 2.5351562 0 0 1 4.7441406 2.0996094 z M 4.7441406 3.3671875 A 1.2675781 1.2675781 0 0 0 4.7441406 5.9023438 A 1.2675781 1.2675781 0 0 0 4.7441406 3.3671875 z M 9.8476562 5 L 9.8476562 9.7949219 L 14.642578 9.7949219 L 14.642578 5 L 9.8476562 5 z M 9.5605469 5.2871094 L 4.9335938 14.5 L 14.355469 10.082031 L 9.5605469 10.082031 L 9.5605469 5.2871094 z " />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://3bug5vfuq1vw"
path="res://.godot/imported/AnimatableVisibleControl.svg-231049b2351dc86fdfb539faef83c988.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg"
dest_files=["res://.godot/imported/AnimatableVisibleControl.svg-231049b2351dc86fdfb539faef83c988.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

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatablePercentControl.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="5.484375"
inkscape:cy="9.140625"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect92-2"
style="fill:#8eef97;stroke-width:1.29486;stroke-linecap:round;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="M 6.1191406 1.578125 L 2.6601562 7.5664062 L 3.2597656 7.9121094 L 6.7167969 1.9238281 L 6.1191406 1.578125 z M 2.3964844 1.9824219 A 1.3831828 1.3831828 0 0 0 1.0957031 2.6699219 A 1.3831828 1.3831828 0 0 0 1.6015625 4.5605469 A 1.3831828 1.3831828 0 0 0 3.4902344 4.0527344 A 1.3831828 1.3831828 0 0 0 2.984375 2.1640625 A 1.3831828 1.3831828 0 0 0 2.3964844 1.9824219 z M 2.2382812 2.8457031 A 0.51869356 0.51869356 0 0 1 2.5527344 2.9121094 A 0.51869356 0.51869356 0 0 1 2.7421875 3.6210938 A 0.51869356 0.51869356 0 0 1 2.0332031 3.8105469 A 0.51869356 0.51869356 0 0 1 1.84375 3.1035156 A 0.51869356 0.51869356 0 0 1 2.2382812 2.8457031 z M 7.1875 4.75 A 1.3831828 1.3831828 0 0 0 5.8867188 5.4375 A 1.3831828 1.3831828 0 0 0 6.3925781 7.3261719 A 1.3831828 1.3831828 0 0 0 8.2832031 6.8203125 A 1.3831828 1.3831828 0 0 0 7.7753906 4.9296875 A 1.3831828 1.3831828 0 0 0 7.1875 4.75 z M 9.8476562 5 L 9.8476562 9.7949219 L 14.642578 9.7949219 L 14.642578 5 L 9.8476562 5 z M 9.5605469 5.2871094 L 4.9335938 14.5 L 14.355469 10.082031 L 9.5605469 10.082031 L 9.5605469 5.2871094 z M 7.03125 5.6132812 A 0.51869356 0.51869356 0 0 1 7.34375 5.6796875 A 0.51869356 0.51869356 0 0 1 7.5332031 6.3886719 A 0.51869356 0.51869356 0 0 1 6.8261719 6.578125 A 0.51869356 0.51869356 0 0 1 6.6347656 5.8691406 A 0.51869356 0.51869356 0 0 1 7.03125 5.6132812 z " />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cice0m617g5ks"
path="res://.godot/imported/AnimatableZoneControl.svg-52fb2c5380b72522d734b8b9ba5fc752.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg"
dest_files=["res://.godot/imported/AnimatableZoneControl.svg-52fb2c5380b72522d734b8b9ba5fc752.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

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatedSwitch.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="3.421875"
inkscape:cy="6.890625"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g2" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)">
<path
id="path1"
style="fill:#8eef97;fill-opacity:1;stroke-width:16.2328"
d="M 39.682511,-18.797691 V 9.4829989 H -45.190974 V 37.795393 H 39.682511 V 66.076082 L 82.135106,23.623343 Z m -69.908866,93.370662 a 64.931228,64.931449 0 0 0 -64.931228,64.931449 64.931228,64.931449 0 0 0 64.931228,64.93145 H 67.170487 A 64.931228,64.931449 0 0 0 132.10172,139.50442 64.931228,64.931449 0 0 0 67.170487,74.572971 Z m 0,32.465719 h 41.247816 a 64.931228,64.931449 0 0 0 -8.7822021,32.46573 64.931228,64.931449 0 0 0 8.7822021,32.46572 h -41.247816 a 32.465614,32.465724 0 0 1 -32.465614,-32.46572 32.465614,32.465724 0 0 1 32.465614,-32.46573 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b40v8yi30eall"
path="res://.godot/imported/AnimatedSwitch.svg-76f991124fb01f03d12bfe5e3a3c31ba.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg"
dest_files=["res://.godot/imported/AnimatedSwitch.svg-76f991124fb01f03d12bfe5e3a3c31ba.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

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="BoundsCheck.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="45.254834"
inkscape:cx="5.8225824"
inkscape:cy="8.4300387"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<path
id="path2-5"
style="fill:#8eef97;fill-opacity:1;stroke-width:0.0595858;stroke-dasharray:none"
d="m 8.709677,2.538 4.663522,3.678581 c 0.06308,0.04997 0.10036,0.09344 0.117877,0.132606 0.01032,0.02266 0.01132,0.04335 0.0049,0.05894 -0.01003,0.02543 -0.03511,0.04883 -0.07122,0.07367 0,0 -0.181159,0.08803 -0.306971,0.105593 -0.0024,-1.62e-4 -0.0056,-4.87e-4 -0.0074,0 l -1.458736,0.201365 c -0.02172,0.003 -0.04117,0.0091 -0.05894,0.01965 -0.03464,0.02055 -0.05989,0.05477 -0.07122,0.09331 -0.0061,0.02076 -0.0081,0.04104 -0.0049,0.06385 0.0024,0.01737 0.0074,0.03438 0.01474,0.04911 h -0.0025 l 0.86689,1.775442 c 0.002,0.0049 -1.63e-4,0.01209 -0.0025,0.01965 -0.0036,0.0093 -0.0085,0.01921 -0.01719,0.02455 l -0.928285,0.454296 h -0.0025 c -0.0027,8.85e-4 -0.0049,0.0036 -0.0074,0.0049 -0.0084,0.0024 -0.01831,5.69e-4 -0.02701,-0.0025 -0.0075,-0.0023 -0.01151,-0.0049 -0.01474,-0.0098 L 10.522049,7.510741 c -0.0096,-0.01979 -0.02367,-0.03804 -0.03929,-0.05157 -0.01563,-0.01353 -0.03228,-0.02293 -0.05158,-0.02947 -0.01929,-0.0065 -0.04077,-0.01101 -0.06139,-0.0098 -0.02063,0.0012 -0.04175,0.0075 -0.06139,0.01719 -0.01537,0.0079 -0.02968,0.01783 -0.04175,0.02947 L 9.210663,8.500392 c -0.04546,0.04468 -0.0919,0.08214 -0.137524,0.112961 -0.04568,0.03035 -0.09044,0.05501 -0.132612,0.06876 -0.04125,0.01322 -0.07696,0.01753 -0.103142,0.0098 -0.0082,-0.0023 -0.01483,-0.0082 -0.02211,-0.01473 -0.0073,-0.0065 -0.01385,-0.01378 -0.01965,-0.02456 -0.01002,-0.01848 -0.01852,-0.04172 -0.02455,-0.07121 -0.006,-0.02949 -0.0096,-0.06523 -0.0098,-0.105594 z m 2.584862,7.088728 h -0.0018 c -0.0026,8.69e-4 -0.0069,0.0052 -0.0069,0.0052 0,0 -0.0179,0.0011 -0.02599,-0.0017 -0.007,-0.0021 -0.01086,-0.0058 -0.01387,-0.0104 L 10.433431,7.973832 c -0.009,-0.01841 -0.02185,-0.03592 -0.03638,-0.04851 -0.01454,-0.01259 -0.03057,-0.02164 -0.04852,-0.02772 -0.01795,-0.0061 -0.03798,-0.0098 -0.05717,-0.0087 -0.01919,0.0011 -0.0389,0.0066 -0.05717,0.01559 -0.01431,0.0073 -0.02689,0.01689 -0.03811,0.02772 L 8.85868,9.248366 c -0.0423,0.04157 -0.08577,0.07701 -0.128214,0.105685 -0.04249,0.02823 -0.08378,0.05131 -0.123014,0.06411 -0.03838,0.0123 -0.07267,0.01583 -0.09703,0.0087 -0.0075,-0.0021 -0.01229,-0.0078 -0.01906,-0.01386 -0.0067,-0.006 -0.01367,-0.0125 -0.01905,-0.02252 -0.0093,-0.01719 -0.01692,-0.04013 -0.02252,-0.06757 -0.0056,-0.02744 -0.0084,-0.05947 -0.0087,-0.09702 -0.02008,-1.96641 -0.02692,-3.9331044 -0.05891,-5.8992921 0,0 -0.06482,-0.00247 -0.08144,-0.00173 -0.01662,7.312e-4 -0.03366,0.00254 -0.05025,0.00693 -0.01678,0.00327 -0.03348,0.00903 -0.04851,0.019058 -0.02004,0.012481 -0.03529,0.029237 -0.05025,0.046778 -0.0072,0.00277 -0.01483,0.010827 -0.02945,0.04158 C 6.7916131,6.238333 2.5,13.462 2.5,13.462 l 8.895029,-3.556333 c 0.0067,-0.0023 0.01335,-0.004 0.01906,-0.0069 l 0.589087,-0.28933 0.28588,-0.140337 c 0.0034,-0.0014 0.0075,-0.0014 0.01039,-0.0035 0.07571,-0.04178 0.129792,-0.112781 0.155937,-0.188847 0.02684,-0.07823 -0.01733,-0.206172 -0.01733,-0.206172 0,0 -0.762799,0.369992 -1.143522,0.556148 z M 2.2558594,0.77539062 C 1.8121817,0.77222361 1.5771259,0.83320546 1.2539062,0.91210938 l -0.056641,0.0351562 -0.078125,0.0585938 -0.072266,0.066406 -0.0644531,0.072266 -0.0585938,0.078125 -0.0507812,0.083984 -0.0429688,0.089844 -0.0351562,0.09375 L 0.7695305,1.5878904 0.7539055,1.6894529 0.75,1.7792969 V 4.6835938 5.1972656 H 1.7773438 V 4.6835938 1.8046875 1.8027344 H 5.875 6.3886719 V 0.77539062 H 5.875 Z m 5.1601562,0 V 1.8027344 h 0.5136719 4.1074215 0.513672 V 0.77539062 H 12.037109 7.9296875 Z m 6.1621094,0 V 1.8027344 h 0.511719 0.130859 0.002 v 0.00195 3.1621094 0.5136719 H 15.25 v -0.5136719 -3.1875 L 15.2461,1.6894498 15.23047,1.5878873 15.20508,1.4902313 15.16992,1.3964813 15.12695,1.3066373 15.07617,1.2226533 15.01758,1.1445283 14.95313,1.0722623 14.88086,1.0058563 14.80273,0.9472625 14.71875,0.8964813 14.62891,0.8535125 14.53516,0.8203094 14.4375,0.7949188 14.335938,0.7792938 14.246098,0.7753878 h -0.15625 z M 0.75,6.2246094 v 0.5136718 4.1074218 0.513672 H 1.7773438 V 10.845703 6.7382812 6.2246094 Z m 13.472656,0.2832031 v 0.5136719 4.1074216 0.513672 H 15.25 V 11.128906 7.0214844 6.5078125 Z M 0.75,12.386719 v 0.511719 1.347656 l 0.003906,0.08984 0.015625,0.101562 0.0253906,0.09766 0.0351562,0.09375 0.0429688,0.08984 0.0507812,0.08398 0.0585938,0.07813 0.0644531,0.07227 0.072266,0.06445 0.078125,0.05859 0.083984,0.05078 0.089844,0.04297 0.09375,0.03516 0.097656,0.02539 0.1015625,0.01563 0.089844,0.0039 H 3.75 4.2636719 v -1.02734 H 3.75 1.7792969 1.7773438 v -0.002 -1.322265 -0.511719 z m 13.472656,0.283203 v 0.513672 1.037109 0.002 h -0.002 -2.253906 -0.513672 V 15.25 h 0.513672 2.279297 l 0.08984,-0.0039 0.101562,-0.01563 0.09766,-0.02539 0.09375,-0.03516 0.08984,-0.04297 0.08398,-0.05078 0.07813,-0.05859 0.07227,-0.06445 0.06445,-0.07227 0.05859,-0.07813 0.05078,-0.08398 0.04297,-0.08984 0.03516,-0.09375 0.02539,-0.09766 0.01563,-0.101562 0.0039,-0.08984 v -1.0625 -0.513672 z M 5.2910156,14.222656 V 15.25 H 5.8046875 9.9121094 10.425781 V 14.222656 H 9.9121094 5.8046875 Z" />
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bladvsiu7rha1"
path="res://.godot/imported/BoundsCheck.svg-02b170ef6b912468a1f094b439993793.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg"
dest_files=["res://.godot/imported/BoundsCheck.svg-02b170ef6b912468a1f094b439993793.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

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="Carousel.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="45.254834"
inkscape:cx="6.2755727"
inkscape:cy="7.5461552"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect1"
style="fill:#8eef97;stroke-width:1.00983;stroke-linecap:round;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="M 6.5878906,3.25 C 6.2619664,3.25 6,3.5053007 6,3.8222656 V 6.3339844 C 5.4677874,6.4073837 4.9651034,6.5094218 4.5,6.6386719 V 6.2050781 C 4.467678,6.1981224 4.0475257,6.1077072 3.9277344,5.9472656 3.7899997,5.7627915 3.8365665,5.2825145 3.8398438,5.25 H 1.0878906 C 0.7619663,5.25 0.5,5.5053007 0.5,5.8222656 V 12.177734 C 0.5,12.494699 0.7619663,12.75 1.0878906,12.75 H 3.9121094 C 4.2380335,12.75 4.5,12.494699 4.5,12.177734 V 8.2050781 C 4.933584,8.0597122 5.440452,7.9372271 6,7.8496094 V 10.177734 C 6,10.494699 6.2619664,10.75 6.5878906,10.75 H 9.4121094 C 9.738034,10.75 10,10.494699 10,10.177734 V 7.8496094 c 0.559262,0.087319 1.066077,0.2086506 1.5,0.3535156 v 3.974609 c 0,0.316965 0.261966,0.572266 0.587891,0.572266 h 2.824218 C 15.238034,12.75 15.5,12.494699 15.5,12.177734 V 6.2050781 C 15.46768,6.1981181 15.047526,6.1077071 14.927734,5.9472656 14.790001,5.7627915 14.836564,5.2825145 14.839844,5.25 H 12.087891 C 11.761966,5.25 11.5,5.5053007 11.5,5.8222656 v 0.8125 C 11.034934,6.5061128 10.531913,6.4049131 10,6.3320312 V 4.2050781 C 9.967678,4.1981224 9.547526,4.1077072 9.4277344,3.9472656 9.2899996,3.7627915 9.3365668,3.2825145 9.3398438,3.25 Z M 9.34375,3.25 10,4.1992188 V 3.8222656 C 10,3.5053007 9.738034,3.25 9.4121094,3.25 Z m -5.5,2 L 4.5,6.1992188 V 5.8222656 C 4.5,5.5053007 4.2380335,5.25 3.9121094,5.25 Z m 11,0 L 15.5,6.1992188 V 5.8222656 C 15.5,5.5053007 15.238034,5.25 14.912109,5.25 Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://vu8og3rtsflq"
path="res://.godot/imported/Carousel.svg-24a12fa029ec5088ae1cb278064dea40.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/Carousel.svg"
dest_files=["res://.godot/imported/Carousel.svg-24a12fa029ec5088ae1cb278064dea40.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

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="AnimatableMount.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="9.765625"
inkscape:cy="6.140625"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"></tspan></text>
<path
id="path4"
style="color:#000000;fill:#8eef97;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:markers fill stroke"
d="M 8 0 A 2 2 0 0 0 6.0136719 1.8125 C 5.1236507 2.0994171 4.3162059 2.5728088 3.6386719 3.1875 A 2 2 0 0 0 1.0722656 4 A 2 2 0 0 0 1.6484375 6.6269531 C 1.5524645 7.0701586 1.5 7.5287197 1.5 8 C 1.5 8.4712803 1.5524645 8.9298414 1.6484375 9.3730469 A 2 2 0 0 0 1.0722656 12 A 2 2 0 0 0 3.6386719 12.8125 C 4.3162059 13.427191 5.1236507 13.900583 6.0136719 14.1875 A 2 2 0 0 0 8 16 A 2 2 0 0 0 9.9863281 14.1875 C 10.876349 13.900583 11.683794 13.427191 12.361328 12.8125 A 2 2 0 0 0 14.927734 12 A 2 2 0 0 0 14.353516 9.3691406 C 14.448906 8.9272161 14.5 8.4698082 14.5 8 C 14.5 7.5301919 14.448906 7.0727838 14.353516 6.6308594 A 2 2 0 0 0 14.927734 4 A 2 2 0 0 0 12.361328 3.1875 C 11.684844 2.5737618 10.878619 2.1015051 9.9902344 1.8144531 A 2 2 0 0 0 8 0 z M 6.4414062 3.2480469 A 2 2 0 0 0 8 4 A 2 2 0 0 0 9.5585938 3.2480469 C 10.224874 3.465094 10.828645 3.8154837 11.337891 4.2714844 A 2 2 0 0 0 11.464844 6 A 2 2 0 0 0 12.894531 6.9726562 C 12.963279 7.3042234 13 7.6474435 13 8 C 13 8.3518923 12.963027 8.6944022 12.894531 9.0253906 A 2 2 0 0 0 11.464844 10 A 2 2 0 0 0 11.337891 11.728516 C 10.829544 12.183711 10.227385 12.534811 9.5625 12.751953 A 2 2 0 0 0 8 12 A 2 2 0 0 0 6.4414062 12.751953 C 5.7760209 12.535198 5.1728779 12.185579 4.6640625 11.730469 A 2 2 0 0 0 4.5351562 10 A 2 2 0 0 0 3.1054688 9.0273438 C 3.0367209 8.6957766 3 8.3525566 3 8 C 3 7.6481077 3.0369732 7.3055978 3.1054688 6.9746094 A 2 2 0 0 0 4.5351562 6 A 2 2 0 0 0 4.6640625 4.2695312 C 5.1728779 3.8144207 5.7760209 3.4648024 6.4414062 3.2480469 z " />
<g
id="g7" />
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c00ttqalyh2ck"
path="res://.godot/imported/CircularContainer.svg-1dd941875614a9abaf152e4d2a3a686b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/CircularContainer.svg"
dest_files=["res://.godot/imported/CircularContainer.svg-1dd941875614a9abaf152e4d2a3a686b.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

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="DistanceCheck.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="7.515625"
inkscape:cy="7.171875"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<path
id="path2"
style="fill:#8eef97;fill-opacity:1;stroke-width:0.0412133;stroke-dasharray:none"
d="M 8 0.5 C 3.8639067 0.5 0.5 3.8639067 0.5 8 C 0.49999994 12.136094 3.8639067 15.5 8 15.5 C 12.136094 15.5 15.5 12.136094 15.5 8 C 15.5 3.8639067 12.136094 0.49999994 8 0.5 z M 8 1.5195312 C 11.585459 1.5195311 14.480469 4.4145407 14.480469 8 C 14.480469 11.585459 11.585459 14.480469 8 14.480469 C 6.3969877 14.480469 4.9327997 13.901831 3.8027344 12.941406 L 11.394531 9.90625 C 11.401231 9.90395 11.408353 9.9013375 11.414062 9.8984375 L 12.003906 9.609375 L 12.289062 9.46875 C 12.292462 9.46735 12.295938 9.4669437 12.298828 9.4648438 C 12.374538 9.4230638 12.428933 9.3534097 12.455078 9.2773438 C 12.481918 9.1991138 12.4375 9.0703125 12.4375 9.0703125 C 12.4375 9.0703125 11.675644 9.4407963 11.294922 9.6269531 L 11.292969 9.6269531 C 11.290369 9.6278251 11.285156 9.6328125 11.285156 9.6328125 C 11.285156 9.6328125 11.267856 9.6336594 11.259766 9.6308594 C 11.252766 9.6287594 11.249104 9.6237406 11.246094 9.6191406 L 10.433594 7.9746094 C 10.424594 7.9561994 10.411014 7.9383712 10.396484 7.9257812 C 10.381944 7.9131913 10.365606 7.9045175 10.347656 7.8984375 C 10.329706 7.8923375 10.310206 7.8875719 10.291016 7.8886719 C 10.271826 7.8897719 10.252645 7.8953069 10.234375 7.9042969 C 10.220065 7.9115969 10.206532 7.9208106 10.195312 7.9316406 L 8.859375 9.2480469 C 8.817075 9.2896168 8.7729127 9.3248407 8.7304688 9.3535156 C 8.6879788 9.3817456 8.6466558 9.4051688 8.6074219 9.4179688 C 8.5690419 9.4302687 8.5341256 9.4348644 8.5097656 9.4277344 C 8.5022656 9.4256344 8.4989575 9.4181694 8.4921875 9.4121094 C 8.4854875 9.4061094 8.4780362 9.400645 8.4726562 9.390625 C 8.4633563 9.373435 8.4548187 9.3497056 8.4492188 9.3222656 C 8.4436188 9.2948257 8.4417062 9.2641125 8.4414062 9.2265625 C 8.4213263 7.260155 8.4148025 5.2923576 8.3828125 3.3261719 C 8.3828125 3.3261719 8.3174012 3.3234788 8.3007812 3.3242188 C 8.2841613 3.3249538 8.26659 3.3276413 8.25 3.3320312 C 8.23322 3.3353012 8.2162019 3.3415345 8.2011719 3.3515625 C 8.1811319 3.3640435 8.1673037 3.3808965 8.1523438 3.3984375 C 8.1451438 3.4012075 8.1376669 3.4087002 8.1230469 3.4394531 C 7.1026661 5.5858654 4.4022755 10.22464 3.1699219 12.322266 C 2.1439516 11.176153 1.5195312 9.6632327 1.5195312 8 C 1.5195312 4.4145407 4.4145407 1.5195313 8 1.5195312 z M 8.7089844 2.5371094 L 8.7617188 8.4765625 C 8.7619187 8.5169265 8.7654844 8.5525413 8.7714844 8.5820312 C 8.7775144 8.6115212 8.7849019 8.6338638 8.7949219 8.6523438 C 8.8007219 8.6631237 8.8071531 8.6712344 8.8144531 8.6777344 C 8.8217331 8.6842644 8.8296906 8.6891063 8.8378906 8.6914062 C 8.8640726 8.6991362 8.9001563 8.6948606 8.9414062 8.6816406 C 8.9835782 8.6678906 9.0265857 8.6436312 9.0722656 8.6132812 C 9.1178896 8.5824603 9.1654775 8.54468 9.2109375 8.5 L 10.267578 7.4667969 C 10.279648 7.4551539 10.293224 7.44536 10.308594 7.4375 C 10.328234 7.427861 10.348511 7.4211119 10.369141 7.4199219 C 10.389761 7.4187329 10.412351 7.4231475 10.431641 7.4296875 C 10.450941 7.4362275 10.466792 7.4454534 10.482422 7.4589844 C 10.498042 7.4725134 10.511884 7.4899756 10.521484 7.5097656 L 11.396484 9.28125 C 11.399714 9.28615 11.402656 9.2887156 11.410156 9.2910156 C 11.418856 9.2940926 11.4291 9.2953687 11.4375 9.2929688 C 11.44 9.2916688 11.442613 9.2899495 11.445312 9.2890625 L 11.447266 9.2890625 L 12.376953 8.8339844 C 12.385643 8.8286444 12.388978 8.8198469 12.392578 8.8105469 C 12.394919 8.8029869 12.398484 8.7959156 12.396484 8.7910156 L 11.529297 7.015625 L 11.53125 7.015625 C 11.52391 7.000887 11.518025 6.9822137 11.515625 6.9648438 C 11.512425 6.9420398 11.515384 6.9231047 11.521484 6.9023438 C 11.532814 6.8638018 11.557157 6.8291457 11.591797 6.8085938 C 11.609567 6.7980488 11.630624 6.7920325 11.652344 6.7890625 L 13.111328 6.5878906 C 13.113128 6.5874222 13.114788 6.5877392 13.117188 6.5878906 C 13.242998 6.5703231 13.423828 6.4824219 13.423828 6.4824219 C 13.459938 6.4575769 13.486064 6.4336311 13.496094 6.4082031 C 13.502514 6.3926161 13.500554 6.3722664 13.490234 6.3496094 C 13.472717 6.3104426 13.436127 6.2667718 13.373047 6.2167969 L 8.7089844 2.5371094 z " />
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://den188fn44ahq"
path="res://.godot/imported/DistanceCheck.svg-cc9439b9bfdf47f875ceab8615f27948.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg"
dest_files=["res://.godot/imported/DistanceCheck.svg-cc9439b9bfdf47f875ceab8615f27948.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

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="Drawer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="9.640625"
inkscape:cy="6.421875"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect2"
style="fill:#8eef97;stroke-width:11.0382;stroke-linecap:round;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="m 9.032595,1.75 c -0.5540498,-1e-7 -0.9991024,0.4491454 -0.9991024,1.0058593 V 3.296875 H 10.607057 V 3.8925781 H 8.0334926 V 6.9492186 H 5.9128288 4.9798145 v -0.5 l -2.5813397,1.5 2.5813397,1.5000004 v -0.5 h 0.9330143 2.1206638 v 4.296874 C 8.0334925,13.802809 8.4785452,14.25 9.032595,14.25 H 9.885915 13.998953 15 V 13.246093 2.7558593 1.75 H 13.998953 9.885915 Z M 4.9798145,7.6992186 h 0.9330143 2.1206638 v 0.5 H 5.9128288 4.9798145 Z"
sodipodi:nodetypes="sscccccccccccccssccccccccsccccccc" />
<g
id="path4"
transform="matrix(0.99521529,0,0,0.99999998,-0.23534689,0.2695312)">
<g
id="g13">
<g
id="path13">
<path
style="color:#000000;fill:#8eef97;fill-rule:evenodd;-inkscape-stroke:none"
d="M 5.4414062,5.8320312 2.2460938,7.6796875 5.4414062,9.5253906 Z M 5.0410156,6.5253906 V 8.8320313 L 3.0449219,7.6796875 Z"
id="path15" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://lje075l441hl"
path="res://.godot/imported/Drawer.svg-7020a44fe2fbf3bc7e577f6647e28243.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/Drawer.svg"
dest_files=["res://.godot/imported/Drawer.svg-7020a44fe2fbf3bc7e577f6647e28243.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

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="HoldButton.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="7.578125"
inkscape:cy="11.109375"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g2" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)">
<path
id="path1-9"
style="fill:#8eef97;fill-opacity:1;stroke:none;stroke-width:16.2328;stroke-opacity:1"
d="M 2.2392589,7.2640805 V 58.118343 L -21.22222,42.456248 -39.23049,69.468613 9.4679308,101.93418 c 5.4548142,3.63938 12.5534562,3.63938 18.0082702,0 L 76.174622,69.468613 58.166352,42.456248 34.704873,58.118343 V 7.2640805 Z M -46.7128,129.2953 c -6.063399,0 -10.938122,4.87556 -10.938122,10.9381 v 5.70684 h -14.267115 c -3.878694,0 -7.006739,3.12855 -7.006739,7.00672 v 18.42042 7.00672 H 115.86891 v -24.06383 -1.36331 c 0,-3.87817 -3.12803,-7.00672 -7.00674,-7.00672 H 94.595054 v -5.70684 c 0,-6.06254 -4.874718,-10.9381 -10.938122,-10.9381 h -1.648645 v 9.89184 a 63.536855,19.02034 0 0 1 -4.280134,6.7531 H -40.784021 a 63.536855,19.02034 0 0 1 -4.280134,-6.7531 v -9.89184 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ceqkp35yu0v27"
path="res://.godot/imported/HoldButton.svg-059d8d0ebedf00f7d7ee555447e9ac85.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/HoldButton.svg"
dest_files=["res://.godot/imported/HoldButton.svg-059d8d0ebedf00f7d7ee555447e9ac85.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

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="MaxRatioContainer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="7.140625"
inkscape:cy="6.953125"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="path76"
style="color:#000000;fill:#8eef97;stroke-linecap:square;stroke-miterlimit:3.9;stroke-dasharray:18.0028, 18.0028;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 0 0 L 0 10.75 L 1.5 10.75 L 1.5 2.9277344 L 7.7480469 9.1757812 L 3.8886719 9.1757812 L 3.8886719 10.589844 L 10.589844 10.589844 L 10.589844 3.8886719 L 9.1757812 3.8886719 L 9.1757812 7.7480469 L 2.9277344 1.5 L 10.251953 1.5 L 10.251953 0 L 0 0 z M 15 9.75 L 15 14.310547 L 12.126953 11.4375 L 13.992188 11.4375 L 13.992188 10.753906 L 10.753906 10.753906 L 10.753906 13.992188 L 11.4375 13.992188 L 11.4375 12.126953 L 14.310547 15 L 9.75 15 L 9.75 16 L 16 16 L 16 9.75 L 15 9.75 z " />
<g
id="path78">
<g
id="g89" />
</g>
<g
id="path79">
<g
id="g91" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://crwd74vrtbrmm"
path="res://.godot/imported/MaxRatioContainer.svg-69252897895331776d794bb3e7d4450a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg"
dest_files=["res://.godot/imported/MaxRatioContainer.svg-69252897895331776d794bb3e7d4450a.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="MaxSizeContainer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="5.578125"
inkscape:cy="8.046875"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="path78">
<g
id="g85" />
</g>
<g
id="path79">
<g
id="g87">
<path
id="path87"
style="color:#000000;fill:#8eef97;-inkscape-stroke:none"
d="M 8.0664063,8.0664063 V 13.046875 H 9.1171875 V 9.1171875 H 13.046875 V 8.0664063 Z M 9,8.25 a 0.75,0.75 0 0 0 -0.53125,0.21875 0.75,0.75 0 0 0 0,1.0625 l 6,6 a 0.75,0.75 0 0 0 1.0625,0 0.75,0.75 0 0 0 0,-1.0625 l -6,-6 A 0.75,0.75 0 0 0 9,8.25 Z m 5.5,-3 V 14.5 H 5.7480469 V 16 H 16 V 5.25 Z M 6.8828125,2.953125 V 6.8828125 H 2.953125 V 7.9335937 H 7.9335937 V 2.953125 Z M 1,0.25 a 0.75,0.75 0 0 0 -0.53125,0.21875 0.75,0.75 0 0 0 0,1.0625 l 6,6 a 0.75,0.75 0 0 0 1.0625,0 0.75,0.75 0 0 0 0,-1.0625 l -6,-6 A 0.75,0.75 0 0 0 1,0.25 Z M 0,0 V 10.75 H 1.5 V 1.5 h 8.751953 V 0 Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cj5vpwdi8b2t4"
path="res://.godot/imported/MaxSizeContainer.svg-c449fe736ac19e4da4954b352d7a4bc0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg"
dest_files=["res://.godot/imported/MaxSizeContainer.svg-c449fe736ac19e4da4954b352d7a4bc0.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="ModulateTransitionButton.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="22.627417"
inkscape:cx="5.9883105"
inkscape:cy="11.159029"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<g
id="g4"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)"
style="fill:#8eef97;fill-opacity:1">
<path
id="path1"
style="stroke-width:12.3099"
d="m -39.797083,0.42818857 c -6.641652,0 -11.973645,5.33175813 -11.973645,11.97305343 V 132.61263 c 0,6.64128 5.331993,11.97305 11.973645,11.97305 h 21.951681 v 6.90014 H -42.465907 V 176.1051 H 105.25712 v -24.61928 h -24.6205 v -6.90014 h 21.95168 c 6.64166,0 11.97364,-5.33177 11.97364,-11.97305 V 12.401242 c 0,-6.6412953 -5.33198,-11.97305343 -11.97364,-11.97305343 z M -25.924005,17.065444 H 88.715224 c 5.10896,0 9.20865,4.123546 9.20865,9.232234 v 92.442552 c 0,5.10869 -4.09969,9.2082 -9.20865,9.2082 H 79.891272 C 77.682194,120.22478 70.604751,114.55688 62.171241,114.55688 H 0.61997721 c -8.43351751,0 -15.51095321,5.6679 -17.72003221,13.39155 h -8.82395 c -5.108962,0 -9.208647,-4.09951 -9.208647,-9.2082 V 26.297678 c 0,-5.108688 4.099685,-9.232234 9.208647,-9.232234 z M -12.050928,33.7027 c -3.57628,0 -6.467691,2.867249 -6.467691,6.44333 v 6.515457 L -4.6455408,33.7027 Z m 23.634724,0 -30.102415,28.177548 V 79.911956 L 30.842609,33.7027 Z m 35.464107,0 -65.566522,61.403971 v 9.761169 c 0,2.03366 0.943408,3.8193 2.404347,5.00079 L 65.393065,33.7027 Z M 79.506577,35.698209 -1.3996738,111.33521 H 16.729097 L 81.309837,50.844843 V 40.14603 c 0,-1.737697 -0.693978,-3.289675 -1.80326,-4.447821 z m 1.80326,30.365396 -48.327359,45.271605 H 51.135291 L 81.309837,83.061495 Z m 0,32.192609 -13.969252,13.078996 h 7.50156 c 3.576279,0 6.467692,-2.89129 6.467692,-6.46737 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bf8ifq48pj2iv"
path="res://.godot/imported/ModulateTransitionButton.svg-124cc46ae542378e7b346986eb52d0c7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg"
dest_files=["res://.godot/imported/ModulateTransitionButton.svg-124cc46ae542378e7b346986eb52d0c7.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="ModulateTransitionContainer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="6.75"
inkscape:cy="10.203125"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<g
id="g4"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)"
style="fill:#8eef97;fill-opacity:1">
<path
id="rect7"
style="stroke-width:3.05337;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="m -16.834572,45.181424 c -3.969758,0 -7.164803,3.195084 -7.164803,7.164842 v 7.236971 L -8.6118792,45.181424 Z m 26.2308716,0 -33.3956746,31.280064 v 20.027896 l 54.769868,-51.30796 z m 39.3823724,0 -72.778047,68.162166 v 10.84344 c 0,2.25755 1.047085,4.2424 2.668769,5.55395 L 69.143061,45.181424 Z M 84.79503,47.393389 -5.0054349,131.35187 H 15.118525 L 86.790596,64.223553 V 52.346266 c 0,-1.928993 -0.764236,-3.667234 -1.995566,-4.952877 z m 1.995566,33.708415 -53.63985,50.250066 H 53.298749 L 86.790596,99.975632 Z m 0,35.752076 -15.483668,14.49799 h 8.318865 c 3.969759,0 7.164803,-3.19509 7.164803,-7.16484 z M -47.633607,8.2512339 c -7.372408,0 -13.295758,5.9234221 -13.295758,13.2958301 V 154.98623 c 0,7.3724 5.92335,13.29583 13.295758,13.29583 H 110.42483 c 7.37241,0 13.29576,-5.92343 13.29576,-13.29583 V 21.547064 c 0,-7.372408 -5.92335,-13.2958301 -13.29576,-13.2958301 z M -32.222068,26.716329 H 95.013289 c 5.671081,0 10.242301,4.571275 10.242301,10.242357 V 139.57461 c 0,5.67108 -4.57122,10.24235 -10.242301,10.24235 H -32.222068 c -5.671083,0 -10.242302,-4.57127 -10.242302,-10.24235 V 36.958686 c 0,-5.671082 4.571219,-10.242357 10.242302,-10.242357 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://s3hp67f43n0x"
path="res://.godot/imported/ModulateTransitionContainer.svg-41f98f4655621d9ed04421a4b13baee4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg"
dest_files=["res://.godot/imported/ModulateTransitionContainer.svg-41f98f4655621d9ed04421a4b13baee4.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="MotionCheck.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="45.254834"
inkscape:cx="6.8169513"
inkscape:cy="4.5409514"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<g
id="g4"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)"
style="fill:#8eef97;fill-opacity:1">
<path
id="path2-5"
style="fill:#8eef97;fill-opacity:1;stroke-width:0.733503;stroke-dasharray:none"
d="m 40.131733,21.02908 57.407945,45.283565 c 0.77651,0.615195 1.23543,1.15024 1.45106,1.632386 0.12704,0.278909 0.13935,0.533628 0.0603,0.725505 -0.12347,0.313023 -0.4322,0.601043 -0.87672,0.906883 0,0 -2.23007,1.08361 -3.77881,1.29986 -0.0295,-0.002 -0.0689,-0.006 -0.0911,0 l -17.95703,2.47881 c -0.26738,0.0366 -0.50681,0.11202 -0.72555,0.24183 -0.42642,0.253 -0.73725,0.67426 -0.87672,1.14871 -0.0751,0.25557 -0.0997,0.50523 -0.0603,0.78595 0.0295,0.21383 0.0911,0.42316 0.18145,0.60459 h -0.0308 l 10.67141,21.8558 c 0.0246,0.0603 -0.002,0.14883 -0.0308,0.2419 -0.0443,0.11448 -0.10463,0.23647 -0.21161,0.30221 l -11.42718,5.592411 h -0.0308 c -0.0332,0.0109 -0.0603,0.0443 -0.0911,0.0603 -0.10341,0.0295 -0.2254,0.007 -0.3325,-0.0308 -0.0923,-0.0283 -0.14169,-0.0603 -0.18145,-0.12064 L 62.44194,82.243729 c -0.118176,-0.24362 -0.291377,-0.46828 -0.483659,-0.63482 -0.192406,-0.16657 -0.397367,-0.28226 -0.63495,-0.36277 -0.23746,-0.0805 -0.501879,-0.13552 -0.755711,-0.12088 -0.253955,0.0147 -0.513942,0.0929 -0.75571,0.2116 -0.189205,0.0968 -0.365361,0.21942 -0.513943,0.36275 l -12.999184,12.72652 c -0.559613,0.55002 -1.131289,1.01115 -1.69292,1.39056 -0.562321,0.37361 -1.113316,0.67718 -1.632454,0.84644 -0.507787,0.16274 -0.947377,0.2158 -1.269677,0.12064 -0.100942,-0.0283 -0.182558,-0.10094 -0.272174,-0.18133 -0.08986,-0.08 -0.170494,-0.16963 -0.241892,-0.30233 -0.123346,-0.22749 -0.227981,-0.51358 -0.30221,-0.8766 -0.07386,-0.36303 -0.118176,-0.80299 -0.120638,-1.29987 z m 31.819645,87.26269 h -0.0222 c -0.032,0.0107 -0.0849,0.064 -0.0849,0.064 0,0 -0.22035,0.0135 -0.31994,-0.0209 -0.0862,-0.0258 -0.13368,-0.0714 -0.17074,-0.12802 L 61.351139,87.944539 c -0.11079,-0.22663 -0.268973,-0.44218 -0.447837,-0.59716 -0.178988,-0.15498 -0.376317,-0.26639 -0.597281,-0.34123 -0.220965,-0.0751 -0.467534,-0.12064 -0.703763,-0.1071 -0.236229,0.0135 -0.478859,0.0812 -0.703762,0.19191 -0.176157,0.0899 -0.331016,0.20792 -0.469134,0.34124 L 41.965959,103.63414 c -0.520713,0.51172 -1.055828,0.94799 -1.578314,1.30099 -0.523052,0.34751 -1.031331,0.63162 -1.514302,0.78919 -0.472457,0.15142 -0.894567,0.19487 -1.194439,0.1071 -0.09232,-0.0259 -0.15129,-0.096 -0.234628,-0.17062 -0.08248,-0.0739 -0.168278,-0.15387 -0.234506,-0.27722 -0.114483,-0.21161 -0.208285,-0.494 -0.277221,-0.83179 -0.06894,-0.33779 -0.103404,-0.73208 -0.107097,-1.19432 -0.247185,-24.206631 -0.331385,-48.416768 -0.725182,-72.620663 0,0 -0.797934,-0.03041 -1.002526,-0.0213 -0.204592,0.009 -0.414354,0.03127 -0.618577,0.08531 -0.206562,0.04025 -0.412139,0.11116 -0.597158,0.234605 -0.246693,0.153642 -0.43442,0.359909 -0.618578,0.57584 -0.08863,0.0341 -0.182557,0.133281 -0.362529,0.511853 C 16.520372,66.58041 -36.309372,155.50421 -36.309372,155.50421 l 109.49778,-43.77868 c 0.0825,-0.0283 0.16434,-0.0492 0.23463,-0.0849 l 7.25165,-3.56168 3.51919,-1.72755 c 0.0419,-0.0172 0.0923,-0.0172 0.1279,-0.0431 0.93199,-0.51431 1.59774,-1.38834 1.91958,-2.32472 0.33041,-0.96301 -0.21333,-2.53799 -0.21333,-2.53799 0,0 -9.39005,4.55462 -14.07675,6.84622 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://jywj8x8u5pop"
path="res://.godot/imported/MotionCheck.svg-5facd6c8a5eea77e7e6606c52a8d456d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/MotionCheck.svg"
dest_files=["res://.godot/imported/MotionCheck.svg-5facd6c8a5eea77e7e6606c52a8d456d.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

View file

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="PaddingContainer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="1.421875"
inkscape:cy="7.765625"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect35"
style="color:#000000;fill:#8eef97;stroke-width:0.5;stroke-linecap:round;stroke-miterlimit:3.9;stroke-dasharray:none;paint-order:stroke fill markers"
d="M 1,1 V 15 H 15 V 1 Z M 2,2 H 4.9697265 7.9394531 L 7.1601562,2.7792969 V 3.3398438 L 8,2.5 8.8398438,3.3398438 V 2.7792969 L 8.0605469,2 H 14 V 7.9394531 L 13.220703,7.1601562 H 12.660156 L 13.5,8 12.660156,8.8398438 h 0.560547 L 14,8.0605469 V 14 H 2 V 8.0605469 L 2.7792969,8.8398438 H 3.3398438 L 2.5,8 3.3398438,7.1601562 H 2.7792969 L 2,7.9394531 Z m 2,2 v 8 h 3.8007812 v 1.5 0.199219 H 8.1992188 V 13.5 12 H 12 V 4 Z m 1,1 h 6 v 6 H 5 Z"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccc" />
<g
id="path36">
<g
id="g2" />
</g>
<g
id="path3">
<path
style="color:#000000;fill:#8eef97;stroke-linecap:square;stroke-miterlimit:3.9;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 2.3007813,7.8007812 V 8.1992188 H 2.5 4 4.1992187 V 7.8007812 H 4 2.5 Z"
id="path11" />
<g
id="g10">
<path
style="color:#000000;fill:#8eef97;fill-rule:evenodd;-inkscape-stroke:none"
d="M 1.94,8 2.78,7.16 H 3.34 L 2.5,8 3.34,8.84 H 2.78 Z"
id="path10" />
</g>
</g>
<g
id="path6">
<path
style="color:#000000;fill:#8eef97;stroke-linecap:square;stroke-miterlimit:3.9;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 2.3007813,7.8007812 V 8.1992188 H 2.5 4 4.1992187 V 7.8007812 H 4 2.5 Z"
id="path13" />
<g
id="g12" />
</g>
<g
id="path7">
<path
style="color:#000000;fill:#8eef97;stroke-linecap:square;stroke-miterlimit:3.9;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 11.800781,7.8007812 V 8.1992188 H 12 13.5 13.699219 V 7.8007812 H 13.5 12 Z"
id="path15" />
<g
id="g14" />
</g>
<g
id="path8">
<path
style="color:#000000;fill:#8eef97;stroke-linecap:square;stroke-miterlimit:3.9;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 7.8007812,2.3007813 V 2.5 4 4.1992187 H 8.1992188 V 4 2.5 2.3007813 Z"
id="path17" />
<g
id="g16" />
</g>
<g
id="path9">
<g
id="g18">
<path
style="color:#000000;fill:#8eef97;fill-rule:evenodd;-inkscape-stroke:none"
d="M 8,14.06 7.16,13.22 V 12.66 L 8,13.5 8.84,12.66 v 0.56 z"
id="path18" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c6o6frklk2afj"
path="res://.godot/imported/PaddingContainer.svg-9d29b77cc431f8e4258d6655803790fe.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg"
dest_files=["res://.godot/imported/PaddingContainer.svg-9d29b77cc431f8e4258d6655803790fe.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

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="Page.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="22.627417"
inkscape:cx="0.99436891"
inkscape:cy="8.1980192"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g2" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)">
<path
id="rect11"
style="color:#000000;fill:#8eef97;fill-opacity:1;stroke-width:0.959576;stroke-miterlimit:3.9;-inkscape-stroke:none;paint-order:stroke fill markers"
d="m -43.178001,-22.076718 c -12.938746,0 -23.588403,11.565574 -23.588403,25.6169459 V 182.09795 c 0,14.05138 10.649657,25.61695 23.588403,25.61695 H 80.122133 c 12.93874,0 23.588397,-3.3225 23.588397,-17.37388 V 28.269459 L 52.475512,-22.076718 H 48.86116 36.306042 34.150113 Z m 0,16.2325202 H 36.306042 V 42.536322 H 88.777563 V 182.09795 c 0,5.33935 -3.73886,9.38442 -8.65543,9.38442 H -43.178001 c -4.916566,0 -8.655422,-4.04507 -8.655422,-9.38442 V 3.5402279 c 0,-5.33935 3.738856,-9.3844257 8.655422,-9.3844257 z M 48.86116,-4.6394401 85.543663,29.981481 H 48.86116 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cqtdlpg3h0j5d"
path="res://.godot/imported/Page.svg-d8188a8e86ac732bd815acd1e14bc1cd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/Page.svg"
dest_files=["res://.godot/imported/Page.svg-d8188a8e86ac732bd815acd1e14bc1cd.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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M1.553 4.104A1 1 0 0 0 1 5v6a1 1 0 0 0 .553.895l6 3a1 1 0 0 0 .894 0l6-3A1 1 0 0 0 15 11V5a1 1 0 0 0-.553-.894l-6-3a1 1 0 0 0-.894 0zm6.447-1 3.764 1.882L8 6.868 4.236 4.986zm-5 3.5 4 2v3.766l-4-2z"/></svg>

After

Width:  |  Height:  |  Size: 294 B

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2wlsev3wn5rq"
path="res://.godot/imported/PageInfo.svg-cac5d98635bfc001a76d9044e0d773df.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/PageInfo.svg"
dest_files=["res://.godot/imported/PageInfo.svg-cac5d98635bfc001a76d9044e0d773df.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

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="ProportionalContainer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="3.734375"
inkscape:cy="6.046875"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<path
id="rect35"
style="color:#000000;fill:#8eef97;stroke-linecap:round;stroke-miterlimit:3.9;-inkscape-stroke:none;paint-order:stroke fill markers"
d="M 1 1 L 1 6 L 1 15 L 10 15 L 15 15 L 15 1 L 1 1 z M 2.234375 2.234375 L 12.988281 2.234375 L 11.111328 4.1113281 L 11.111328 2.5546875 L 10.333984 3.3339844 L 10.333984 5.6660156 L 12.666016 5.6660156 L 13.445312 4.8886719 L 11.888672 4.8886719 L 13.765625 3.0117188 L 13.765625 13.765625 L 10 13.765625 L 10 6 L 2.234375 6 L 2.234375 2.234375 z M 2.234375 7 L 9 7 L 9 13.765625 L 2.234375 13.765625 L 2.234375 7 z " />
<g
id="path36">
<g
id="g2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d08yq8cd27f3f"
path="res://.godot/imported/ProportionalContainer.svg-71fab0e839b225c75a1402a08ebf61b4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg"
dest_files=["res://.godot/imported/ProportionalContainer.svg-71fab0e839b225c75a1402a08ebf61b4.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

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="RouterStack.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="7.015625"
inkscape:cy="7.078125"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g2" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)">
<path
id="rect13"
style="fill:#8eef97;stroke-width:9.24964;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="m -57.302171,10.03989 c -2.980772,0 -5.389802,2.377268 -5.389802,5.35798 v 41.183523 c 0,2.98071 2.40903,5.38969 5.389802,5.38969 H 94.246302 c 2.98077,0 5.3898,-2.40898 5.3898,-5.38969 V 15.39787 c 0,-2.980712 -2.40903,-5.35798 -5.3898,-5.35798 z m 8.116404,56.813603 c -2.980775,0 -5.389799,2.37728 -5.389799,5.35799 v 41.183507 c 0,2.98072 2.409024,5.38969 5.389799,5.38969 H 102.3627 c 2.98078,0 5.3898,-2.40897 5.3898,-5.38969 V 72.211483 c 0,-2.98071 -2.40902,-5.35799 -5.3898,-5.35799 z M -65.418573,123.6671 c -2.98078,0 -5.3898,2.37726 -5.3898,5.35798 v 41.18352 c 0,2.98071 2.40902,5.38969 5.3898,5.38969 H 86.129902 c 2.98077,0 5.3898,-2.40898 5.3898,-5.38969 v -41.18352 c 0,-2.98072 -2.40903,-5.35798 -5.3898,-5.35798 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bx72t0x80xoc"
path="res://.godot/imported/RouterStack.svg-943b2eec7eb910602fd6b012fee2113d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/RouterStack.svg"
dest_files=["res://.godot/imported/RouterStack.svg-943b2eec7eb910602fd6b012fee2113d.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="StyleTransitionButton.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="22.627417"
inkscape:cx="5.9883105"
inkscape:cy="11.159029"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<g
id="g4"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)"
style="fill:#8eef97;fill-opacity:1">
<path
id="rect7-8"
style="fill:#8eef97;fill-opacity:1;stroke-width:2.56756;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="m -35.057656,3.61034 c -6.199321,0 -11.179728,4.980619 -11.179728,11.180114 V 127.00033 c 0,6.1995 4.980407,11.18012 11.179728,11.18012 h 17.214376 v 10.12221 h -24.619443 v 24.62029 H 105.25394 V 148.30266 H 80.634491 v -10.12221 h 17.21438 c 6.199329,0 11.179729,-4.98062 11.179729,-11.18012 V 14.790454 c 0,-6.199495 -4.9804,-11.180114 -11.179729,-11.180114 z m 12.958867,15.531944 h 106.98879 c 4.76871,0 8.60719,3.838633 8.60719,8.607486 v 86.29125 c 0,4.76883 -3.83848,8.63153 -8.60719,8.63153 h -5.69805 C 76.393813,116.03406 69.826169,111.37221 62.169913,111.37221 H 0.62130275 c -7.65626305,0 -14.22389875,4.66185 -17.02203675,11.30034 h -5.698055 c -4.768707,0 -8.607189,-3.8627 -8.607189,-8.63153 V 27.74977 c 0,-4.768853 3.838482,-8.607486 8.607189,-8.607486 z m 12.9348247,15.531944 c -3.3380987,0 -6.0106057,2.696662 -6.0106057,6.034857 v 60.396655 c 0,3.33821 2.672507,6.03487 6.0106057,6.03487 H 71.955181 c 3.338096,0 6.010606,-2.69666 6.010606,-6.03487 V 40.709085 c 0,-3.338195 -2.67251,-6.034857 -6.010606,-6.034857 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://beombqir8gvsg"
path="res://.godot/imported/StyleTransitionButton.svg-31239487ac3b145d1d200758ae441fa7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg"
dest_files=["res://.godot/imported/StyleTransitionButton.svg-31239487ac3b145d1d200758ae441fa7.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="StyleTransitionContainer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="6.75"
inkscape:cy="10.203125"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<g
id="g4"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)"
style="fill:#8eef97;fill-opacity:1">
<path
id="rect7"
style="stroke-width:3.05337;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="M -16.833739,45.181423 H 79.62496 c 3.969763,0 7.165636,3.195874 7.165636,7.165636 v 71.839171 c 0,3.96976 -3.195873,7.16563 -7.165636,7.16563 h -96.458699 c -3.969762,0 -7.165635,-3.19587 -7.165635,-7.16563 V 52.347059 c 0,-3.969762 3.195873,-7.165636 7.165635,-7.165636 z M -47.633607,8.2512339 c -7.372408,0 -13.295758,5.9234221 -13.295758,13.2958301 V 154.98623 c 0,7.3724 5.92335,13.29583 13.295758,13.29583 H 110.42483 c 7.37241,0 13.29576,-5.92343 13.29576,-13.29583 V 21.547064 c 0,-7.372408 -5.92335,-13.2958301 -13.29576,-13.2958301 z M -32.222068,26.716329 H 95.013289 c 5.671081,0 10.242301,4.571275 10.242301,10.242357 V 139.57461 c 0,5.67108 -4.57122,10.24235 -10.242301,10.24235 H -32.222068 c -5.671083,0 -10.242302,-4.57127 -10.242302,-10.24235 V 36.958686 c 0,-5.671082 4.571219,-10.242357 10.242302,-10.242357 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c6dj30vpi468i"
path="res://.godot/imported/StyleTransitionContainer.svg-d423c826296d72d4b9686de202d9c396.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg"
dest_files=["res://.godot/imported/StyleTransitionContainer.svg-d423c826296d72d4b9686de202d9c396.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="StyleTransitionPanel.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="6.75"
inkscape:cy="10.203125"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<g
id="g4"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)"
style="fill:#8eef97;fill-opacity:1">
<path
id="rect7"
style="stroke-width:18.46504659;stroke-miterlimit:3.9;paint-order:stroke fill markers;stroke:none;stroke-opacity:1;stroke-dasharray:73.86018637, 18.46504659;stroke-dashoffset:0;fill:#8eef97;fill-opacity:1"
d="M -16.833739,45.181423 H 79.62496 c 3.969763,0 7.165636,3.195874 7.165636,7.165636 v 71.839171 c 0,3.96976 -3.195873,7.16563 -7.165636,7.16563 h -96.458699 c -3.969762,0 -7.165635,-3.19587 -7.165635,-7.16563 V 52.347059 c 0,-3.969762 3.195873,-7.165636 7.165635,-7.165636 z M -47.633607,8.2512339 c -7.372401,0 -13.295758,5.9234291 -13.295758,13.2958301 v 17.479328 h 18.464995 v -2.067706 c 0,-5.325214 4.045379,-9.634738 9.232497,-10.146185 V 8.2512339 Z m 32.866729,0 V 26.716329 H 3.6981175 V 8.2512339 Z m 36.929991,0 V 26.716329 H 40.628108 V 8.2512339 Z m 36.92999,0 V 26.716329 H 77.558098 V 8.2512339 Z m 36.92999,0 V 26.812501 c 5.187117,0.511447 9.232497,4.820971 9.232497,10.146185 v 2.067706 h 18.465 V 21.547064 c 0,-7.372401 -5.92336,-13.2958301 -13.29576,-13.2958301 z M -60.929365,57.491488 V 75.956583 H -42.46437 V 57.491488 Z m 166.184955,0 v 18.465095 h 18.465 V 57.491488 Z M -60.929365,94.421678 V 112.88677 H -42.46437 V 94.421678 Z m 166.184955,0 v 18.465092 h 18.465 V 94.421678 Z M -60.929365,131.35187 v 23.63436 c 0,7.37239 5.923357,13.29583 13.295758,13.29583 h 14.401734 v -18.56127 c -5.187118,-0.51145 -9.232497,-4.82097 -9.232497,-10.14618 v -8.22274 z m 166.184955,0 v 8.22274 c 0,5.32521 -4.04538,9.63473 -9.232497,10.14618 v 18.56127 h 14.401737 c 7.3724,0 13.29576,-5.92344 13.29576,-13.29583 v -23.63436 z m -120.022468,18.46509 v 18.4651 H 3.6981175 v -18.4651 z m 36.929991,0 v 18.4651 h 18.464995 v -18.4651 z m 36.92999,0 v 18.4651 h 18.464995 v -18.4651 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cbu1p6if3bhsq"
path="res://.godot/imported/StyleTransitionPanel.svg-6e7803726b3f0f97dacbb20fcb7a611c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg"
dest_files=["res://.godot/imported/StyleTransitionPanel.svg-6e7803726b3f0f97dacbb20fcb7a611c.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

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
id="svg1"
sodipodi:docname="SwapContainer.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="32"
inkscape:cx="7.765625"
inkscape:cy="4.921875"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<text
xml:space="preserve"
style="font-size:13.3333px;font-family:Cambria;-inkscape-font-specification:Cambria;fill:#8654ce;stroke-width:5.66929;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="4.4930115"
y="10.958069"
id="text1"><tspan
sodipodi:role="line"
id="tspan1" /></text>
<g
id="g7" />
<g
id="g2"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)" />
<g
id="g3"
transform="matrix(0.06160364,0,0,0.06160364,6.8620535,2.2820062)" />
<g
id="g4"
transform="matrix(0.08123479,0,0,0.08123435,5.4495842,0.82971638)"
style="fill:#8eef97;fill-opacity:1">
<path
id="rect1"
style="fill:#8eef97;stroke-width:5.42346;stroke-miterlimit:3.9;paint-order:stroke fill markers"
d="m 33.251393,6.2631586 c -17.890975,0 -34.14793266,4.0726514 -46.278476,10.9608624 -12.130545,6.888208 -20.529549,17.013486 -20.529549,28.938994 h -7.423114 l 12.033563,12.004754 11.801592,-12.004754 h -7.336124 c 0,-7.601178 5.543377,-15.143597 15.9480956,-21.051813 10.4047192,-5.908217 25.2873394,-9.771985 41.7840124,-9.771985 16.496674,0 31.379295,3.863768 41.784014,9.771985 10.404717,5.908216 15.948095,13.450635 15.948095,21.051813 h 9.075908 c 0,-11.925508 -8.398998,-22.050786 -20.529541,-28.938994 C 67.399325,10.33581 51.142371,6.2631586 33.251393,6.2631586 Z M -57.420784,61.24145 c -1.94727,0 -3.508581,1.561347 -3.508581,3.508637 V 166.76149 c 0,1.9473 1.561311,3.50864 3.508581,3.50864 H 0.05035604 c 1.94726956,0 3.50858196,-1.56134 3.50858196,-3.50864 V 64.750087 c 0,-1.94729 -1.5613124,-3.508637 -3.50858196,-3.508637 z m 120.161658,0 c -1.947269,0 -3.50858,1.561347 -3.50858,3.508637 V 166.76149 c 0,1.9473 1.561311,3.50864 3.50858,3.50864 h 57.471136 c 1.94727,0 3.50857,-1.56134 3.50857,-3.50864 V 64.750087 c 0,-1.94729 -1.5613,-3.508637 -3.50857,-3.508637 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b6ebfwbec45wl"
path="res://.godot/imported/SwapContainer.svg-2f0195c9a4f5afd5ec3524f1349104ed.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/FreeControl/assets/icons/CustomType/SwapContainer.svg"
dest_files=["res://.godot/imported/SwapContainer.svg-2f0195c9a4f5afd5ec3524f1349104ed.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

View file

@ -0,0 +1,258 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
extends EditorPlugin
const GLOBAL_FOLDER := "res://addons/FreeControl/src/Other/Global/"
const CUSTOM_CLASS_FOLDER := "res://addons/FreeControl/src/CustomClasses/"
const ICON_FOLDER := "res://addons/FreeControl/assets/icons/CustomType/"
func _enter_tree() -> void:
# AnimatableControls
# Control
add_custom_type(
"AnimatableControl",
"Container",
load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableControl.gd"),
load(ICON_FOLDER + "AnimatableControl.svg")
)
add_custom_type(
"AnimatableScrollControl",
"Container",
load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableScrollControl.gd"),
load(ICON_FOLDER + "AnimatableScrollControl.svg")
)
add_custom_type(
"AnimatableZoneControl",
"Container",
load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableZoneControl.gd"),
load(ICON_FOLDER + "AnimatableZoneControl.svg")
)
add_custom_type(
"AnimatableVisibleControl",
"Container",
load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableVisibleControl.gd"),
load(ICON_FOLDER + "AnimatableVisibleControl.svg")
)
# Mount
add_custom_type(
"AnimatableMount",
"Control",
load(CUSTOM_CLASS_FOLDER + "AnimatableControl/mount/AnimatableMount.gd"),
load(ICON_FOLDER + "AnimatableMount.svg")
)
add_custom_type(
"AnimatableTransformationMount",
"Control",
load(CUSTOM_CLASS_FOLDER + "AnimatableControl/mount/AnimatableTransformationMount.gd"),
load(ICON_FOLDER + "AnimatableTransformationMount.svg")
)
# Buttons
# Base
add_custom_type(
"AnimatedSwitch",
"BaseButton",
load(CUSTOM_CLASS_FOLDER + "Buttons/Base/AnimatedSwitch.gd"),
load(ICON_FOLDER + "AnimatedSwitch.svg")
)
add_custom_type(
"HoldButton",
"BaseButton",
load(CUSTOM_CLASS_FOLDER + "Buttons/Base/HoldButton.gd"),
load(ICON_FOLDER + "HoldButton.svg")
)
# MotionCheck
add_custom_type(
"BoundsCheck",
"Control",
load(CUSTOM_CLASS_FOLDER + "Buttons/Base/MotionCheck/BoundsCheck.gd"),
load(ICON_FOLDER + "BoundsCheck.svg")
)
add_custom_type(
"DistanceCheck",
"Control",
load(CUSTOM_CLASS_FOLDER + "Buttons/Base/MotionCheck/DistanceCheck.gd"),
load(ICON_FOLDER + "DistanceCheck.svg")
)
add_custom_type(
"MotionCheck",
"Control",
load(CUSTOM_CLASS_FOLDER + "Buttons/Base/MotionCheck/MotionCheck.gd"),
load(ICON_FOLDER + "MotionCheck.svg")
)
# Complex
add_custom_type(
"ModulateTransitionButton",
"Container",
load(CUSTOM_CLASS_FOLDER + "Buttons/Complex/ModulateTransitionButton.gd"),
load(ICON_FOLDER + "ModulateTransitionButton.svg")
)
add_custom_type(
"StyleTransitionButton",
"Container",
load(CUSTOM_CLASS_FOLDER + "Buttons/Complex/StyleTransitionButton.gd"),
load(ICON_FOLDER + "StyleTransitionButton.svg")
)
# Carousel
add_custom_type(
"Carousel",
"Container",
load(CUSTOM_CLASS_FOLDER + "Carousel/Carousel.gd"),
load(ICON_FOLDER + "Carousel.svg")
)
# CircularContainer
add_custom_type(
"CircularContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "CircularContainer/CircularContainer.gd"),
load(ICON_FOLDER + "CircularContainer.svg")
)
# Drawer
add_custom_type(
"Drawer",
"Container",
load(CUSTOM_CLASS_FOLDER + "Drawer/Drawer.gd"),
load(ICON_FOLDER + "Drawer.svg")
)
# PaddingContainer
add_custom_type(
"PaddingContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "PaddingContainer/PaddingContainer.gd"),
load(ICON_FOLDER + "PaddingContainer.svg")
)
# ProportionalContainer
add_custom_type(
"ProportionalContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "ProportionalContainer/ProportionalContainer.gd"),
load(ICON_FOLDER + "ProportionalContainer.svg")
)
# Routers
add_custom_type(
"RouterStack",
"Container",
load(CUSTOM_CLASS_FOLDER + "Routers/RouterStack.gd"),
load(ICON_FOLDER + "RouterStack.svg")
)
# Base
add_custom_type(
"Page",
"Container",
load(CUSTOM_CLASS_FOLDER + "Routers/Base/Page.gd"),
load(ICON_FOLDER + "Page.svg")
)
add_custom_type(
"PageInfo",
"Resource",
load(CUSTOM_CLASS_FOLDER + "Routers/Base/PageInfo.gd"),
load(ICON_FOLDER + "PageInfo.svg")
)
# SizeControllers
# MaxSizeContainer
add_custom_type(
"MaxSizeContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "SizeController/MaxSizeContainer.gd"),
load(ICON_FOLDER + "MaxSizeContainer.svg")
)
# MaxRatioContainer
add_custom_type(
"MaxRatioContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "SizeController/MaxRatioContainer.gd"),
load(ICON_FOLDER + "MaxRatioContainer.svg")
)
# SwapContainer
add_custom_type(
"SwapContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "SwapContainer/SwapContainer.gd"),
load(ICON_FOLDER + "SwapContainer.svg")
)
# TransitionContainers
add_custom_type(
"ModulateTransitionContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "TransitionContainers/ModulateTransitionContainer.gd"),
load(ICON_FOLDER + "ModulateTransitionContainer.svg")
)
add_custom_type(
"StyleTransitionContainer",
"Container",
load(CUSTOM_CLASS_FOLDER + "TransitionContainers/StyleTransitionContainer.gd"),
load(ICON_FOLDER + "StyleTransitionContainer.svg")
)
add_custom_type(
"StyleTransitionPanel",
"Container",
load(CUSTOM_CLASS_FOLDER + "TransitionContainers/StyleTransitionPanel.gd"),
load(ICON_FOLDER + "StyleTransitionPanel.svg")
)
func _exit_tree() -> void:
# AnimatableControls
# Control
remove_custom_type("AnimatableControl")
remove_custom_type("AnimatableScrollControl")
remove_custom_type("AnimatableZoneControl")
remove_custom_type("AnimatableVisibleControl")
# Mount
remove_custom_type("AnimatableMount")
remove_custom_type("AnimatableTransformationMount")
# Buttons
# Base
remove_custom_type("AnimatedSwitch")
remove_custom_type("HoldButton")
# MotionCheck
remove_custom_type("BoundsCheck")
remove_custom_type("DistanceCheck")
remove_custom_type("MotionCheck")
# Complex
remove_custom_type("ModulateTransitionButton")
remove_custom_type("StyleTransitionButton")
# Carousel
remove_custom_type("Carousel")
# CircularContainer
remove_custom_type("CircularContainer")
# Drawer
remove_custom_type("Drawer")
# PaddingContainer
remove_custom_type("PaddingContainer")
# ProportionalContainer
remove_custom_type("ProportionalContainer")
# Routers
remove_custom_type("RouterStack")
# Base
remove_custom_type("Page")
remove_custom_type("PageInfo")
# SizeControllers
remove_custom_type("MaxSizeContainer")
remove_custom_type("MaxRatioContainer")
# SwapContainer
remove_custom_type("SwapContainer")
# TransitionContainers
remove_custom_type("ModulateTransitionContainer")
remove_custom_type("StyleTransitionContainer")
remove_custom_type("StyleTransitionPanel")

View file

@ -0,0 +1,7 @@
[plugin]
name="FreeControl"
description="Multiple Control nodes for easier UI manipulation"
author="Xavier Alvarez"
version="1.5.1"
script="main.gd"

View file

@ -0,0 +1,139 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name AnimatableControl extends Container
## A container to be used for free transformation within a UI.
## This signal emits when one of the following properties change: scale, position,
## rotation, pivot_offset
signal transformation_changed
## The size mode this node's size will be bounded by.
enum SIZE_MODE {
NONE = 0b00, ## This node's size is not bounded
MIN = 0b01, ## This node's size will be greater than or equal to this node's mount size
MAX = 0b10, ## This node's size will be less than or equal to this node's mount size
EXACT = 0b11 ## This node's size will be the same as this node's mount size
}
## Controls how this node's size is bounded, according to the node's mount size
@export var size_mode : SIZE_MODE = SIZE_MODE.EXACT:
set(val):
if size_mode != val:
size_mode = val
if _mount:
_mount.update_minimum_size()
_bound_size()
notify_property_list_changed()
## Auto sets the pivot to be at some position percentage of the size.
@export var pivot_ratio : Vector2:
set(val):
if pivot_ratio != val:
pivot_ratio = val
pivot_offset = size * val
var _mount : AnimatableMount
func _get_configuration_warnings() -> PackedStringArray:
if get_parent() is AnimatableMount:
return []
return ["This node only serves to be animatable within a UI. Please only attach as a child to a 'AnimatableMount' node."]
func _validate_property(property: Dictionary) -> void:
if property.name in ["layout_mode", "anchors_preset"]:
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name == "size":
if size_mode == SIZE_MODE.EXACT:
property.usage |= PROPERTY_USAGE_READ_ONLY
func _set(property: StringName, value: Variant) -> bool:
if property in ["scale", "position", "rotation"]:
transformation_changed.emit()
_bound_size()
elif property == "pivot_offset":
if is_node_ready() && size > Vector2.ZERO && pivot_offset != value:
pivot_ratio = pivot_offset / size
transformation_changed.emit()
return false
func _init() -> void:
resized.connect(_handle_resize)
sort_children.connect(_sort_children)
tree_exited.connect(_on_tree_exit)
tree_entered.connect(_on_tree_enter)
item_rect_changed.connect(transformation_changed.emit)
func _on_tree_enter() -> void:
_mount = (get_parent() as AnimatableMount)
if _mount:
_mount._on_mount(self)
func _on_tree_exit() -> void:
if _mount:
_mount._on_unmount(self)
_mount = null
func _handle_resize() -> void:
_bound_size()
_update_pivot()
func _update_pivot() -> void:
set_pivot_offset(pivot_ratio * size)
transformation_changed.emit()
func _sort_children() -> void:
for child : Control in _get_control_children():
_resize_child(child)
func _resize_child(child : Control) -> void:
var child_size := child.get_combined_minimum_size()
var set_pos : Vector2
match child.size_flags_horizontal & ~SIZE_EXPAND:
SIZE_FILL:
set_pos.x = 0
child_size.x = max(child_size.x, size.x)
SIZE_SHRINK_BEGIN:
set_pos.x = 0
SIZE_SHRINK_CENTER:
set_pos.x = (size.x - child_size.x) * 0.5
SIZE_SHRINK_END:
set_pos.x = size.x - child_size.x
match child.size_flags_vertical & ~SIZE_EXPAND:
SIZE_FILL:
set_pos.y = 0
child_size.y = max(child_size.y, size.y)
SIZE_SHRINK_BEGIN:
set_pos.y = 0
SIZE_SHRINK_CENTER:
set_pos.y = (size.y - child_size.y) * 0.5
SIZE_SHRINK_END:
set_pos.y = size.y - child_size.y
fit_child_in_rect(child, Rect2(set_pos, child_size))
func _get_minimum_size() -> Vector2:
if clip_children: return Vector2.ZERO
var min_size := Vector2.ZERO
for child : Control in _get_control_children():
min_size = min_size.max(child.get_combined_minimum_size())
return min_size
func _bound_size() -> void:
if !_mount: return
var new_size : Vector2 = size
if size_mode == SIZE_MODE.MAX:
new_size = _mount.get_relative_size(self).min(size)
elif size_mode == SIZE_MODE.MIN:
new_size = _mount.get_relative_size(self).max(size)
elif size_mode == SIZE_MODE.EXACT:
new_size = _mount.get_relative_size(self)
if new_size != size:
size = new_size
func _get_control_children() -> Array[Control]:
var ret : Array[Control]
ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible))
return ret
## Gets the mount this node is currently a child to.[br]
## If this node is not a child to any [AnimatableMount] nodes, this returns [code]null[/code] instead.
func get_mount() -> AnimatableMount:
return _mount
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,72 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name AnimatableScrollControl extends AnimatableControl
## A container to be used for free transformation, within a UI, depended on a [ScrollContainer]'s scroll progress.
## The [ScrollContainer] this node will consider for operations. Is automatically
## set to the closet parent [ScrollContainer] in the tree if [member scroll] is
## [code]null[/code] and [Engine] is in editor mode.
## [br][br]
## [b]NOTE[/b]: It is recomended that this node's [AnimatableMount] is a child of
## [member scroll].
@export var scroll : ScrollContainer:
set(val):
if scroll != val:
if scroll:
scroll.get_h_scroll_bar().value_changed.disconnect(_scrolled_horizontal)
scroll.get_v_scroll_bar().value_changed.disconnect(_scrolled_vertical)
scroll = val
if val:
val.get_h_scroll_bar().value_changed.connect(_scrolled_horizontal)
val.get_v_scroll_bar().value_changed.connect(_scrolled_vertical)
if is_node_ready():
_scrolled_horizontal(val.get_h_scroll_bar().value)
_scrolled_vertical(val.get_v_scroll_bar().value)
func _enter_tree() -> void:
if !scroll && Engine.is_editor_hint(): scroll = get_parent_scroll()
## A virtual function that is called when [member scroll] is horizontally scrolled.
## [br][br]
## Paramter [param scroll] is the current horizontal progress of the scroll.
func _scrolled_horizontal(scroll_hor : float) -> void: pass
## A virtual function that is called when [member scroll] is vertically scrolled.
## [br][br]
## Paramter [param scroll] is the current vertical progress of the scroll.
func _scrolled_vertical(scroll_ver : float) -> void: pass
## Returns the global difference between this node's [AnimatableMount] and
## [member scroll] positions.
func get_origin_offset() -> Vector2:
if !_mount || !scroll: return Vector2.ZERO
return _mount.global_position - scroll.global_position
## Returns the horizontal and vertical progress of [member scroll].
func get_scroll_offset() -> Vector2:
if !scroll: return Vector2.ZERO
return Vector2(scroll.scroll_horizontal, scroll.scroll_vertical)
## Gets the closet parent [ScrollContainer] in the tree.
func get_parent_scroll() -> ScrollContainer:
var ret : Control = (get_parent() as Control)
while ret != null:
if ret is ScrollContainer: return ret
ret = (ret.get_parent() as Control)
return null
## Returns a percentage of how visible this node's [AnimatableMount] is, within
## the rect of [member scroll].
func is_visible_percent() -> float:
if !_mount || !scroll: return 0
return (_mount.get_global_rect().intersection(scroll.get_global_rect()).get_area()) / (_mount.size.x * _mount.size.y)
## Returns a percentage of how visible this node's [AnimatableMount] is, within the
## horizontal bounds of [member scroll].
func get_visible_horizontal_percent() -> float:
if !_mount || !scroll: return 0
return (min(_mount.global_position.x + _mount.size.x, scroll.global_position.x + scroll.size.x) - max(_mount.global_position.x, scroll.global_position.x)) / _mount.size.x
## Returns a percentage of how visible this node's [AnimatableMount] is, within the
## vertical bounds of [member scroll].
func get_visible_vertical_percent() -> float:
if !_mount || !scroll: return 0
return (min(_mount.global_position.y + _mount.size.y, scroll.global_position.y + scroll.size.y) - max(_mount.global_position.y, scroll.global_position.y)) / _mount.size.y
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,397 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name AnimatableVisibleControl extends AnimatableScrollControl
## A container to be used for free transformation, within a UI, depending on if
## the node is visible in a [ScrollContainer] scroll.
## Emitted when requested threshold has been entered.
signal entered_threshold
## Emitted when requested threshold has been exited.
signal exited_threshold
## Emitted when this node's [AnimatableMount]'s rect entered visible range.
signal entered_screen
## Emitted when this node's [AnimatableMount]'s rect exited visible range.
signal exited_screen
## Modes of threshold type checking.
enum CHECK_MODE {
NONE = 0b000, ## No behavior.
HORIZONTAL = 0b001, ## Only checks horizontally using [member threshold_horizontal].
VERTICAL = 0b010, ## Only checks vertically using [member threshold_vertical].
BOTH = 0b011 ## Checks horizontally and vertically.
}
## Modes of threshold size.
enum THRESHOLD_EDITOR_DIMS {
None = 0b00, ## Both horizontal and vertical axis are based on ratio.
Horizontal = 0b01, ## Horizontal axis is based on exact pixel and vertical on ratio.
Vertical = 0b10, ## Horizontal axis is based on ratio and vertical on exact pixel.
Both = 0b11, ## Both horizontal and vertical axis are based on exact pixel.
}
## Color for inner highlighting - Indicates when visiblity is required to met
## threshold.
const HIGHLIGHT_COLOR := Color(Color.RED, 0.3)
## Color for overlap highlighting - Indicates when visiblity is required, starting
## from the far end, to met threshold.
const ANTI_HIGHLIGHT_COLOR := Color(Color.DARK_CYAN, 1)
## Color for helpful lines to make highlighting for clear.
const INTERSECT_HIGHLIGHT_COLOR := Color(Color.RED, 0.8)
@export_group("Mode")
## Sets the mode of threshold type checking.
@export var check_mode: CHECK_MODE = CHECK_MODE.NONE:
set(val):
if check_mode != val:
check_mode = val
notify_property_list_changed()
queue_redraw()
## A flag variable used to distinguish if the threshold amount is described by
## a ratio of the size of the [member AnimatableScrollControl.scroll] value, or
## by a const pixel value.[br]
## Horizontal and vertical axis are consistered differently.
## [br][br]
## See [enum THRESHOLD_EDITOR_DIMS], [member threshold_horizontal], and [member threshold_vertical].
var threshold_pixel : int:
set(val):
if (threshold_pixel ^ val) & THRESHOLD_EDITOR_DIMS.Horizontal:
_scrolled_horizontal(get_scroll_offset().x)
if (threshold_pixel ^ val) & THRESHOLD_EDITOR_DIMS.Vertical:
_scrolled_vertical(get_scroll_offset().y)
threshold_pixel = val
notify_property_list_changed()
queue_redraw()
## The minimum horizontal percentage this node's [AnimatableMount]'s rect must be
## visible in [member scroll] for this node to be consistered visible.
var threshold_horizontal : float = 0.5:
set(val):
if threshold_horizontal != val:
threshold_horizontal = val
_scrolled_horizontal(0)
queue_redraw()
## The minimum vertical percentage this node's [AnimatableMount]'s rect must be
## visible in [member scroll] for this node to be consistered visible.
var threshold_vertical : float = 0.5:
set(val):
if threshold_vertical != val:
threshold_vertical = val
_scrolled_vertical(0)
queue_redraw()
## [b]Editor usage only.[/b] Shows or hides the helpful threshold highlighter.
var hide_indicator : bool = true:
set(val):
if hide_indicator != val:
hide_indicator = val
queue_redraw()
var _last_threshold_horizontal : float
var _last_threshold_vertical : float
var _last_visible : bool
func _get_property_list() -> Array[Dictionary]:
var ret : Array[Dictionary] = []
var horizontal : int = 0 if check_mode & CHECK_MODE.HORIZONTAL else PROPERTY_USAGE_READ_ONLY
var vertical : int = 0 if check_mode & CHECK_MODE.VERTICAL else PROPERTY_USAGE_READ_ONLY
var either : int = horizontal & vertical
var options : String
if !horizontal: options = "Horizontal:1,"
if !vertical: options += "Vertical:2"
ret.append({
"name": "Threshold",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP,
"hint_string": ""
})
ret.append({
"name": "threshold_pixel",
"type": TYPE_INT,
"hint": PROPERTY_HINT_FLAGS,
"hint_string": options,
"usage": PROPERTY_USAGE_DEFAULT | either
})
ret.append({
"name": "threshold_horizontal",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT | horizontal
}.merged({} if threshold_pixel & 1 else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001"
}))
ret.append({
"name": "threshold_vertical",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT | vertical
}.merged({} if threshold_pixel & 2 else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001"
}))
ret.append({
"name": "Indicator",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP,
"hint_str": ""
})
ret.append({
"name": "hide_indicator",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_DEFAULT
})
return ret
func _property_can_revert(property: StringName) -> bool:
if property == "threshold_pixel":
if self[property] != 0: return true
elif property in ["threshold_horizontal", "threshold_vertical"]:
if self[property] != 0.5: return true
elif property == "hide_indicator":
return !hide_indicator
return false
func _property_get_revert(property: StringName) -> Variant:
if property == "threshold_pixel":
return 0
elif property in ["threshold_horizontal", "threshold_vertical"]:
return 0.5
elif property == "hide_indicator":
return true
return null
func _init() -> void:
item_rect_changed.connect(queue_redraw)
func _get_threshold_size() -> Array[Vector2]:
var ratio_thr : Vector2
var full_thr : Vector2
if is_zero_approx(_mount.size.x):
ratio_thr.x = 1
full_thr.x = _mount.size.x
elif threshold_pixel & THRESHOLD_EDITOR_DIMS.Horizontal:
var hor := clamp(threshold_horizontal, 0, _mount.size.x)
ratio_thr.x = hor / _mount.size.x
full_thr.x = hor
else:
var hor := clamp(threshold_horizontal, 0, 1)
ratio_thr.x = hor
full_thr.x = hor * _mount.size.x
if is_zero_approx(_mount.size.y):
ratio_thr.y = 1
full_thr.y = _mount.size.y
elif threshold_pixel & THRESHOLD_EDITOR_DIMS.Vertical:
var vec := clamp(threshold_vertical, 0, _mount.size.y)
ratio_thr.y = vec / _mount.size.y
full_thr.y = vec
else:
var vec := clamp(threshold_vertical, 0, 1)
ratio_thr.y = vec
full_thr.y = vec * _mount.size.y
return [ratio_thr, full_thr]
func _draw() -> void:
if !_mount || !Engine.is_editor_hint() || hide_indicator: return
var threshold_adjust := _get_threshold_size()
draw_set_transform(-position)
draw_rect(Rect2(Vector2.ZERO, size), Color.CORAL, false)
match check_mode:
CHECK_MODE.HORIZONTAL:
var left := threshold_adjust[1].x
var right := size.x - left
if threshold_adjust[0].x > 0.5:
left = size.x - left
right = size.x - right
_draw_highlight(
left,
0,
right,
size.y,
threshold_adjust[0].x < 0.5
)
CHECK_MODE.VERTICAL:
var top := threshold_adjust[1].y
var bottom := size.y - top
if threshold_adjust[0].y > 0.5:
top = size.y - top
bottom = size.y - bottom
_draw_highlight(
0,
top,
size.x,
bottom,
threshold_adjust[0].y < 0.5
)
CHECK_MODE.BOTH:
var left := threshold_adjust[1].x
var right := size.x - left
var top := threshold_adjust[1].y
var bottom := size.y - top
var draw_middle : bool = true
if threshold_adjust[0].x >= 0.5:
left = size.x - left
right = size.x - right
draw_middle = false
if threshold_adjust[0].y >= 0.5:
top = size.y - top
bottom = size.y - bottom
draw_middle = false
_draw_highlight(
left,
top,
right,
bottom,
draw_middle
)
if !draw_middle:
if threshold_adjust[0].x >= 0.5:
if threshold_adjust[0].y < 0.5:
draw_line(
Vector2(left, top),
Vector2(right, top),
INTERSECT_HIGHLIGHT_COLOR,
5
)
draw_line(
Vector2(left, bottom),
Vector2(right, bottom),
INTERSECT_HIGHLIGHT_COLOR,
5
)
elif threshold_adjust[0].y >= 0.5:
draw_line(
Vector2(left, top),
Vector2(left, bottom),
INTERSECT_HIGHLIGHT_COLOR,
5
)
draw_line(
Vector2(right, top),
Vector2(right, bottom),
INTERSECT_HIGHLIGHT_COLOR,
5
)
func _draw_highlight(
left : float,
top : float,
right : float,
bottom : float,
draw_middle : bool
) -> void:
# Middle
if draw_middle:
draw_rect(Rect2(Vector2(left, top), Vector2(right - left, bottom - top)), HIGHLIGHT_COLOR)
return
# Outer
# Left
draw_rect(Rect2(Vector2(0, 0), Vector2(left, size.y)), ANTI_HIGHLIGHT_COLOR)
# Right
draw_rect(Rect2(Vector2(right, 0), Vector2(size.x - right, size.y)), ANTI_HIGHLIGHT_COLOR)
# Top
draw_rect(Rect2(Vector2(left, 0), Vector2(right - left, top)), ANTI_HIGHLIGHT_COLOR)
# Bottom
draw_rect(Rect2(Vector2(left, bottom), Vector2(right - left, size.y - bottom)), ANTI_HIGHLIGHT_COLOR)
func _scrolled_horizontal(_scroll_hor : float) -> void:
if !(check_mode & CHECK_MODE.HORIZONTAL): return
var threshold_adjust := _get_threshold_size()
var val : float = is_visible_percent()
# Checks if visible
if val > 0:
# If visible, but wasn't visible last scroll, then it entered visible area
if !_last_visible:
entered_screen.emit()
_last_visible = true
# Calls the while function
_while_visible(val)
# Else, if visible last frame, then it exited visible area
elif _last_visible:
_while_visible(0)
exited_screen.emit()
_last_visible = false
val = get_visible_horizontal_percent()
# Checks if in threshold
if val >= threshold_adjust[0].x:
# If in threshold, but not last frame, then it entered threshold area
if _last_threshold_horizontal < threshold_adjust[0].x:
entered_threshold.emit()
# Calls the while function
_while_threshold(val)
# If in threshold, but not last frame, then it entered threshold area
elif _last_threshold_horizontal > threshold_adjust[0].x:
_while_threshold(0)
exited_threshold.emit()
_last_threshold_horizontal = val
func _scrolled_vertical(_scroll_ver : float) -> void:
if !(check_mode & CHECK_MODE.VERTICAL): return
var threshold_adjust := _get_threshold_size()
var val : float = is_visible_percent()
# Checks if visible
if val > 0:
# If visible, but wasn't visible last scroll, then it entered visible area
if !_last_visible:
entered_screen.emit()
_last_visible = true
# Calls the while function
_while_visible(val)
# Else, if visible last frame, then it exited visible area
elif _last_visible:
_while_visible(0)
exited_screen.emit()
_last_visible = false
val = get_visible_vertical_percent()
# Checks if in threshold
if val >= threshold_adjust[0].y:
# If in threshold, but not last frame, then it entered threshold area
if _last_threshold_vertical < threshold_adjust[0].y:
entered_threshold.emit()
# Calls the while function
_while_threshold(val)
# If in threshold, but not last frame, then it entered threshold area
elif _last_threshold_vertical > threshold_adjust[0].y:
_while_threshold(0)
exited_threshold.emit()
_last_threshold_vertical = val
# Public Functions
## Returns the rect [threshold_horizontal] and [threshold_vertical] create.
func get_threshold_rect(consider_mode : bool = false) -> Rect2:
var threshold_adjust := _get_threshold_size()
return Rect2(threshold_adjust[1], size - threshold_adjust[1])
# Virtual Functions
## A virtual function that is called while this node is in the visible area of it's
## scroll. Is called after each scroll of [member scroll].
## [br][br]
## Paramter [param intersect] is the current visible percent.
func _while_visible(intersect : float) -> void: pass
## A virtual function that is called while this node's visible threshold is met. Is
## called after each scroll of [member scroll].
## [br][br]
## Paramter [param intersect] is the current threshold value met.
func _while_threshold(intersect : float) -> void: pass
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,386 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name AnimatableZoneControl extends AnimatableScrollControl
## A container to be used for free transformation, within a UI, depended on a
## [ScrollContainer]'s scroll progress.
## Modes of zone type checking.
enum CHECK_MODE {
NONE = 0b000, ## No behavior.
HORIZONTAL = 0b001, ## Only checks if this node's mount is in the zone horizontally.
VERTICAL = 0b010, ## Only checks if this node's mount is in the zone vertically.
BOTH = 0b011 ## Checks horizontally and vertically.
}
## Modes of zone size and center position.
enum ZONE_EDITOR_DIMS {
None = 0b00, ## Both horizontal and vertical axis are based on ratio.
Horizontal = 0b01, ## Horizontal axis is based on exact pixel and vertical on ratio.
Vertical = 0b10, ## Horizontal axis is based on ratio and vertical on exact pixel.
Both = 0b11, ## Both horizontal and vertical axis are based on exact pixel.
}
## Emitted when this node's [AnimatableMount]'s entered the zone area.
signal entered_zone
## Emitted when this node's [AnimatableMount]'s exited the zone area.
signal exited_zone
## Color for inner highlighting - Indicates when visiblity is required to met threshold.
const HIGHLIGHT_COLOR := Color(Color.RED, 0.3)
@export_group("Mode")
## Sets the mode of zone checking.
@export var check_mode: CHECK_MODE = CHECK_MODE.NONE:
set(val):
if check_mode != val:
check_mode = val
notify_property_list_changed()
queue_redraw()
## A flag variable used to distinguish if the center of the zone is described by
## a ratio of the size of the [member AnimatableScrollControl.scroll] value, or
## by a const pixel value.[br]
## Horizontal and vertical axis are consistered differently.
## [br][br]
## See [enum ZONE_EDITOR_DIMS], [member zone_horizontal], and [member zone_vertical].
var zone_point_pixel : int = 0:
set(val):
if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Horizontal:
_scrolled_horizontal(get_scroll_offset().x)
if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Vertical:
_scrolled_vertical(get_scroll_offset().y)
zone_point_pixel = val
notify_property_list_changed()
queue_redraw()
var _zone_horizontal : float = 0.5
## The horizontal position of the zone's center, described either as the ratio of
## the size of the [member AnimatableScrollControl.scroll] value, or by a const
## pixel value.[br]
## [br][br]
## See [member zone_point_pixel]
var zone_horizontal : float:
get: return _zone_horizontal
set(val):
if _zone_horizontal != val:
_zone_horizontal = val
_scrolled_horizontal(get_scroll_offset().x)
queue_redraw()
var _zone_vertical : float = 0.5
## The vertical position of the zone's center, described either as the ratio of
## the size of the [member AnimatableScrollControl.scroll] value, or by a const
## pixel value.[br]
## [br][br]
## See [member zone_point_pixel]
var zone_vertical : float = 0.5:
get: return _zone_vertical
set(val):
if _zone_vertical != val:
_zone_vertical = val
_scrolled_vertical(get_scroll_offset().y)
queue_redraw()
## A flag variable used to distinguish if the size of the zone is described by
## a ratio of the size of the [member AnimatableScrollControl.scroll] value, or
## by a const pixel value.[br]
## Horizontal and vertical axis are consistered differently.
## [br][br]
## See [enum ZONE_EDITOR_DIMS], [member zone_horizontal], and [member zone_vertical].
var zone_range_by_pixel : int = 0:
set(val):
if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Horizontal:
_scrolled_horizontal(get_scroll_offset().x)
if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Vertical:
_scrolled_horizontal(get_scroll_offset().y)
zone_range_by_pixel = val
notify_property_list_changed()
queue_redraw()
var _zone_range_horizontal : float = 0.05
## The horizontal size of the zone, described either as the ratio of the size
## of the [member AnimatableScrollControl.scroll] value, or by a const pixel
## value.[br]
## [br][br]
## See [member zone_vertical]
var zone_range_horizontal : float:
get: return _zone_range_horizontal
set(val):
if _zone_range_horizontal != val:
_zone_range_horizontal = val
_scrolled_horizontal(get_scroll_offset().x)
queue_redraw()
var _zone_range_vertical : float = 0.05
## The vertical size of the zone, described either as the ratio of the size
## of the [member AnimatableScrollControl.scroll] value, or by a const pixel
## value.[br]
## [br][br]
## See [member zone_vertical]
var zone_range_vertical : float:
get: return _zone_range_vertical
set(val):
if _zone_range_vertical != val:
_zone_range_vertical = val
_scrolled_vertical(get_scroll_offset().y)
queue_redraw()
## [b]Editor usage only.[/b] Shows or hides the helpful threshold highlighter.
var hide_indicator : bool = true:
set(val):
if hide_indicator != val:
hide_indicator = val
queue_redraw()
var _last_overlapped : int = 2
func _draw() -> void:
if !_mount || !Engine.is_editor_hint() || hide_indicator || !scroll || check_mode == CHECK_MODE.NONE: return
var draw_rect := get_zone_rect()
var scroll_transform := scroll.get_global_transform()
var transform := _mount.get_global_transform()
draw_set_transform(scroll_transform.get_origin() - transform.get_origin(),
scroll_transform.get_rotation() - transform.get_rotation(),
scroll_transform.get_scale() / transform.get_scale())
draw_rect(draw_rect, HIGHLIGHT_COLOR)
func _get_property_list() -> Array[Dictionary]:
var ret : Array[Dictionary] = []
var horizontal : int = 0 if check_mode & CHECK_MODE.HORIZONTAL else PROPERTY_USAGE_READ_ONLY
var vertical : int = 0 if check_mode & CHECK_MODE.VERTICAL else PROPERTY_USAGE_READ_ONLY
var either : int = horizontal & vertical
var options : String
if !horizontal: options = "Horizontal:1,"
if !vertical: options += "Vertical:2"
ret.append({
"name": "Zone Point",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP,
"hint_string": ""
})
ret.append({
"name": "zone_point_pixel",
"type": TYPE_INT,
"hint": PROPERTY_HINT_FLAGS,
"hint_string": options,
"usage": PROPERTY_USAGE_DEFAULT | either
})
ret.append({
"name": "zone_horizontal",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT | horizontal
}.merged({} if zone_point_pixel & 1 else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_less,or_greater"
}))
ret.append({
"name": "zone_vertical",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT | vertical
}.merged({} if zone_point_pixel & 2 else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_less,or_greater"
}))
ret.append({
"name": "Zone Range",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP,
"hint_string": ""
})
ret.append({
"name": "zone_range_by_pixel",
"type": TYPE_INT,
"hint": PROPERTY_HINT_FLAGS,
"hint_string": options,
"usage": PROPERTY_USAGE_DEFAULT | either
})
ret.append({
"name": "zone_range_horizontal",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT | horizontal
}.merged({} if zone_range_by_pixel & 1 else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_less,or_greater"
}))
ret.append({
"name": "zone_range_vertical",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT | vertical
}.merged({} if zone_range_by_pixel & 2 else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_less,or_greater"
}))
ret.append({
"name": "Indicator",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP,
"hint_str": ""
})
ret.append({
"name": "hide_indicator",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_DEFAULT
})
return ret
func _property_can_revert(property: StringName) -> bool:
if property in ["zone_point_pixel", "zone_range_by_pixel"]:
if self[property] != 0: return true
elif property in ["zone_horizontal", "zone_vertical"]:
if self[property] != 0.5: return true
elif property in ["zone_range_horizontal", "zone_range_vertical"]:
if self[property] != 0.05: return true
elif property == "hide_indicator":
return !hide_indicator
return false
func _property_get_revert(property: StringName) -> Variant:
if property in ["zone_point_pixel", "zone_range_by_pixel"]:
return 0
elif property in ["zone_horizontal", "zone_vertical"]:
return 0.5
elif property in ["zone_range_horizontal", "zone_range_vertical"]:
return 0.05
elif property == "hide_indicator":
return true
return null
func _scrolled_horizontal(scroll_hor : float) -> void:
if !(check_mode & CHECK_MODE.HORIZONTAL) || !scroll: return
var overlapped := is_overlaped_with_activate_zone()
if overlapped:
if _last_overlapped != 1:
entered_zone.emit()
_last_overlapped = 1
_while_in_zone(zone_local_scroll().x)
elif _last_overlapped:
_last_overlapped = 0
_while_in_zone(1 if zone_local_scroll().x > 0.5 else 0)
exited_zone.emit()
func _scrolled_vertical(scroll_ver : float) -> void:
if !(check_mode & CHECK_MODE.VERTICAL) || !scroll: return
var overlapped := is_overlaped_with_activate_zone()
if overlapped:
if _last_overlapped != 1:
entered_zone.emit()
_last_overlapped = 1
_while_in_zone(zone_local_scroll().y)
elif _last_overlapped:
_last_overlapped = 0
_while_in_zone(1 if zone_local_scroll().y > 0.5 else 0)
exited_zone.emit()
func _get_zone_pos() -> Vector2:
var ret := Vector2(zone_horizontal, zone_vertical)
if zone_point_pixel == ZONE_EDITOR_DIMS.None:
ret *= scroll.size
elif zone_point_pixel == ZONE_EDITOR_DIMS.Vertical:
ret.x *= scroll.size.x
elif zone_point_pixel == ZONE_EDITOR_DIMS.Horizontal:
ret.y *= scroll.size.y
return ret
func _get_zone_range() -> Vector2:
var ret := Vector2(zone_range_horizontal, zone_range_vertical) * 0.5
if zone_range_by_pixel == ZONE_EDITOR_DIMS.None:
ret *= scroll.size
elif zone_range_by_pixel == ZONE_EDITOR_DIMS.Vertical:
ret.x *= scroll.size.x
elif zone_range_by_pixel == ZONE_EDITOR_DIMS.Horizontal:
ret.y *= scroll.size.y
return ret
# Public Functions
## Returns [code]true[/code] if this node's mount is overlaping the zone area.[br]
## This function's value is dependant on the value of [member check_mode].
func is_overlaped_with_activate_zone() -> bool:
var item_pos_start := get_origin_offset()
var item_pos_end := item_pos_start + size
var zone_pos := _get_zone_pos()
var zone_range := _get_zone_range()
var zone_pos_start := zone_pos - zone_range
var zone_pos_end := zone_pos + zone_range
if (check_mode == CHECK_MODE.VERTICAL):
return (zone_pos_start.y <= item_pos_end.y && zone_pos_end.y >= item_pos_start.y)
elif (check_mode == CHECK_MODE.HORIZONTAL):
return (zone_pos_start.x <= item_pos_end.x && zone_pos_end.x >= item_pos_start.x)
elif (check_mode == CHECK_MODE.BOTH):
return (zone_pos_start.y <= item_pos_end.y && zone_pos_end.y >= item_pos_start.y) && (zone_pos_start.x <= item_pos_end.x && zone_pos_end.x >= item_pos_start.x)
return false
## Gets the Rect2 associated to the zone.
## [br][br]
## Also see [method get_zone_global_rect], [member zone_horizontal],
## [member zone_vertical], [member zone_range_horizontal], [member zone_range_vertical].
func get_zone_rect() -> Rect2:
if check_mode == CHECK_MODE.NONE || !scroll: return Rect2()
var ret : Rect2 = scroll.get_rect()
var zone_pos := _get_zone_pos()
var zone_range := _get_zone_range()
if (check_mode == CHECK_MODE.VERTICAL):
var pos := zone_pos.y - zone_range.y
var max_pos := max(pos, 0)
ret.position.y = max_pos
ret.size.y = min(zone_range.y + zone_range.y + pos, scroll.size.y) - max_pos
elif (check_mode == CHECK_MODE.HORIZONTAL):
var pos := zone_pos.x - zone_range.x
var max_pos := max(pos, 0)
ret.position.x = max_pos
ret.size.x = min(zone_range.x + zone_range.x + pos, scroll.size.x) - max_pos
elif (check_mode == CHECK_MODE.BOTH):
var pos := zone_pos - zone_range
var max_pos := pos.max(Vector2.ZERO)
ret.position = max_pos
ret.size = scroll.size.min(zone_range + zone_range + pos) - max_pos
return ret
## Gets the global Rect2 associated to the zone.
## [br][br]
## Also see [method get_zone_rect], [member zone_horizontal], [member zone_vertical],
## [member zone_range_horizontal], [member zone_range_vertical].
func get_zone_global_rect() -> Rect2:
if !scroll: return Rect2()
var zone_rect := get_zone_rect()
zone_rect.position += scroll.global_position
return zone_rect
## Gets the percentage of this node's mount intersection with the zone.
## [br][br]
## Also see [method get_zone_rect], [method get_zone_global_rect].
func in_zone_percent() -> float:
if !_mount: return 0
return (_mount.get_global_rect().intersection(get_zone_global_rect()).get_area()) / (_mount.size.x * _mount.size.y)
## The local scroll within the zone zone. Returns [code]0[/code] if this node's
## mount is not inside the zone area.
## [br][br]
## Also see [method get_zone_rect], [method get_zone_global_rect].
func zone_local_scroll() -> Vector2:
if !_mount: return Vector2.ZERO
var zone := get_zone_global_rect()
var mount := _mount.get_global_rect()
if zone.size + mount.size == Vector2.ZERO: return Vector2.ZERO
return Vector2.ONE + ((zone.position - _mount.global_position - mount.size) / (zone.size + mount.size)).clampf(-1, 0)
# Virtual Functions
## A virtual function that is called while this node is in the zone area. Is called
## after each scroll of [member scroll].
## [br][br]
## Paramter [param _scroll] is the local scroll within the zone.
func _while_in_zone(scroll : float) -> void: pass
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,50 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name AnimatableMount extends Control
## Used as a mount for size consistency between children [AnimatableControl] nodes.
## Emits before children are sorted
signal pre_sort_children
## Emits after children have been sorted
signal sort_children
var _min_size : Vector2
func _get_configuration_warnings() -> PackedStringArray:
for child : Node in get_children():
if child is AnimatableControl: return []
return ["This node has no 'AnimatableControl' nodes as children"]
func _get_minimum_size() -> Vector2:
if clip_children: return Vector2.ZERO
_update_children_minimum_size()
return _min_size
func _update_children_minimum_size() -> void:
_min_size = Vector2.ZERO
# Ensures size is the same as the largest size (of both axis) of children
for child : Node in get_children():
if child is AnimatableControl:
if child.size_mode & child.SIZE_MODE.MIN:
_min_size = _min_size.max(child.get_combined_minimum_size())
func _init() -> void:
resized.connect(_sort_children, CONNECT_DEFERRED)
size_flags_changed.connect(_sort_children, CONNECT_DEFERRED)
func _sort_children() -> void:
pre_sort_children.emit()
for child : Node in get_children():
if child is AnimatableControl:
child._bound_size()
sort_children.emit()
## A virtual helper function that should be used when creating your own mounts.[br]
## Is called upon an [AnimatableControl] being added as a child.
func _on_mount(control : AnimatableControl) -> void: pass
## A virtual helper function that should be used when creating your own mounts.[br]
## Is called upon an [AnimatableControl] being removed as a child.
func _on_unmount(control : AnimatableControl) -> void: pass
## A helper function that should be used when creating your own mounts.[br]
## Returns size of this mount.
func get_relative_size(control : AnimatableControl) -> Vector2: return size
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,138 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name AnimatableTransformationMount extends AnimatableMount
## An [AnimatableMount] that adjusts for it's children 2D transformations: Rotation, Position, and Scale.
## If [code]true[/code] this node will adjust it's size to fit its children's scales.
@export var adjust_scale : bool:
set(val):
if val != adjust_scale:
adjust_scale = val
update_minimum_size()
## If [code]true[/code] this node will adjust it's size to fit its children's rotations.[br]
## [b]NOTE[/b]: A large [member pivot_offset] can cause floating point precision issues.
@export var adjust_rotate : bool:
set(val):
if val != adjust_rotate:
adjust_rotate = val
update_minimum_size()
## If [code]true[/code] this node adjust its children's positions inside it's size.
@export var adjust_position : bool:
set(val):
if val != adjust_position:
adjust_position = val
update_minimum_size()
var _child_min_size : Vector2
func _update_children_minimum_size() -> void:
var _old_min_size := _min_size
_min_size = Vector2.ZERO
_child_min_size = Vector2.ZERO
var children_info: Array[Array] = []
for child : AnimatableControl in get_children():
if child:
var child_size : Vector2
var child_offset : Vector2
# Scale child size, if needed
if adjust_scale:
child_size = child.get_combined_minimum_size() * child.scale
else:
child_size = child.get_combined_minimum_size()
_child_min_size = _child_min_size.max(child_size)
# Rotates child size, if needed.
if adjust_rotate:
child_offset = child.pivot_offset
if adjust_scale: child_offset *= child.scale
# Gets the bounding box of the rect, when rotated around a pivot
var bb_rect := _get_rotated_rect_bounding_box(
Rect2(Vector2.ZERO, child_size),
child_offset,
child.rotation
)
child_size = bb_rect.size
child_offset = bb_rect.position
children_info.append([child, child_size, child_offset])
_min_size = _min_size.max(child_size)
# Leaves position adjusts after so size, after calculating min-size of children, can be used
if adjust_position:
if _old_min_size != _min_size:
# If in Container, and min_size changed, update after the container as resorted children
if get_parent_control() is Container:
get_parent_control().sort_children.connect(_adjust_children_positions.bind(children_info), CONNECT_ONE_SHOT)
else:
# Otherwise, if min_size changed still, update after minimum_size_changed changed
minimum_size_changed.connect(_adjust_children_positions.bind(children_info), CONNECT_ONE_SHOT | CONNECT_DEFERRED)
update_minimum_size()
else:
# If min_size did not change, deffer children position changes
call_deferred("_adjust_children_positions", children_info)
elif _old_min_size != _min_size:
update_minimum_size()
func _adjust_children_positions(children_info: Array[Array]) -> void:
for child_info : Array in children_info:
var child : AnimatableControl = child_info[0]
var child_size : Vector2 = child_info[1]
var child_offset : Vector2 = child_info[2]
var piv_offset : Vector2
# Rotates pivot, if needed
if adjust_rotate:
piv_offset = -child.pivot_offset.rotated(rotation)
else:
piv_offset = -child.pivot_offset
# If adjusts the pivot by scale, if needed
if adjust_scale:
piv_offset *= (child.scale - Vector2.ONE)
# Not clamp, because min should have priorty
# max_size_adjusted_for_child_size = size - child_size - child_offset - piv_offset
# min_size_adjusted_for_child_size = -piv_offset - child_offset
var new_pos := child.position.min(size - child_size - child_offset - piv_offset).max(-piv_offset - child_offset)
# Changes position, if needed
if child.position != new_pos: child.position = new_pos
func _get_rotated_rect_bounding_box(rect : Rect2, pivot : Vector2, angle : float) -> Rect2:
# Base Values
var pos := rect.position
var sze := rect.size
var trig := Vector2(cos(angle), sin(angle))
# Simplified equation for centerPoint - bb_size*0.5
var bb_pos := Vector2(
(sze.x * (trig.x - abs(trig.x)) - sze.y * (trig.y + abs(trig.y))) * 0.5 + pivot.x * (1 - trig.x) + trig.y * pivot.y + pos.x,
(sze.x * (trig.y - abs(trig.y)) + sze.y * (trig.x - abs(trig.x))) * 0.5 + pivot.y * (1 - trig.x) - trig.y * pivot.x + pos.y
)
trig = trig.abs()
## Finds the fix of the bounding box of the rotated rectangle
var bb_size := Vector2(
sze.x * trig.x + sze.y * trig.y,
sze.x * trig.y + sze.y * trig.x
)
return Rect2(bb_pos, bb_size)
func _init() -> void:
size_flags_changed.connect(update_minimum_size, CONNECT_DEFERRED)
func _on_mount(control : AnimatableControl) -> void:
control.transformation_changed.connect(update_minimum_size, CONNECT_DEFERRED)
func _on_unmount(control : AnimatableControl) -> void:
control.transformation_changed.disconnect(update_minimum_size)
## Returns the adjusted size of this mount.
func get_relative_size(control : AnimatableControl) -> Vector2:
if adjust_scale:
return _child_min_size / control.scale
return _child_min_size
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,307 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name AnimatedSwitch extends BaseButton
## Animated verison of a switch button.
@export_group("Redirect")
## If [code]true[/code], the switch will be displayed vertical.
@export var vertical : bool = false:
set(val):
if vertical != val:
vertical = val
force_state(button_pressed)
## If [code]true[/code], the switch will be flipped.
@export var flip : bool = false:
set(val):
if flip != val:
flip = val
force_state(button_pressed)
@export_group("Size")
## The size of the switch's base.
@export var switch_size : Vector2 = Vector2(100, 50):
set(val):
if switch_size != val:
switch_size = val
_handle_resize()
update_minimum_size()
## The size of the switch's knob.
@export var knob_size : Vector2 = Vector2(40, 40):
set(val):
if knob_size != val:
knob_size = val
_handle_resize()
update_minimum_size()
## The base offset of the knob from it's set position.
@export var knob_offset : Vector2 = Vector2.ZERO:
set(val):
if knob_offset != val:
knob_offset = val
_handle_resize()
update_minimum_size()
## An amount of pixels the knob will extend past the switch's base.
## [br][br]
## Also see [member switch_size].
@export var knob_overextend : float = 10:
set(val):
if knob_overextend != val:
knob_overextend = val
_handle_resize()
update_minimum_size()
@export_group("Display")
## The style of the switch.
@export var switch_bg : StyleBox:
set(val):
if switch_bg != val:
switch_bg = val
if knob_bg:
_switch.add_theme_stylebox_override("panel", switch_bg)
else:
_switch.remove_theme_stylebox_override("panel")
## The style of the knob.
@export var knob_bg : StyleBox:
set(val):
if knob_bg != val:
knob_bg = val
if knob_bg:
_knob.add_theme_stylebox_override("panel", knob_bg)
else:
_knob.remove_theme_stylebox_override("panel")
@export_group("Colors")
@export_subgroup("Switch")
## The color of the switch's base when unfocused.
@export var switch_bg_normal : Color:
set(val):
if switch_bg_normal != val:
switch_bg_normal = val
_kill_color_animation()
_animate_color(false)
## The color of the switch's base when focused.
@export var switch_bg_focus : Color:
set(val):
if switch_bg_focus != val:
switch_bg_focus = val
_kill_color_animation()
_animate_color(false)
## The color of the switch's base when disabled.
@export var switch_bg_disabled : Color:
set(val):
if switch_bg_disabled != val:
switch_bg_disabled = val
_kill_color_animation()
_animate_color(false)
@export_subgroup("Knob")
## The color of the switch's knob when unfocused.
@export var knob_bg_normal : Color:
set(val):
if knob_bg_normal != val:
knob_bg_normal = val
_kill_color_animation()
_animate_color(false)
## The color of the switch's knob when focused.
@export var knob_bg_focus : Color:
set(val):
if knob_bg_focus != val:
knob_bg_focus = val
_kill_color_animation()
_animate_color(false)
## The color of the switch's knob when disabled.
@export var knob_bg_disabled : Color:
set(val):
if knob_bg_disabled != val:
knob_bg_disabled = val
_kill_color_animation()
_animate_color(false)
@export_group("Animation Properties")
@export_subgroup("Main")
## The ease of the knob's movement across the base.
@export var main_ease : Tween.EaseType
## The transition of the knob's movement across the base.
@export var main_transition : Tween.TransitionType
## The duration of the knob's movement across the base.
@export_range(0.001, 0.5, 0.001, "or_greater") var main_duration : float = 0.15
@export_subgroup("Knob Color")
## If [code]true[/code], then the knob will change color according to this node's state.
@export var animate_knob_color : bool = true
## The ease of the knob's color change.
@export var knob_color_ease : Tween.EaseType
## The transition of the knob's color change.
@export var knob_color_transition : Tween.TransitionType
## The duration of the knob's color change.
@export_range(0.001, 0.5, 0.001, "or_greater") var knob_color_duration : float = 0.1
@export_subgroup("Switch Color")
## If [code]true[/code], then the base will change color according to this node's state.
@export var animate_switch_color : bool = true
## The ease of the base's color change.
@export var switch_color_ease : Tween.EaseType
## The transition of the base's color change.
@export var switch_color_transition : Tween.TransitionType
## The duration of the base's color change.
@export_range(0.001, 0.5, 0.001, "or_greater") var switch_color_duration : float = 0.1
var _knob : Panel
var _switch : Panel
var _main_animate_tween : Tween
var _knob_color_animate_tween : Tween
var _switch_color_animate_tween : Tween
## Instantly changes the switch's state without animation.
func force_state(knob_state : bool) -> void:
_handle_animations(false, knob_state)
## Changes the switch's state with animation.
func toggle_state(knob_state : bool) -> void:
_handle_animations(true, knob_state)
## Gets the current knob color.
func get_knob_color() -> Color:
if disabled:
return knob_bg_disabled
return knob_bg_focus if button_pressed else knob_bg_normal
## Gets the current switch base color.
func get_switch_color() -> Color:
if disabled:
return switch_bg_disabled
return switch_bg_focus if button_pressed else switch_bg_normal
func _kill_main_animation() -> void:
if _main_animate_tween && _main_animate_tween.is_running():
_main_animate_tween.kill()
func _kill_color_animation() -> void:
if _knob_color_animate_tween && _knob_color_animate_tween.is_running():
_knob_color_animate_tween.kill()
if _switch_color_animate_tween && _switch_color_animate_tween.is_running():
_main_animate_tween.kill()
func _handle_animations(animate : bool, knob_state : bool) -> void:
_kill_main_animation()
_kill_color_animation()
button_pressed = knob_state
if animate:
_animate_knob(knob_state)
_animate_color(true)
return
_position_knob(float(knob_state))
_animate_color(false)
func _animate_knob(knob_state : bool) -> void:
_main_animate_tween = create_tween()
_main_animate_tween.set_ease(main_ease)
_main_animate_tween.set_trans(main_transition)
_main_animate_tween.tween_method(
_position_knob,
1.0 - float(knob_state),
0.0 + float(knob_state),
main_duration
)
func _position_knob(delta : float) -> void:
if flip:
delta = 1.0 - delta
var offset : Vector2
var delta_v : Vector2
if vertical:
offset = Vector2(0.0, knob_overextend)
delta_v = Vector2(0.5, delta)
else:
offset = Vector2(knob_overextend, 0.0)
delta_v = Vector2(delta, 0.5)
_knob.position = (
(switch_size - knob_size + offset + offset) * delta_v # Size
+ (_switch.position - offset) # Position
+ knob_offset # Offset
)
func _animate_color(animate : bool = false) -> void:
if animate && animate_knob_color:
_knob_color_animate_tween = create_tween()
_knob_color_animate_tween.set_ease(knob_color_ease)
_knob_color_animate_tween.set_trans(knob_color_transition)
_knob_color_animate_tween.tween_property(
_knob,
"self_modulate",
get_knob_color(),
knob_color_duration
)
else:
_knob.self_modulate = get_knob_color()
if animate && animate_switch_color:
_switch_color_animate_tween = create_tween()
_switch_color_animate_tween.set_ease(switch_color_ease)
_switch_color_animate_tween.set_trans(switch_color_transition)
_switch_color_animate_tween.tween_property(
_switch,
"self_modulate",
get_switch_color(),
switch_color_duration
)
else:
_switch.self_modulate = get_switch_color()
func _init() -> void:
toggle_mode = true
_switch = Panel.new()
_knob = Panel.new()
_switch.mouse_filter = Control.MOUSE_FILTER_IGNORE
_knob.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_switch)
add_child(_knob)
resized.connect(_handle_resize)
toggled.connect(toggle_state)
_handle_resize()
_animate_color(false)
func _handle_resize() -> void:
_switch.position = (size - switch_size) * 0.5
_switch.size = switch_size
_knob.size = knob_size
force_state(button_pressed)
func _get_minimum_size() -> Vector2:
return (knob_size + (knob_offset.abs() * 0.5)).max(switch_size + Vector2(max(0, knob_overextend) * 2, 0))
func _validate_property(property: Dictionary) -> void:
if property.name == "toggle_mode":
property.usage &= ~PROPERTY_USAGE_EDITOR
func _set(property: StringName, value: Variant) -> bool:
if property == "disabled":
disabled = value
_animate_color()
return true
return false
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,178 @@
@tool
class_name HoldButton extends Control
## A [Control] node used for hold buttons.
## Emits the state of the button as it is pressed.
## [br][br]
## Also see [member toggle_mode] and [signal button_state].
signal pressed_state(val : bool)
## Emits the state of the button as it is released.
## [br][br]
## Also see [member toggle_mode] and [signal button_state].
signal release_state(val : bool)
## Emits the state of the button as it is pressed or released.
## [br][br]
## Also see [signal pressed_state] and [signal release_state].
signal button_state(val : bool)
## Emits when button is released with all vaild conditions.
## [br][br]
## Also see [member release_when_outside] and [member cancel_when_outside].
signal press_vaild
## Emits when button is released without all vaild conditions.
## [br][br]
## Also see [member release_when_outside] and [member cancel_when_outside].
signal press_invaild
## Emits when press starts.
signal press_start
## Emits when press ends.
signal press_end
## If [code]true[/code], the button's state is pressed. Means the button is pressed down
## or toggled (if [member toggle_mode] is active). Only works if [member toggle_mode] is
## [code]false[/code].
@export var button_pressed : bool
## If [code]true[/code], the button is in [member toggle_mode]. Makes the button
## flip state between pressed and unpressed each time its area is clicked.
@export var toggle_mode : bool:
set(val):
if toggle_mode != val:
toggle_mode = val
if !val:
button_pressed = false
notify_property_list_changed()
## If [code]true[/code], then this node does not accept input.
@export var disabled : bool:
set(val):
if disabled != val:
disabled = val
_bounds_check.disabled = val
_distance_check.disabled = val
@export_group("Release At")
## If [code]true[/code], the button's held state is released if input moves outside of
## bounds.
@export var release_when_outside : bool = true:
set(val):
release_when_outside = val
_bounds_check.release_when_outside = val
## If [code]true[/code], the button's held state is released and all checking is stopped
## if input moves outside of bounds.
@export var cancel_when_outside : bool = true:
set(val):
cancel_when_outside = val
_bounds_check.cancel_when_outside = val
@export_group("Release On Drag")
## The current check mode.
##
## Also see [enum CHECK_MODE].
@export var mode : DistanceCheck.CHECK_MODE = DistanceCheck.CHECK_MODE.BOTH:
set(val):
mode = val
_distance_check.mode = val
## The max pixels difference, between the start and current position, that can be tolerated.
@export var distance : float = 30:
set(val):
distance = val
_distance_check.distance = val
var _bounds_check : BoundsCheck
var _distance_check : DistanceCheck
## Forcibly stops this node's check.
func force_release() -> void:
_bounds_check.force_release()
_distance_check.force_release()
_on_end_invaild()
## Returns if mouse or touch is being held (mouse or touch outside of limit without being released).
## [br][br]
## Also see [method force_release].
func is_held() -> bool:
return _bounds_check.is_checking()
func _init() -> void:
_distance_check = DistanceCheck.new()
_distance_check.name = "distance_check"
_distance_check.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(_distance_check)
_distance_check.mouse_filter = Control.MOUSE_FILTER_IGNORE
_distance_check.cancel_when_outside = true
_distance_check.disabled = disabled
_distance_check.mode = mode
_distance_check.distance = distance
_distance_check.pos_exceeded.connect(force_release)
_bounds_check = BoundsCheck.new()
_bounds_check.name = "bounds_check"
_bounds_check.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(_bounds_check)
_bounds_check.mouse_filter = Control.MOUSE_FILTER_IGNORE
_bounds_check.disabled = disabled
_bounds_check.release_when_outside = release_when_outside
_bounds_check.cancel_when_outside = cancel_when_outside
_bounds_check.end_vaild.connect(_on_end_vaild)
_bounds_check.end_invaild.connect(_on_end_invaild)
_bounds_check.start_check.connect(_on_start_check)
_bounds_check.end_check.connect(_on_end_check)
func _validate_property(property: Dictionary) -> void:
if property.name == "button_pressed":
if !toggle_mode:
property.usage |= PROPERTY_USAGE_READ_ONLY
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseMotion || event is InputEventScreenDrag || event is InputEventMouseButton || event is InputEventScreenTouch:
event.position += global_position
_bounds_check._gui_input(event)
_distance_check._gui_input(event)
func _on_start_check() -> void:
press_start.emit()
if toggle_mode:
button_pressed = !button_pressed
else:
button_pressed = true
button_state.emit(button_pressed)
pressed_state.emit(button_pressed)
func _on_end_check() -> void:
_distance_check.force_release()
press_end.emit()
func _on_end_vaild() -> void:
press_vaild.emit()
if !toggle_mode:
button_pressed = false
button_state.emit(button_pressed)
release_state.emit(button_pressed)
func _on_end_invaild() -> void:
press_invaild.emit()
if toggle_mode:
button_pressed = !button_pressed
else:
button_pressed = false
button_state.emit(button_pressed)
release_state.emit(button_pressed)

View file

@ -0,0 +1,9 @@
@tool
class_name BoundsCheck extends MotionCheck
## A [Control] node used to check if a mouse or touch moved outside this node's bounds after
## a vaild press inside.
func _pos_check(pos : Vector2) -> bool:
return get_global_rect().has_point(pos)

View file

@ -0,0 +1,46 @@
@tool
class_name DistanceCheck extends MotionCheck
## A [Control] node used to check if a mouse or touch moved a distance away from where
## the user originally pressed.
## How this node will determine the distance between the starting and current location
## of mouse and touch movement.
enum CHECK_MODE {
NONE = 0b000, ## No action.
HORIZONTAL = 0b001, ## Only checks the horizontal difference.
VERTICAL = 0b010, ## Only checks the vertical difference.
BOTH = 0b011, ## Only checks orthogonal difference.
DISTANCE = 0b100 ## Uses the Pythagorean Theorem.
}
## The current check mode.
##
## Also see [enum CHECK_MODE].
@export var mode : CHECK_MODE
## The max pixels difference, between the start and current position, that can be tolerated.
@export var distance : float = 30
var _prev_pos : Vector2
func _pos_check(pos : Vector2) -> bool:
if mode & CHECK_MODE.DISTANCE:
if _prev_pos.distance_squared_to(pos) > distance * distance:
return false
return true
var diff := (_prev_pos - pos).abs()
if mode & CHECK_MODE.HORIZONTAL:
if diff.x > distance:
return false
if mode & CHECK_MODE.VERTICAL:
if diff.y > distance:
return false
return true
func _on_check_start(event: InputEvent) -> void:
_prev_pos = event.position

View file

@ -0,0 +1,165 @@
@tool
class_name MotionCheck extends Control
## Checks for motion of the mouse or touch input after a press.
## Emited when check has started (mouse or touch is pressed).
signal start_check
## Emited when check has ended (mouse or touch is released or distance limit has exceeded).
signal end_check
## Emited when mouse or touch is released within the distence limit.
signal end_vaild
## Emited when check has ended without mouse or touch being released within the distence limit.
## [br][br]
## Also see [member cancel_when_outside] and [method _pos_check].
signal end_invaild
## Emited when mouse or touch is moved outside the distence limit.
## [br][br]
## Also see [member cancel_when_outside] and [method _pos_check].
signal pos_exceeded
## Emited when the current held state changes.
signal held_state(state : bool)
## If [code]true[/code], the button's held state is released if input moves outside of
## bounds.
## [br][br]
## Also see [member cancel_when_outside] and [method _pos_check].
@export var release_when_outside : bool = false:
set(val):
if release_when_outside != val:
release_when_outside = val
## If [code]true[/code], then the check will end when mouse or touch is moved outside the distence limit.
## [br][br]
## Also see [member release_when_outside] and [method _pos_check].
@export var cancel_when_outside : bool = false:
set(val):
if cancel_when_outside != val:
cancel_when_outside = val
## If [code]true[/code], then this node does not accept input.
@export var disabled : bool:
set(val):
if disabled != val:
disabled = val
if val:
force_release()
var _checking : bool = false
var _holding : bool = false
# Public Methods
## Forcibly stops this node's check.
func force_release() -> void:
if _holding:
held_state.emit(false)
_holding = false
if _checking:
_invaild_end()
_checking = false
## Returns if this node is currently checking a mouse or touch press.
func is_checking() -> bool: return _checking
## Returns if mouse or touch is being held (mouse or touch outside of limit without being released).
## [br][br]
## Also see [method force_release].
func is_held() -> bool: return _holding
# Virtual Methods
## A virtual method that should be overloaded. Returns [code]true[/code] if [param pos] is
## within the distance limit. [code]false[/code] otherwise.
func _pos_check(pos : Vector2) -> bool:
return false
## A virtual method that should be overloaded. This is called when an input starts a
## check.
func _on_check_start(event: InputEvent) -> void:
pass
## A virtual method that should be overloaded. This is called when an input releases a
## check.
func _on_check_release(event: InputEvent) -> void:
pass
## A virtual method that should be overloaded. This is called when an input exceeds a
## check.
## [br][br]
## Also see [method _pos_check].
func _on_check_exceeded(event: InputEvent) -> void:
pass
# Private Methods
func _init() -> void:
mouse_filter = MOUSE_FILTER_PASS
tree_exiting.connect(force_release)
func _property_can_revert(property: StringName) -> bool:
if property == "mouse_filter":
return mouse_filter == MOUSE_FILTER_PASS
return false
func _property_get_revert(property: StringName) -> Variant:
if property == "mouse_filter":
return MOUSE_FILTER_PASS
return null
func _gui_input(event: InputEvent) -> void:
if disabled: return
if event is InputEventMouseButton || event is InputEventScreenTouch:
if event.pressed:
if !_checking && get_global_rect().has_point(event.position):
_on_check_start(event)
_checking = true
held_state.emit(true)
start_check.emit()
elif _checking:
_on_check_release(event)
_checking = false
if !_holding:
held_state.emit(false)
else:
_holding = false
if _pos_check(event.position):
_vaild_end()
else:
_invaild_end()
if mouse_filter == MOUSE_FILTER_STOP: accept_event()
if _checking:
if event is InputEventMouseMotion || event is InputEventScreenDrag:
if _holding:
if _pos_check(event.position):
held_state.emit(true)
_checking = true
_holding = false
return
if !_pos_check(event.position):
pos_exceeded.emit()
if release_when_outside:
held_state.emit(false)
_holding = true
if cancel_when_outside:
_on_check_exceeded(event)
_holding = false
_checking = false
_invaild_end()
return
if mouse_filter == MOUSE_FILTER_STOP: accept_event()
func _invaild_end() -> void:
end_invaild.emit()
end_check.emit()
func _vaild_end() -> void:
end_vaild.emit()
end_check.emit()

View file

@ -0,0 +1,151 @@
@tool
class_name ModulateTransitionButton extends ModulateTransitionContainer
## A button that inherts from [ModulateTransitionContainer] and uses [HoldButton] as
## input.
## Emits the state of the button as it is released.
signal release_state(toggle : bool)
## Emits when button is released with all vaild conditions.
signal press_vaild
## Emits when press starts.
signal press_start
## Emits when press ends.
signal press_end
@export_group("Toggleable")
## If [code]true[/code], the button's state is pressed. Means the button is pressed down
## or toggled (if [member toggle_mode] is active). Only works if [member toggle_mode] is
## [code]false[/code].
@export var button_pressed : bool:
set(val):
if button_pressed != val:
button_pressed = val
_set_button_color(val)
## If [code]true[/code], the button is in [member toggle_mode]. Makes the button
## flip state between pressed and unpressed each time its area is clicked.
@export var toggle_mode : bool:
set(val):
if toggle_mode != val:
toggle_mode = val
button_pressed = false
notify_property_list_changed()
if _button: _button.toggle_mode = val
## If [code]true[/code], then this node does not accept input.
@export var disabled : bool:
set = _set_disabled
@export_group("Alpha")
## The color to modulate to when this node is unfocused.
@export var normal_color : Color = Color(1.0, 1.0, 1.0, 1.0):
set(val):
if normal_color != val:
normal_color = val
if is_node_ready(): colors[0] = val
force_color(focused_color)
## The color to modulate to when this node is focused.
@export var focus_color : Color = Color(1.0, 1.0, 1.0, 0.75):
set(val):
if focus_color != val:
focus_color = val
if is_node_ready(): colors[1] = val
force_color(focused_color)
## The color to modulate to when this node is disabled.
@export var disabled_color : Color = Color(1.0, 1.0, 1.0, 0.5):
set(val):
if disabled_color != val:
disabled_color = val
if is_node_ready(): colors[2] = val
force_color(focused_color)
var _button : HoldButton
## Forcibly stops this node's check.
func force_release() -> void:
if _button: _button.force_release()
## Returns if mouse or touch is being held (mouse or touch outside of limit without being released).
func is_held() -> bool:
return _button && _button.is_held()
func _init() -> void:
super()
_button = HoldButton.new()
add_child(_button)
_button.move_to_front()
if !Engine.is_editor_hint():
child_order_changed.connect(_button.move_to_front, CONNECT_DEFERRED)
_button.button_state.connect(_set_button_color)
_button.release_state.connect(_emit_vaild_release)
_button.press_start.connect(press_start.emit)
_button.press_end.connect(press_end.emit)
_button.press_vaild.connect(press_vaild.emit)
_button.mouse_filter = mouse_filter
_button.mouse_force_pass_scroll_events = mouse_force_pass_scroll_events
_button.mouse_default_cursor_shape = mouse_default_cursor_shape
_button.toggle_mode = toggle_mode
_button.button_pressed = button_pressed
_button.disabled = disabled
func _ready() -> void:
colors = [normal_color, focus_color, disabled_color]
force_color(2 if disabled else (1 if button_pressed else 0))
func _validate_property(property: Dictionary) -> void:
match property.name:
"pressed":
if !toggle_mode:
property.usage |= PROPERTY_USAGE_READ_ONLY
"focused_alpha", "alphas":
property.usage &= ~PROPERTY_USAGE_EDITOR
func _set(property: StringName, value: Variant) -> bool:
if _button:
match property:
"mouse_filter":
_button.mouse_filter = value
"mouse_force_pass_scroll_events":
_button.mouse_force_pass_scroll_events = value
"mouse_default_cursor_shape":
_button.mouse_default_cursor_shape = value
return false
func _get(property: StringName) -> Variant:
if _button:
match property:
"mouse_filter":
return _button.mouse_filter
"mouse_force_pass_scroll_events":
return _button.mouse_force_pass_scroll_events
"mouse_default_cursor_shape":
return _button.mouse_default_cursor_shape
return null
func _set_disabled(val : bool) -> void:
disabled = val
_set_button_color(button_pressed)
if _button:
_button.disabled = disabled
func _set_button_color(val : bool) -> void:
if disabled: set_color(2)
else: set_color(int(val))
func _emit_vaild_release(release : bool) -> void:
_set_button_color(release)
release_state.emit(release)

View file

@ -0,0 +1,158 @@
@tool
class_name StyleTransitionButton extends StyleTransitionContainer
## A button that inherts from [StyleTransitionContainer] and uses [HoldButton] as
## input.
## Emits the state of the button as it is released.
signal release_state(toggle : bool)
## Emits when button is released with all vaild conditions.
signal press_vaild
## Emits when press starts.
signal press_start
## Emits when press ends.
signal press_end
@export_group("Toggleable")
## If [code]true[/code], the button's state is pressed. Means the button is pressed down
## or toggled (if [member toggle_mode] is active). Only works if [member toggle_mode] is
## [code]false[/code].
@export var button_pressed : bool:
set(val):
if button_pressed != val:
button_pressed = val
_set_button_color(val)
## If [code]true[/code], the button is in [member toggle_mode]. Makes the button
## flip state between pressed and unpressed each time its area is clicked.
@export var toggle_mode : bool:
set(val):
if toggle_mode != val:
toggle_mode = val
button_pressed = false
notify_property_list_changed()
if _button: _button.toggle_mode = val
var _disabled : bool:
set = _set_disabled
## If [code]true[/code], then this node does not accept input.
@export var disabled : bool:
set(val):
if disabled != val:
disabled = val
_set_disabled(val)
@export_group("Colors")
## The color to modulate to when this node is unfocused.
@export var normal_color : Color = Color(0.525, 0.329, 0.808):
set(val):
if normal_color != val:
normal_color = val
if is_node_ready(): colors[0] = val
force_color(focused_color)
## The color to modulate to when this node is focused.
@export var focus_color : Color = Color(0.611, 0.441, 0.886):
set(val):
if focus_color != val:
focus_color = val
if is_node_ready(): colors[1] = val
force_color(focused_color)
## The color to modulate to when this node is disabled.
@export var disabled_color : Color = Color(0.318, 0.247, 0.565):
set(val):
if disabled_color != val:
disabled_color = val
if is_node_ready(): colors[2] = val
force_color(focused_color)
var _button : HoldButton
## Forcibly stops this node's check.
func force_release() -> void:
if _button: _button.force_release()
## Returns if mouse or touch is being held (mouse or touch outside of limit without being released).
func is_held() -> bool:
return _button && _button.is_held()
func _init() -> void:
_button = HoldButton.new()
add_child(_button)
_button.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_button.move_to_front()
if !Engine.is_editor_hint():
child_order_changed.connect(_button.move_to_front, CONNECT_DEFERRED)
_button.button_state.connect(_set_button_color)
_button.release_state.connect(_emit_vaild_release)
_button.press_start.connect(press_start.emit)
_button.press_end.connect(press_end.emit)
_button.press_vaild.connect(press_vaild.emit)
_button.mouse_filter = mouse_filter
_button.mouse_force_pass_scroll_events = mouse_force_pass_scroll_events
_button.mouse_default_cursor_shape = mouse_default_cursor_shape
_button.toggle_mode = toggle_mode
_button.button_pressed = button_pressed
_button.disabled = _disabled
func _ready() -> void:
super()
colors = [normal_color, focus_color, disabled_color]
force_color(2 if _disabled else (1 if button_pressed else 0))
func _validate_property(property: Dictionary) -> void:
match property.name:
"pressed":
if !toggle_mode:
property.usage |= PROPERTY_USAGE_READ_ONLY
"focused_color", "colors":
property.usage &= ~PROPERTY_USAGE_EDITOR
func _set(property: StringName, value: Variant) -> bool:
if _button:
match property:
"mouse_filter":
_button.mouse_filter = value
"mouse_force_pass_scroll_events":
_button.mouse_force_pass_scroll_events = value
"mouse_default_cursor_shape":
_button.mouse_default_cursor_shape = value
return false
func _get(property: StringName) -> Variant:
if _button:
match property:
"mouse_filter":
return _button.mouse_filter
"mouse_force_pass_scroll_events":
return _button.mouse_force_pass_scroll_events
"mouse_default_cursor_shape":
return _button.mouse_default_cursor_shape
return null
func _set_disabled(val : bool) -> void:
_disabled = val || disabled
_set_button_color(button_pressed)
if _button:
_button.disabled = _disabled
func _set_button_color(val : bool) -> void:
if _disabled: set_color(2)
else: set_color(int(val))
func _emit_vaild_release(release : bool) -> void:
_set_button_color(release)
release_state.emit(release)

View file

@ -0,0 +1,616 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name Carousel extends Container
## A container for Carousel Display of [Control] nodes.
## Changes the behavior of how draging scrolls the carousel items. Also see [member snap_carousel_transtion_type], [member snap_carousel_ease_type], and [member paging_requirement].
enum SNAP_BEHAVIOR {
NONE = 0b00, ## No behavior.
SNAP = 0b01, ## Once drag is released, the carousel will snap to the nearest item.x
PAGING = 0b10 ## Carousel items will not scroll when dragged, unless [member paging_requirement] threshold is met. [member hard_stop] will be assumed as [code]true[/code] for this.
}
## Internel enum used to differentiate what animation is currently playing
enum ANIMATION_TYPE {
NONE = 0b00, ## No behavior.
MANUAL = 0b01, ## Currently animating via request by [method go_to_index].
SNAP = 0b10 ## Currently animating via an auto-item snapping request.
}
## This signal is emited when a snap reaches it's destination.
signal snap_end
## This signal is emited when a snap begins.
signal snap_begin
## This signal is emited when a drag finishes. This does not include the slowdown caused when [member hard_stop] is [code]false[/code].
signal drag_end
## This signal is emited when a drag begins.
signal drag_begin
## This signal is emited when the slowdown, caused when [member hard_stop] is [code]false[/code], finished naturally.
signal slowdown_end
## This signal is emited when the slowdown, caused when [member hard_stop] is [code]false[/code], is interrupted by another drag or other feature.
signal slowdown_interupted
@export_group("Carousel Options")
## The index of the item this carousel will start at.
@export var starting_index : int = 0:
set(val):
if starting_index != val:
starting_index = val
go_to_index(-val, false)
## The size of each item in the carousel.
@export var item_size : Vector2 = Vector2(200, 200):
set(val):
if item_size != val:
var current_index := get_carousel_index()
item_size = val
_settup_children()
go_to_index(current_index, false)
## The space between each item in the carousel.
@export var item_seperation : int = 0:
set(val):
if item_seperation != val:
item_seperation = val
_kill_animation()
_adjust_children()
## The orientation the carousel items will be displayed in.
@export_range(0, 360, 0.001, "or_less", "or_greater") var carousel_angle : float = 0.0:
set(val):
if carousel_angle != val:
var current_index := get_carousel_index()
carousel_angle = val
_angle_vec = Vector2.RIGHT.rotated(deg_to_rad(carousel_angle))
_kill_animation()
_adjust_children()
go_to_index(current_index, false)
@export_group("Loop Options")
## Allows looping from the last item to the first and vice versa.
@export var allow_loop : bool = true
## If [code]true[/code], the carousel will display it's items as if looping. Otherwise, the items will not loop.
## [br][br]
## also see [member enforce_border] and [member border_limit].
@export var display_loop : bool = true:
set(val):
if val != display_loop:
display_loop = val
_adjust_children()
notify_property_list_changed()
## The number of items, surrounding the current item of the current index, that will be visible.
## If [code]-1[/code], all items will be visible.
@export var display_range : int = -1:
set(val):
val = max(-1, val)
if val != display_range:
display_range = val
_adjust_children()
@export_group("Snap")
## Assigns the behavior of how draging scrolls the carousel items. Also see [member snap_carousel_transtion_type], [member snap_carousel_ease_type], and [member paging_requirement].
@export var snap_behavior : SNAP_BEHAVIOR = SNAP_BEHAVIOR.SNAP:
set(val):
if val != snap_behavior:
snap_behavior = val
_end_drag_slowdown()
_create_animation(get_carousel_index(), ANIMATION_TYPE.SNAP)
notify_property_list_changed()
## If [member snap_behavior] is [SNAP_BEHAVIOR.PAGING], this is the draging threshold needed to page to the next carousel item.
@export var paging_requirement : int = 200:
set(val):
val = max(1, val)
if val != paging_requirement:
paging_requirement = val
_adjust_children()
@export_group("Animation Options")
@export_subgroup("Manual")
## The duration of the animation any call to [method go_to_index] will cause, if the animation option is requested.
@export_range(0.001, 2.0, 0.001, "or_greater") var manual_carousel_duration : float = 0.4
## The [enum Tween.TransitionType] of the animation any call to [method go_to_index] will cause, if the animation option is requested.
@export var manual_carousel_transtion_type : Tween.TransitionType
## The [enum Tween.EaseType] of the animation any call to [method go_to_index] will cause, if the animation option is requested.
@export var manual_carousel_ease_type : Tween.EaseType
@export_subgroup("Snap")
## The duration of the animation when snapping to an item.
@export_range(0.001, 2.0, 0.001, "or_greater") var snap_carousel_duration : float = 0.2
## The [enum Tween.TransitionType] of the animation when snapping to an item.
@export var snap_carousel_transtion_type : Tween.TransitionType
## The [enum Tween.EaseType] of the animation when snapping to an item.
@export var snap_carousel_ease_type : Tween.EaseType
@export_group("Drag")
## If [code]true[/code], the user is allowed to drag via their mouse or touch.
@export var can_drag : bool = true:
set(val):
if val != can_drag:
can_drag = val
notify_property_list_changed()
if !val:
_drag_scroll_value = 0
if _is_dragging:
_adjust_children()
## If [code]true[/code], the user is allowed to drag outisde the drawer's bounding box.
## [br][br]
## Also see [member can_drag].
@export var drag_outside : bool = false:
set(val):
if val != drag_outside:
drag_outside = val
@export_subgroup("Limits")
## The max amount a user can drag in either direction. If [code]0[/code], then the user can drag any amount they wish.
@export var drag_limit : int = 0:
set(val):
val = max(0, val)
if val != drag_limit: drag_limit = val
## When dragging, the user will not be able to move past the last or first item, besides for [member border_limit] number of extra pixels.
## [br][br]
## This value is assumed [code]false[/code] is [member display_loop] is [code]true[/code].
@export var enforce_border : bool = false:
set(val):
if val != enforce_border:
enforce_border = val
_adjust_children()
notify_property_list_changed()
## The amount of extra pixels a user can drag past the last and before the first item in the carousel.
## [br][br]
## This property does nothing if enforce_border is [code]false[/code].
@export var border_limit : int = 0:
set(val):
if val != border_limit:
border_limit = val
_adjust_children()
@export_subgroup("Slowdown")
## If [code]true[/code] the carousel will immediately stop when not being dragged. Otherwise, drag speed will be gradually decreased.
## [br][br]
## This property is assumed [code]true[/code] if [member snap_behavior] is set to [SNAP_BEHAVIOR.PAGING]. Also see [member slowdown_drag], [member slowdown_friction], and [member slowdown_cutoff].
@export var hard_stop : bool = true:
set(val):
if val != hard_stop:
hard_stop = val
_end_drag_slowdown()
notify_property_list_changed()
## The percentage multiplier the drag velocity will experience each frame.
## [br][br]
## This property does nothing if [member hard_stop] is [code]true[/code].
@export_range(0.0, 1.0, 0.001) var slowdown_drag : float = 0.9
## The constant decrease the drag velocity will experience each frame.
## [br][br]
## This property does nothing if [member hard_stop] is [code]true[/code].
@export_range(0.0, 5.0, 0.001, "or_greater", "hide_slider") var slowdown_friction : float = 0.1
## The cutoff amount. If drag velocity magnitude drops below this amount, the slowdown has finished.
## [br][br]
## This property does nothing if [member hard_stop] is [code]true[/code].
@export_range(0.01, 10.0, 0.001, "or_greater", "hide_slider") var slowdown_cutoff : float = 0.01
var _scroll_value : int
var _drag_scroll_value : int
var _drag_velocity : float
var _item_count : int = 0
var _item_infos : Array
var _scroll_tween : Tween
var _is_dragging : bool = false
var _last_animation : ANIMATION_TYPE = ANIMATION_TYPE.NONE
var _angle_vec : Vector2
var _mouse_checking : bool
# Public Functions
## Gets the index of the current carousel item.[br]
## If [param with_drag] is [code]true[/code] the current drag will also be considered.[br]
## If [param with_clamp] is [code]true[/code] the index will be looped if [member allow_loop] is true or clamped to a vaild index within the carousel.
func get_carousel_index(with_drag : bool = false, with_clamp : bool = true) -> int:
if _item_count == 0: return -1
var scroll : int = _scroll_value
if with_drag: scroll += _drag_scroll_value
var calculated := floori((float(scroll) / float(_get_relevant_axis())) + 0.5)
if with_clamp:
if allow_loop:
calculated = posmod(calculated, _item_count)
else:
calculated = clampi(calculated, 0, _item_count - 1)
return calculated
## Moves to an item of the given index within the carousel. If an invalid index is given, it will be posmod into a vaild index.
func go_to_index(idx : int, animation : bool = true) -> void:
if _item_count == 0: return
if allow_loop:
idx = (((idx % _item_count) - _item_count) % _item_count)
else:
idx = clamp(idx, 0, _item_count - 1)
if animation:
_create_animation(idx, ANIMATION_TYPE.MANUAL)
else:
_kill_animation()
_scroll_value = -_get_relevant_axis() * idx
_adjust_children()
## Moves to the previous item in the carousel, if there is one.
func prev(animation : bool = true) -> void:
go_to_index(get_carousel_index() - 1, animation)
## Moves to the next item in the carousel, if there is one.
func next(animation : bool = true) -> void:
go_to_index(get_carousel_index() + 1, animation)
## Enacts a manual drag on the carousel. This can be used even if [member can_drag] is [code]false[/code].
## Note that [param from] and [param dir] are considered in local coordinates.
## [br][br]
## Is not affected by [member hard_stop], [member drag_outside], and [member drag_limit].
func flick(from : Vector2, dir : Vector2) -> void:
drag_begin.emit()
_kill_animation()
_end_drag_slowdown()
_handle_drag_angle(dir - from)
_on_drag_release()
## Returns if the carousel is currening scrolling via na animation
func is_animating() -> bool:
return _scroll_tween.is_running()
## Returns if the carousel is currening being dragged by player input.
func being_dragged() -> bool:
return _is_dragging
## Returns the current scroll value.
func get_scroll(with_drag : bool = false) -> int:
if with_drag:
return _scroll_value + _drag_scroll_value
return _scroll_value
## Returns the current number of items in the carousel
func get_item_count() -> int:
return _item_count
# Virtual Functions
## A virtual function that is is called whenever the scroll changes.
func _on_progress(scroll : int) -> void: pass
## A virtual function that is is called whenever the scroll changes, for each visible item in the carousel
func _on_item_progress(item : Control, local_scroll : int, scroll : int, local_index : int, index : int) -> void: pass
func _handle_drag_angle(local_pos : Vector2) -> void:
var projected_scalar : float = -local_pos.dot(_angle_vec) / _angle_vec.length_squared()
_drag_velocity = projected_scalar
_drag_scroll_value += projected_scalar
if drag_limit != 0:
_drag_scroll_value = clampi(_drag_scroll_value, -drag_limit, drag_limit)
if snap_behavior == SNAP_BEHAVIOR.PAGING:
if paging_requirement < _drag_scroll_value:
_drag_scroll_value = 0
var desired := get_carousel_index() + 1
if allow_loop || desired < _item_count:
_create_animation(desired, ANIMATION_TYPE.SNAP)
elif -paging_requirement > _drag_scroll_value:
_drag_scroll_value = 0
var desired := get_carousel_index() - 1
if allow_loop || desired >= 0:
_create_animation(desired, ANIMATION_TYPE.SNAP)
else:
_adjust_children()
# Private Functions
func _get_child_rect(child : Control) -> Rect2:
var child_pos : Vector2 = (size - item_size) * 0.5
var child_size : Vector2
child_size = child.get_combined_minimum_size()
match child.size_flags_horizontal:
SIZE_FILL: child_size.x = item_size.x
SIZE_SHRINK_BEGIN: pass
SIZE_SHRINK_CENTER: child_pos.x += (item_size.x - child_size.x) * 0.5
SIZE_SHRINK_END: child_pos.x += (item_size.x - child_size.x)
match child.size_flags_vertical:
SIZE_FILL: child_size.y = item_size.y
SIZE_SHRINK_BEGIN: pass
SIZE_SHRINK_CENTER: child_pos.y += (item_size.y - child_size.y) * 0.5
SIZE_SHRINK_END: child_pos.y += (item_size.y - child_size.y)
child.size = child_size
child.position = child_pos
return Rect2(child_pos, child_size)
func _get_control_children() -> Array[Control]:
var ret : Array[Control]
ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible))
return ret
func _get_relevant_axis() -> int:
var abs_angle_vec = _angle_vec.abs()
if abs_angle_vec.y >= abs_angle_vec.x:
return (item_size.x / abs_angle_vec.y) + item_seperation
return (item_size.y / abs_angle_vec.x) + item_seperation
func _get_adjusted_scroll() -> int:
var scroll := _scroll_value
if snap_behavior != SNAP_BEHAVIOR.PAGING:
scroll += _drag_scroll_value
if enforce_border:
scroll = clampi(scroll, -border_limit, _get_relevant_axis() * (_item_count - 1) + border_limit)
elif display_loop:
scroll = posmod(scroll, _get_relevant_axis() * _item_count)
return scroll
func _create_animation(idx : int, animation_type : ANIMATION_TYPE) -> void:
_kill_animation()
if _item_count == 0: return
_scroll_tween = create_tween()
var axis := _get_relevant_axis()
var max_scroll := axis * _item_count
if max_scroll == 0: max_scroll = 1
var desired_scroll := posmod(axis * idx, max_scroll)
if allow_loop && display_loop:
_scroll_value = posmod(_scroll_value, max_scroll)
if abs(_scroll_value - desired_scroll) > (max_scroll >> 1):
var left_distance := posmod(_scroll_value - desired_scroll, max_scroll)
var right_distance := posmod(desired_scroll - _scroll_value, max_scroll)
if left_distance < right_distance:
desired_scroll -= max_scroll
else:
desired_scroll += max_scroll
_last_animation = animation_type
match animation_type:
ANIMATION_TYPE.MANUAL:
_scroll_tween.set_ease(manual_carousel_ease_type)
_scroll_tween.set_trans(manual_carousel_transtion_type)
_scroll_tween.tween_method(
_animation_method,
_scroll_value,
desired_scroll,
manual_carousel_duration)
ANIMATION_TYPE.SNAP:
snap_begin.emit()
_scroll_tween.set_ease(snap_carousel_ease_type)
_scroll_tween.set_trans(snap_carousel_transtion_type)
_scroll_tween.tween_method(
_animation_method,
_scroll_value,
desired_scroll,
snap_carousel_duration)
_scroll_tween.tween_callback(_kill_animation)
_scroll_tween.play()
func _animation_method(scroll : int) -> void:
_scroll_value = scroll
_adjust_children()
func _kill_animation() -> void:
if _last_animation == ANIMATION_TYPE.SNAP:
snap_end.emit()
_last_animation = ANIMATION_TYPE.NONE
if _scroll_tween && _scroll_tween.is_running():
_scroll_tween.kill()
func _sort_children() -> void:
_settup_children()
_adjust_children()
func _settup_children() -> void:
var children : Array[Control] = _get_control_children()
_item_count = children.size()
_item_infos.resize(_item_count)
for i : int in range(0, _item_count):
var item_info := ItemInfo.new()
item_info.node = children[i]
item_info.rect = _get_child_rect(children[i])
_item_infos[i] = item_info
func _adjust_children() -> void:
if _item_count == 0: return
var range : Array
var max_local_offset : int
var children : Array[Control] = _get_control_children()
var axis := _get_relevant_axis()
var scroll := _get_adjusted_scroll()
var index : int
var local_scroll : int
var adjustment : int
if axis == 0:
index = 0
local_scroll = 0
adjustment = 0
else:
index = int(scroll / axis)
local_scroll = fposmod(scroll, axis)
adjustment = int(scroll < 0)
index += adjustment & int(local_scroll == 0)
_on_progress(scroll)
if display_loop:
if display_range == -1:
max_local_offset = (_item_count >> 1)
range = range(0, _item_count)
for i : int in range:
_item_infos[i].loaded = false
else:
max_local_offset = (display_range >> 1)
range = range(0, (display_range << 1) + 1)
for i : int in range(0, _item_count):
var item_info : ItemInfo = _item_infos[i]
item_info.loaded = false
item_info.node.visible = false
for item : int in range:
var local_offset := (item >> 1) * (((item & 1) << 1) - 1) + (item & 1)
var local_index := posmod(index + local_offset, _item_count)
var item_info : ItemInfo = _item_infos[local_index]
if item_info.loaded: break
local_offset += adjustment
var rect : Rect2 = item_info.rect
rect.position += _angle_vec * (local_offset * axis - local_scroll)
fit_child_in_rect(item_info.node, rect)
item_info.loaded = true
item_info.node.visible = true
item_info.node.z_index = max_local_offset - abs(local_offset)
_on_item_progress(item_info.node, local_scroll, scroll, item, local_index)
else:
if display_range == -1:
max_local_offset = (_item_count >> 1) + (_item_count & 1) + 1
range = range(0, _item_count)
else:
max_local_offset = display_range
range = range(max(0, index - display_range), min(_item_count, index + display_range + 1))
for info : ItemInfo in _item_infos:
info.node.visible = false
for item : int in range:
var local_index := item - index
var item_info : ItemInfo = _item_infos[item]
local_index += adjustment
var rect : Rect2 = item_info.rect
rect.position += _angle_vec * (local_index * axis - local_scroll)
fit_child_in_rect(item_info.node, rect)
item_info.node.visible = true
item_info.node.z_index = max_local_offset - abs(local_index)
_on_item_progress(item_info.node, local_scroll, scroll, item, local_index)
func _start_drag_slowdown() -> void:
if is_inside_tree() && !get_tree().process_frame.is_connected(_handle_drag_slowdown):
get_tree().process_frame.connect(_handle_drag_slowdown)
func _end_drag_slowdown() -> void:
if abs(_drag_velocity) < slowdown_cutoff:
slowdown_interupted.emit()
_drag_velocity = 0
if snap_behavior == SNAP_BEHAVIOR.SNAP:
_create_animation(get_carousel_index(), ANIMATION_TYPE.SNAP)
if is_inside_tree() && get_tree().process_frame.is_connected(_handle_drag_slowdown):
get_tree().process_frame.disconnect(_handle_drag_slowdown)
func _handle_drag_slowdown() -> void:
if abs(_drag_velocity) < slowdown_cutoff:
slowdown_end.emit()
_end_drag_slowdown()
return
if _drag_velocity > 0:
_drag_velocity = max(0, _drag_velocity - slowdown_friction)
else:
_drag_velocity = min(0, _drag_velocity + slowdown_friction)
_drag_velocity *= slowdown_drag
_scroll_value += _drag_velocity
_adjust_children()
func _init() -> void:
sort_children.connect(_sort_children)
tree_exiting.connect(_end_drag_slowdown)
mouse_exited.connect(_mouse_check)
_angle_vec = Vector2.RIGHT.rotated(deg_to_rad(carousel_angle))
func _ready() -> void:
_settup_children()
if _item_count > 0:
starting_index = posmod(starting_index, _item_count)
go_to_index(-starting_index, false)
func _validate_property(property: Dictionary) -> void:
if property.name == "enforce_border":
if display_loop:
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name == "border_limit":
if display_loop || !enforce_border:
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name == "paging_requirement":
if snap_behavior != SNAP_BEHAVIOR.PAGING:
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name == "hard_stop":
if snap_behavior == SNAP_BEHAVIOR.PAGING:
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name in ["slowdown_drag", "slowdown_friction", "slowdown_cutoff"]:
if hard_stop || snap_behavior == SNAP_BEHAVIOR.PAGING:
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name in ["drag_outside"]:
if !can_drag:
property.usage |= PROPERTY_USAGE_READ_ONLY
func _gui_input(event: InputEvent) -> void:
if event is not InputEventMouse:
return
var has_point := get_viewport_rect().has_point(event.position)
if (
(event is InputEventScreenDrag || event is InputEventMouseMotion) &&
(drag_outside || has_point)
):
if event.pressure == 0:
if _is_dragging: _on_drag_release()
return
if !_is_dragging && has_point:
drag_begin.emit()
_mouse_checking = true
_end_drag_slowdown()
_kill_animation()
_is_dragging = true
_handle_drag_angle(event.relative)
elif (event is InputEventScreenTouch || event is InputEventMouseButton):
if !event.pressed: _on_drag_release()
func _on_drag_release() -> void:
_mouse_checking = false
_is_dragging = false
drag_end.emit()
if snap_behavior != SNAP_BEHAVIOR.PAGING:
_scroll_value = _get_adjusted_scroll()
_drag_scroll_value = 0
if snap_behavior == SNAP_BEHAVIOR.NONE:
if !hard_stop: _start_drag_slowdown()
elif snap_behavior == SNAP_BEHAVIOR.SNAP:
if hard_stop: _create_animation(get_carousel_index(), ANIMATION_TYPE.SNAP)
else: _start_drag_slowdown()
func _mouse_check() -> void:
if _mouse_checking:
_on_drag_release()
func _get_allowed_size_flags_horizontal() -> PackedInt32Array:
return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END]
func _get_allowed_size_flags_vertical() -> PackedInt32Array:
return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END]
# Used to hold data about a carousel item
class ItemInfo:
var node : Control
var rect : Rect2
var loaded : bool
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,277 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name CircularContainer extends Container
## A container that positions children in a ellipse within the bounds of this node.
## Behavior the auto angle setter will exhibit.
enum BOUND_BEHAVIOR {
NONE, ## No end bound for angles
STOP, ## Angles, if exceeding the max, will be hard stopped at the max.
LOOP, ## Angles, if exceeding the max, will loop back to the begining.
MIRRIOR ## Angles, if exceeding the max, will bounce back and forth between the min and max angles.
}
## The horizontal offset of the ellipse's center.
## [br][br]
## [code]0[/code] is fully left and [code]1[/code] is fully right.
@export_range(0, 1) var origin_x : float = 0.5:
set(val):
origin_x = val
queue_sort()
## The vertical offset of the ellipse's center.
## [br][br]
## [code]0[/code] is fully top and [code]1[/code] is fully bottom.
@export_range(0, 1) var origin_y : float = 0.5:
set(val):
origin_y = val
queue_sort()
## The horizontal radius of the ellipse's center.
## [br][br]
## [code]0[/code] is [code]0[/code], [code]0.5[/code] is half of [member Control.size].x, and [code]1[/code] is [member Control.size].x.
@export_range(0, 1) var xRadius : float = 0.5:
set(val):
xRadius = val
queue_sort()
## The vertical radius of the ellipse's center.
## [br][br]
## [code]0[/code] is [code]0[/code], [code]0.5[/code] is half of [member Control.size].y, and [code]1[/code] is [member Control.size].y.
@export_range(0, 1) var yRadius : float = 0.5:
set(val):
yRadius = val
queue_sort()
@export_group("Angles")
## If [code]false[/code], the node will automatically write the angles of children.[br]
## If [code]true[/code], you will be required to manually input angles for each child.
@export var manual : bool = false:
set(val):
if manual != val:
manual = val
notify_property_list_changed()
_calculate_angles()
## The behavior this node will have if the angle exceeds the max set angle in auto-mode.
## [br][br]
## See [member manual] and [member angle_end],
var bound_behavior : BOUND_BEHAVIOR:
set(val):
if bound_behavior != val:
bound_behavior = val
notify_property_list_changed()
_calculate_angles()
## If true, the nodes will be equal-distantantly placed from each on, between the start and end angles. Overides all other [member bound_behavior].
## [br][br]
## See [member manual], [member bound_behavior], [member angle_start], and [member angle_end].
var equal_distant : bool:
set(val):
if equal_distant != val:
equal_distant = val
notify_property_list_changed()
_calculate_angles()
## The angle auto-mode will increment from.
## [br][br]
## See [member manual], [member bound_behavior], [member angle_start], and [member angle_end].
var angle_start : float = 0:
set(val):
if angle_start != val:
angle_start = val
_calculate_angles()
## The angle auto-mode will increment with.
## [br][br]
## See [member manual].
var angle_step : float = 10:
set(val):
if angle_step != val:
angle_step = val
_calculate_angles()
## The angle auto-mode will increment to.
## [br][br]
## See [member manual] and [member bound_behavior].
var angle_end : float = 360:
set(val):
if angle_end != val:
angle_end = val
_calculate_angles()
@export_storage var _container_angles : PackedFloat32Array
## The list of angles this ndoe uses to position each child, within the order they are positions in the tree.
var angles : PackedFloat32Array:
set(val):
_container_angles.resize(max(_get_control_children().size(), val.size()))
for i in range(0, val.size()):
_container_angles[i] = deg_to_rad(val[i])
for i in range(val.size(), _container_angles.size()):
_container_angles[i] = 0
_fix_childrend()
get:
var ret : PackedFloat32Array
ret.resize(_container_angles.size())
for i in range(0, _container_angles.size()):
ret[i] = rad_to_deg(_container_angles[i])
return ret
func _init() -> void:
sort_children.connect(_fix_childrend)
child_order_changed.connect(_childrend_changed)
func _ready() -> void:
_fix_childrend()
func _childrend_changed() -> void:
_calculate_angles()
_fix_childrend()
func _get_control_children() -> Array[Control]:
var ret : Array[Control]
ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible))
return ret
func _fix_childrend() -> void:
var children := _get_control_children()
for index : int in range(0, children.size()):
var child : Control = children[index]
if child: _fix_child(child, index)
func _fix_child(child : Control, index : int) -> void:
if _container_angles.is_empty(): return
child.reset_size()
# Calculates child position
var child_size := child.get_combined_minimum_size()
var child_pos := -(child_size * 0.5) + (Vector2(origin_x, origin_y) + (Vector2(xRadius, yRadius) * Vector2(cos(_container_angles[index]), sin(_container_angles[index])))) * size
# Keeps children in the rect's top-left boundards
if child_pos.x < 0:
child_pos.x = 0
if child_pos.y < 0:
child_pos.y = 0
# Keeps children in the rect's bottom-right boundards
if child_pos.x + child_size.x > size.x:
child_pos.x += size.x - (child_pos.x + child_size.x)
if child_pos.y + child_size.y > size.y:
child_pos.y += size.y - (child_pos.y + child_size.y)
if child_pos.y + child_size.y > size.y:
child_size.y = size.y - child_pos.y
fit_child_in_rect(child, Rect2(child_pos, child_size))
func _get_property_list() -> Array[Dictionary]:
var properties : Array[Dictionary]
if manual:
properties.append({
"name": "angles",
"type": TYPE_PACKED_FLOAT32_ARRAY,
"usage" : PROPERTY_USAGE_EDITOR
})
else:
var unbounded = PROPERTY_USAGE_READ_ONLY if bound_behavior == BOUND_BEHAVIOR.NONE else 0
var allow_step = PROPERTY_USAGE_READ_ONLY if equal_distant && bound_behavior == BOUND_BEHAVIOR.STOP else 0
properties.append({
"name": "bound_behavior",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": ",".join(BOUND_BEHAVIOR.keys()),
"usage" : PROPERTY_USAGE_DEFAULT
})
properties.append({
"name": "equal_distant",
"type": TYPE_BOOL,
"usage" : PROPERTY_USAGE_DEFAULT
})
properties.append({
"name": "Values",
"type": TYPE_NIL,
"usage" : PROPERTY_USAGE_SUBGROUP,
"hint_string": ""
})
properties.append({
"name": "angle_start",
"type": TYPE_FLOAT,
"usage" : PROPERTY_USAGE_DEFAULT
})
properties.append({
"name": "angle_step",
"type": TYPE_FLOAT,
"usage" : PROPERTY_USAGE_DEFAULT | allow_step
})
properties.append({
"name": "angle_end",
"type": TYPE_FLOAT,
"usage" : PROPERTY_USAGE_DEFAULT | unbounded
})
return properties
func _property_can_revert(property: StringName) -> bool:
match property:
"bound_behavior":
return bound_behavior != BOUND_BEHAVIOR.NONE
"equal_distant":
return equal_distant
"angle_start":
return angle_start != 0
"angle_step":
return angle_step != 10
"angle_end":
return angle_end != 360
return false
func _property_get_revert(property: StringName) -> Variant:
match property:
"bound_behavior":
return BOUND_BEHAVIOR.NONE
"equal_distant":
return false
"angle_start":
return 0
"angle_step":
return 10
"angle_end":
return 360
return null
func _calculate_angles() -> void:
if manual: return
var count := _get_control_children().size()
_container_angles.resize(count)
var start := deg_to_rad(angle_start)
var end := deg_to_rad(angle_end)
var step : float
if equal_distant:
if count != 0: step = deg_to_rad((angle_end - angle_start) / (count - 1))
else:
step = deg_to_rad(angle_step)
var inc_func : Callable
if equal_distant || bound_behavior == BOUND_BEHAVIOR.NONE:
inc_func = func(i : int): return (i * step) + start
elif bound_behavior == BOUND_BEHAVIOR.STOP:
inc_func = func(i : int): return min(i * step, end) + start
elif bound_behavior == BOUND_BEHAVIOR.LOOP:
inc_func = func(i : int): return fmod(i * step, end) + start
elif bound_behavior == BOUND_BEHAVIOR.MIRRIOR:
inc_func = func(i : int): return abs(fmod((i * step) - end, 2 * end) - end) + start
for i : int in range(0, _container_angles.size()):
_container_angles[i] = fposmod(inc_func.call(i), TAU)
_fix_childrend()
func _get_allowed_size_flags_horizontal() -> PackedInt32Array:
return []
func _get_allowed_size_flags_vertical() -> PackedInt32Array:
return []
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,798 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name Drawer extends Container
## A [Container] node used for easy UI Drawers.
## A flag enum used to classify which input type is allowed.
enum ActionMode {
ACTION_MODE_BUTTON_NONE = 0, ## Allows no input
ACTION_MODE_BUTTON_PRESS = 1, ## Toggles the drawer on tap/click press
ACTION_MODE_BUTTON_RELEASE = 2, ## Toggles the drawer on tap/click release
ACTION_MODE_BUTTON_DRAG = 4, ## Allows user to drag the drawer
}
## An enum used to classify where input is accepted.
enum InputAreaMode {
Nowhere = 0, ## No input is alloed anywhere on the screen.
Anywhere = 1, ## Input accepted anywere on the screen.
WithinBounds = 2, ## Input is accepted only within this node's rect.
ExcludeDrawer = 3, ## Input is accepted anywhere except on the drawer's rect.
WithinEmptyBounds = 4, ## Input is accepted only within this node's rect, outside of the drawer's rect.
}
## An enum used to classify when dragging is allowed.
enum DragMode {
NEVER = 0, ## No dragging allowed.
ON_OPEN = 1, ## Dragging is allowed to open the drawer.
ON_CLOSE = 2, ## Dragging is allowed to close the drawer.
ON_OPEN_OR_CLOSE = 0b11 ## Dragging is allowed to open or close the drawer.
}
## Emited when drawer is begining an opening/closing animation.
## [br][br]
## Also see: [member state], [method toggle_drawer].
signal slide_begin
## Emited when drawer is ending an opening/closing animation.
## [br][br]
## Also see: [member state], [method toggle_drawer].
signal slide_end
## Emited when state has changed, but animation has not began.
## [br][br]
## Also see: [member state], [method toggle_drawer].
signal state_toggle_begin(toggle : bool)
## Emited when state has changed and animation has finished.
## [br][br]
## Also see: [member state], [method toggle_drawer].
signal state_toggle_end(toggle : bool)
## Emited when drag has began.
## [br][br]
## Also see: [member allow_drag].
signal drag_start
## Emited when drag has ended.
## [br][br]
## Also see: [member allow_drag].
signal drag_end
@export_storage var _state : bool
## The state of the drawer. If [code]true[/code], the drawer is open. Otherwise closed.
## [br][br]
## Also see: [method toggle_drawer].
var state : bool:
get: return _state
set(val):
if _state != val:
_toggle_drawer(val)
#@export_group("Drawer Angle")
## The angle in which the drawer will open/close from.
## [br][br]
## Also see: [member drawer_angle_axis_snap].
var drawer_angle : float = 0.0:
set(val):
if drawer_angle != val:
drawer_angle = val
_angle_vec = Vector2.RIGHT.rotated(deg_to_rad(drawer_angle))
_kill_animation()
_calculate_childrend()
## If [code]true[/code], the drawer will be snapped to move as strictly cardinally as possible.
## [br][br]
## Also see: [member drawer_angle].
var drawer_angle_axis_snap : bool:
set(val):
if drawer_angle_axis_snap != val:
drawer_angle_axis_snap = val
_kill_animation()
_calculate_childrend()
#@export_group("Drawer Span")
## If [code]false[/code], [member drawer_width] is equal to a ratio of this node's [Control.size]'s x component.
## [br]. Else, [member drawer_width] is directly editable.
var drawer_width_by_pixel : bool:
set(val):
if val != drawer_width_by_pixel:
drawer_width_by_pixel = val
if val:
drawer_width *= size.x
else:
if size.x == 0:
drawer_width = 0
else:
drawer_width /= size.x
notify_property_list_changed()
## The width of the drawer.
## [br][br]
## Also see: [member drawer_width_by_pixel].
var drawer_width : float = 1:
set(val):
if val != drawer_width:
drawer_width = val
_calculate_childrend()
## If [code]false[/code], [member drawer_height] is equal to a ratio of this node's [Control.size]'s y component.
## [br]. Else, [member drawer_height] is directly editable.
var drawer_height_by_pixel : bool:
set(val):
if val != drawer_height_by_pixel:
drawer_height_by_pixel = val
if val:
drawer_height *= size.y
else:
if size.y == 0:
drawer_height = 0
else:
drawer_height /= size.y
notify_property_list_changed()
## The height of the drawer.
## [br][br]
## Also see: [member drawer_height_by_pixel].
var drawer_height : float = 1:
set(val):
if val != drawer_height:
drawer_height = val
_calculate_childrend()
#@export_group("Input Options")
## A flag enum used to classify which input type is allowed.
var action_mode : ActionMode = ActionMode.ACTION_MODE_BUTTON_PRESS:
set(val):
if val != action_mode:
action_mode = val
_is_dragging = false
#@export_subgroup("Margins")
## Extra pixels to where the open drawer lies when open.
var open_margin : int = 0:
set(val):
if val != open_margin:
open_margin = val
_calculate_childrend()
## Extra pixels to where the open drawer lies when closed.
var close_margin : int = 0:
set(val):
if val != close_margin:
close_margin = val
_calculate_childrend()
#@export_subgroup("Drag Options")
## Permissions on how the user may drag to open/close the drawer.
## [br][br]
## Also see: [member allow_drag], [member smooth_drag].
var allow_drag : DragMode = DragMode.ON_OPEN_OR_CLOSE
## If [code]true[/code], the drawer will react while the user drags.
var smooth_drag : bool = true
## The amount of extra the user is allowed to drag (in the open direction) before being stopped.
var drag_give : int = 0
#@export_subgroup("Open Input")
## A node to determine where vaild input, when closed, may start at.
## [br][br]
## Also see: [member allow_drag].
var open_bounds : InputAreaMode = InputAreaMode.WithinEmptyBounds
## The minimum amount you need to drag before your drag is considered to have closed the drawer.
## [br][br]
## Also see: [member allow_drag].
var open_drag_threshold : int = 50:
set(val):
val = max(0, val)
if val != open_drag_threshold:
open_drag_threshold = val
#@export_subgroup("Close Input")
## A node to determine where vaild input, when open, may start at.
## [br][br]
## Also see: [member allow_drag].
var close_bounds : InputAreaMode = InputAreaMode.WithinEmptyBounds
## The minimum amount you need to drag before your drag is considered to have opened the drawer.
## [br][br]
## Also see: [member allow_drag].
var close_drag_threshold : int = 50:
set(val):
val = max(0, val)
if val != close_drag_threshold:
close_drag_threshold = val
#@export_group("Animation")
#@export_subgroup("Manual Animation")
## The [enum Tween.TransitionType] used when manually opening and closing drawer.
## [br][br]
## Also see: [member state], [method toggle_drawer].
var manual_drawer_translate : Tween.TransitionType
## The [enum Tween.EaseType] used when manually opening and closing drawer.
## [br][br]
## Also see: [member state], [method toggle_drawer].
var manual_drawer_ease : Tween.EaseType
## The animation duration used when manually opening and closing drawer.
## [br][br]
## Also see: [member state], [method toggle_drawer].
var manual_drawer_duration : float = 0.2
#@export_subgroup("Drag Animation")
## The [enum Tween.TransitionType] used when snapping after a drag.
var drag_drawer_translate : Tween.TransitionType
## The [enum Tween.EaseType] used when snapping after a drag.
var drag_drawer_ease : Tween.EaseType
## The animation duration used when snapping after a drag.
var drag_drawer_duration : float = 0.2
var _min_size : Vector2
var _angle_vec : Vector2
var _animation_tween : Tween
var _current_progress : float
var _drag_value : float
var _is_dragging : bool
var _has_dragged : bool
var _inner_offset : Vector2
var _outer_offset : Vector2
var _max_offset : float
## Returns if the drawer is currently open.
func is_open() -> bool:
return get_progress_adjusted() > 0.5
## Returns if the drawer is expected to be open.
func is_open_expected() -> bool:
return _state
## Returns if the drawer is currently animating.
func is_animating() -> bool:
return _animation_tween && _animation_tween.is_running()
## Returns the size of the drawer.
func get_drawer_size() -> Vector2:
var ret := Vector2(drawer_width, drawer_height)
if !drawer_width_by_pixel:
ret.x *= size.x
if !drawer_height_by_pixel:
ret.y *= size.y
return ret.max(_min_size)
## Returns the offsert the drawer has, compared to this node's local position.
func get_drawer_offset(with_drag : bool = false) -> Vector2:
return _get_drawer_offset(_inner_offset, _outer_offset, with_drag)
## Returns the rect the drawer has, compared to this node's local position.
func get_drawer_rect(with_drag : bool = false) -> Rect2:
return Rect2(get_drawer_offset(with_drag), get_drawer_size())
## Gets the current progress the drawer is in animations.
## Returns the value in pixel distance.
func get_progress(include_drag : bool = false, with_clamp : bool = true) -> float:
var ret : float = _current_progress
if include_drag:
ret += _drag_value
if with_clamp:
ret = clampf(ret, -drag_give, _max_offset)
return ret
## Gets the percentage of the drawer's current position between being closed and opened.
## [code]0.0[/code] when closed and [code]1.0[/code] when opened.
func get_progress_adjusted(include_drag : bool = false, with_clamp : bool = true) -> float:
if _max_offset == 0.0: return 0.0
return get_progress(include_drag, with_clamp) / _max_offset
func _get_relevant_axis() -> float:
var drawer_size := get_drawer_size()
var abs_angle_vec = _angle_vec.abs()
if abs_angle_vec.y >= abs_angle_vec.x:
return (drawer_size.x / abs_angle_vec.y)
return (drawer_size.y / abs_angle_vec.x)
func _get_control_children() -> Array[Control]:
var ret : Array[Control]
ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible))
return ret
func _calculate_childrend() -> void:
_find_offsets()
_current_progress = _max_offset * float(_state)
_adjust_children()
func _adjust_children() -> void:
var rect := get_drawer_rect(true)
for child : Control in _get_control_children():
fit_child_in_rect(child, rect)
func _find_minimum_size() -> Vector2:
var min_size : Vector2 = Vector2.ZERO
for child : Control in _get_control_children():
min_size = min_size.max(child.get_combined_minimum_size())
return min_size
func _find_offsets() -> void:
var drawer_size := get_drawer_size()
var distances_to_intersection_point := (size / _angle_vec).abs()
var inner_distance := minf(distances_to_intersection_point.x, distances_to_intersection_point.y)
var inner_point : Vector2 = (inner_distance * _angle_vec + (size - drawer_size)) * 0.5
_inner_offset = inner_point.maxf(0).min(size - drawer_size)
if drawer_angle_axis_snap:
var half_drawer_size := drawer_size * 0.5
var inner_point_half := inner_point + half_drawer_size
_outer_offset = inner_point
if abs(inner_point_half.x - size.x) < 0.01:
_outer_offset.x += half_drawer_size.x
elif abs(inner_point_half.x) < 0.01:
_outer_offset.x -= half_drawer_size.x
if abs(inner_point_half.y - size.y) < 0.01:
_outer_offset.y += half_drawer_size.y
elif abs(inner_point_half.y) < 0.01:
_outer_offset.y -= half_drawer_size.y
else:
var distances_to_outer_center := ((size + drawer_size) / _angle_vec).abs()
var outer_distance := minf(distances_to_outer_center.x, distances_to_outer_center.y)
_outer_offset = (outer_distance * _angle_vec + (size - drawer_size)) * 0.5
_max_offset = (_outer_offset - _inner_offset).length()
_inner_offset = (_inner_offset + _angle_vec * open_margin).floor()
_outer_offset = (_outer_offset - _angle_vec * close_margin).floor()
## Allows opening and closing the drawer.
## [br][br]
## Also see: [member state] and [method force_drawer].
func toggle_drawer(open : bool) -> void:
_toggle_drawer(open)
func _toggle_drawer(open : bool, drag_animate : bool = false) -> void:
slide_begin.emit()
_animate_to_progress(float(open), drag_animate)
_animation_tween.tween_callback(slide_end.emit)
if _state != open:
state_toggle_begin.emit(open)
_animation_tween.tween_callback(state_toggle_end.emit.bind(open))
_state = open
func _animate_to_progress(
to_progress : float,
drag_animate : bool = false
) -> void:
_kill_animation()
_animation_tween = create_tween()
if drag_animate:
_animation_tween.set_trans(drag_drawer_translate)
_animation_tween.set_ease(drag_drawer_ease)
_animation_tween.tween_method(
_animation_method,
get_progress(true),
to_progress * _max_offset,
drag_drawer_duration
)
else:
_animation_tween.set_trans(manual_drawer_translate)
_animation_tween.set_ease(manual_drawer_ease)
_animation_tween.tween_method(
_animation_method,
get_progress(true),
to_progress * _max_offset,
manual_drawer_duration
)
func _kill_animation() -> void:
if _animation_tween && _animation_tween.is_running():
_animation_tween.kill()
func _animation_method(progress : float) -> void:
_current_progress = progress
_progress_changed(get_progress_adjusted())
_adjust_children()
## Allows opening and closing the drawer without animation.
## [br][br]
## Also see: [member state] and [method toggle_drawer].
func force_drawer(open : bool) -> void:
_kill_animation()
_state = open
_animation_method(float(open) * _max_offset)
func _init() -> void:
resized.connect(_calculate_childrend)
sort_children.connect(_calculate_childrend)
_angle_vec = Vector2.RIGHT.rotated(deg_to_rad(drawer_angle))
func _get_minimum_size() -> Vector2:
_min_size = _find_minimum_size()
return _min_size
func _get_property_list() -> Array[Dictionary]:
var ret : Array[Dictionary]
ret.append({
"name": "state",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_EDITOR
})
ret.append({
"name": "Drawer Angle",
"type": TYPE_NIL,
"hint_string": "drawer_",
"usage": PROPERTY_USAGE_GROUP
})
ret.append({
"name": "drawer_angle",
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0, 360, 0.001, or_less, or_greater",
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "drawer_angle_axis_snap",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "Drawer Span",
"type": TYPE_NIL,
"hint_string": "drawer_",
"usage": PROPERTY_USAGE_GROUP
})
ret.append({
"name": "drawer_width_by_pixel",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "drawer_width",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT
}.merged({} if drawer_width_by_pixel else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0, 1, 0.001, or_less, or_greater",
}))
ret.append({
"name": "drawer_height_by_pixel",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "drawer_height",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT
}.merged({} if drawer_height_by_pixel else {
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0, 1, 0.001, or_less, or_greater",
}))
ret.append({
"name": "Input Options",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP
})
ret.append({
"name": "action_mode",
"type": TYPE_INT,
"hint": PROPERTY_HINT_FLAGS,
"hint_string": "Press Action:1,Release Action:2,Drag Action:4",
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "Margins",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_SUBGROUP
})
ret.append({
"name": "open_margin",
"type": TYPE_INT,
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "close_margin",
"type": TYPE_INT,
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "Drag Options",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_SUBGROUP
})
ret.append({
"name": "allow_drag",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": _convert_to_enum(DragMode.keys()),
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "smooth_drag",
"type": TYPE_BOOL,
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "drag_give",
"type": TYPE_INT,
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "Open Input",
"type": TYPE_NIL,
"hint_string": "",
"usage": PROPERTY_USAGE_SUBGROUP
})
ret.append({
"name": "open_bounds",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": _convert_to_enum(InputAreaMode.keys()),
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "open_drag_threshold",
"type": TYPE_INT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0, 1, 1, or_greater",
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "Close Input",
"type": TYPE_NIL,
"hint_string": "",
"usage": PROPERTY_USAGE_SUBGROUP
})
ret.append({
"name": "close_bounds",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": _convert_to_enum(InputAreaMode.keys()),
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "close_drag_threshold",
"type": TYPE_INT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0, 1, 1, or_greater",
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "Animation",
"type": TYPE_NIL,
"usage": PROPERTY_USAGE_GROUP
})
ret.append({
"name": "Manual Animation",
"type": TYPE_NIL,
"hint_string": "manual_drawer_",
"usage": PROPERTY_USAGE_SUBGROUP
})
ret.append({
"name": "manual_drawer_translate",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": _get_enum_string("Tween", "TransitionType"),
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "manual_drawer_ease",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": _get_enum_string("Tween", "EaseType"),
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "manual_drawer_duration",
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0, 1, 0.001, or_greater",
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "Drag Animation",
"type": TYPE_NIL,
"hint_string": "drag_drawer_",
"usage": PROPERTY_USAGE_SUBGROUP
})
ret.append({
"name": "drag_drawer_translate",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": _get_enum_string("Tween", "TransitionType"),
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "drag_drawer_ease",
"type": TYPE_INT,
"hint": PROPERTY_HINT_ENUM,
"hint_string": _get_enum_string("Tween", "EaseType"),
"usage": PROPERTY_USAGE_DEFAULT
})
ret.append({
"name": "drag_drawer_duration",
"type": TYPE_FLOAT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0, 1, 0.001, or_greater",
"usage": PROPERTY_USAGE_DEFAULT
})
return ret
func _property_can_revert(property: StringName) -> bool:
return property in [
"state",
"drawer_angle",
"drawer_angle_axis_snap",
"drawer_width_by_pixel",
"drawer_width",
"drawer_height_by_pixel",
"drawer_height",
"action_mode",
"drag_give",
"open_margin",
"close_margin",
"allow_drag",
"smooth_drag",
"open_bounds",
"open_drag_threshold",
"close_bounds",
"close_drag_threshold",
"manual_drawer_translate",
"manual_drawer_ease",
"manual_drawer_duration",
"drag_drawer_translate",
"drag_drawer_ease",
"drag_drawer_duration"
]
func _property_get_revert(property: StringName) -> Variant:
match property:
"smooth_drag":
return true
"state", "drawer_width_by_pixel", "drawer_height_by_pixel", "drawer_angle_axis_snap":
return false
"drag_give", "open_margin", "close_margin":
return 0
"drawer_angle":
return 0.0
"manual_drawer_duration", "drag_drawer_duration":
return 0.2
"open_drag_threshold":
return 50
"close_drag_threshold":
return 50
"drawer_width":
return size.x if drawer_width_by_pixel else 1.0
"drawer_height":
return size.y if drawer_height_by_pixel else 1.0
"action_mode":
return ActionMode.ACTION_MODE_BUTTON_PRESS
"allow_drag":
return DragMode.ON_OPEN_OR_CLOSE
"open_bounds":
return InputAreaMode.WithinEmptyBounds
"close_bounds":
return InputAreaMode.WithinEmptyBounds
"manual_drawer_translate", "drag_drawer_translate":
return Tween.TransitionType.TRANS_LINEAR
"manual_drawer_ease", "drag_drawer_ease":
return Tween.EaseType.EASE_IN
return null
func _get_enum_string(className : StringName, enumName : StringName) -> String:
var ret : String
for constant_name in ClassDB.class_get_enum_constants(className, enumName):
var constant_value: int = ClassDB.class_get_integer_constant(className, constant_name)
ret += "%s:%d, " % [constant_name, constant_value]
return ret.left(-2).replace("_", " ").capitalize().replace(", ", ",")
func _convert_to_enum(strs : PackedStringArray) -> String:
return ", ".join(strs).replace("_", " ").capitalize().replace(", ", ",")
func _confirm_input_accept(event : InputEvent, drag : bool = false) -> bool:
if mouse_filter == MouseFilter.MOUSE_FILTER_IGNORE: return false
var boundType : InputAreaMode
if _state:
boundType = close_bounds
if drag && !(allow_drag & DragMode.ON_CLOSE):
return false
else:
boundType = open_bounds
if drag && !(allow_drag & DragMode.ON_OPEN):
return false
match boundType:
InputAreaMode.Nowhere:
return false
InputAreaMode.Anywhere:
pass
InputAreaMode.WithinBounds:
if !get_rect().has_point(event.position):
return false
InputAreaMode.ExcludeDrawer:
if get_drawer_rect().has_point(event.position):
if mouse_filter == MouseFilter.MOUSE_FILTER_STOP:
get_viewport().set_input_as_handled()
return false
InputAreaMode.WithinEmptyBounds:
if get_rect().intersection(get_drawer_rect()).has_point(event.position):
if mouse_filter == MouseFilter.MOUSE_FILTER_STOP:
get_viewport().set_input_as_handled()
return false
if mouse_filter == MouseFilter.MOUSE_FILTER_STOP:
get_viewport().set_input_as_handled()
return true
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton || event is InputEventScreenTouch:
if event.pressed:
if !_is_dragging:
if action_mode & ActionMode.ACTION_MODE_BUTTON_PRESS:
if !_confirm_input_accept(event, false): return
_toggle_drawer(!is_open())
return
if action_mode & ActionMode.ACTION_MODE_BUTTON_DRAG:
if !_confirm_input_accept(event, true): return
drag_start.emit()
_is_dragging = true
else:
if action_mode & ActionMode.ACTION_MODE_BUTTON_RELEASE:
if _confirm_input_accept(event, false) && !_has_dragged:
_toggle_drawer(!is_open())
_has_dragged = false
_is_dragging = false
_drag_value = 0.0
return
if action_mode & ActionMode.ACTION_MODE_BUTTON_DRAG:
if _is_dragging:
drag_end.emit()
if is_open():
_toggle_drawer(_drag_value > -open_drag_threshold, true)
else:
_toggle_drawer(_drag_value > close_drag_threshold, true)
set_deferred("_has_dragged", false)
set_deferred("_is_dragging", false)
set_deferred("_drag_value", 0.0)
return
if _is_dragging:
if event is InputEventMouseMotion || event is InputEventScreenDrag:
var projected_scalar : float = event.relative.dot(_angle_vec) / _angle_vec.length_squared()
_drag_value += projected_scalar
if is_zero_approx(_drag_value):
_has_dragged = true
_progress_changed(get_progress_adjusted(true))
if smooth_drag: _adjust_children()
# Overload Functions
## Used by [method get_drawer_offset] to calculate the offset of the drawer, given the current progress.
## Overload this method to create custom opening/closing behavior.
func _get_drawer_offset(inner_offset : Vector2, outer_offset : Vector2, with_drag : bool = false) -> Vector2:
#return (outer_offset - inner_offset) * get_progress_adjusted(with_drag) + inner_offset
return inner_offset.lerp(outer_offset, get_progress_adjusted(with_drag))
# Virtual Functions
## A virtual function that is is called whenever the drawer progress changes.
func _progress_changed(progress : float) -> void: pass
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,222 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name PaddingContainer extends Container
## A [Container] that provides percentage and numerical padding to it's children.
## If [code]true[/code], this [Container]'s minimum size will update according to it's
## children and numerical pixel padding.
@export var minimum_size : bool = true:
set(val):
if minimum_size != val:
minimum_size = val
update_minimum_size()
## The percentage left padding.
var child_anchor_left : float = 0:
set(val):
if child_anchor_left != val:
child_anchor_left = val
child_anchor_right = max(val, child_anchor_right)
## The percentage top padding.
var child_anchor_top : float = 0:
set(val):
if child_anchor_top != val:
child_anchor_top = val
child_anchor_bottom = max(val, child_anchor_bottom)
## The percentage right padding.
var child_anchor_right : float = 1:
set(val):
if child_anchor_right != val:
child_anchor_right = val
child_anchor_left = min(val, child_anchor_left)
## The percentage bottom padding.
var child_anchor_bottom : float = 1:
set(val):
if child_anchor_bottom != val:
child_anchor_bottom = val
child_anchor_top = min(val, child_anchor_top)
## The numerical pixel left padding.
var child_offset_left : int = 0:
set(val):
if child_offset_left != val:
child_offset_left = val
update_minimum_size()
## The numerical pixel top padding.
var child_offset_top : int = 0:
set(val):
if child_offset_top != val:
child_offset_top = val
update_minimum_size()
## The numerical pixel right padding.
var child_offset_right : int = 0:
set(val):
if child_offset_right != val:
child_offset_right = val
update_minimum_size()
## The numerical pixel bottom padding.
var child_offset_bottom : int = 0:
set(val):
if child_offset_bottom != val:
child_offset_bottom = val
update_minimum_size()
func _init() -> void:
sort_children.connect(_handel_resize)
func _ready() -> void:
_handel_resize()
func _get_property_list() -> Array[Dictionary]:
var properties : Array[Dictionary] = []
properties.append({
"name" = "Anchors",
"type" = TYPE_NIL,
"usage" = PROPERTY_USAGE_GROUP,
"hint_string" = "child_anchor_"
})
properties.append({
"name": "child_anchor_left",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_greater,or_less"
})
properties.append({
"name": "child_anchor_top",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_greater,or_less"
})
properties.append({
"name": "child_anchor_right",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_greater,or_less"
})
properties.append({
"name": "child_anchor_bottom",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT,
"hint": PROPERTY_HINT_RANGE,
"hint_string": "0,1,0.001,or_greater,or_less"
})
properties.append({
"name" = "Offsets",
"type" = TYPE_NIL,
"usage" = PROPERTY_USAGE_GROUP,
"hint_string" = "child_offset_"
})
properties.append({
"name": "child_offset_left",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT
})
properties.append({
"name": "child_offset_top",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT
})
properties.append({
"name": "child_offset_right",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT
})
properties.append({
"name": "child_offset_bottom",
"type": TYPE_FLOAT,
"usage": PROPERTY_USAGE_DEFAULT
})
return properties
func _property_can_revert(property: StringName) -> bool:
if property in [
"child_anchor_left",
"child_anchor_top",
]:
return self[property] != 0.0
elif property in [
"child_anchor_right",
"child_anchor_bottom",
]:
return self[property] != 1.0
elif property in [
"child_offset_left",
"child_offset_top",
"child_offset_right",
"child_offset_bottom"
]:
return self[property] != 0
return false
func _property_get_revert(property: StringName) -> Variant:
if property in [
"child_anchor_left",
"child_anchor_top",
]:
return 0.0
elif property in [
"child_anchor_right",
"child_anchor_bottom",
]:
return 1.0
elif property in [
"child_offset_left",
"child_offset_top",
"child_offset_right",
"child_offset_bottom"
]:
return 0
return null
func _get_minimum_size() -> Vector2:
if !minimum_size: return Vector2.ZERO
var min : Vector2
for child : Node in get_children():
if child is Control:
min = min.max(child.get_combined_minimum_size())
min += Vector2(
child_offset_left + child_offset_right,
child_offset_top + child_offset_bottom
)
return min
func _handel_resize() -> void:
for child in get_children():
if child is Control:
child.set_anchor_and_offset(
SIDE_LEFT,
child_anchor_left,
child_offset_left
)
child.set_anchor_and_offset(
SIDE_TOP,
child_anchor_top,
child_offset_top
)
child.set_anchor_and_offset(
SIDE_RIGHT,
child_anchor_right,
-child_offset_right
)
child.set_anchor_and_offset(
SIDE_BOTTOM,
child_anchor_bottom,
-child_offset_bottom
)
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,188 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name ProportionalContainer extends Container
## A container that preserves the proportions of its [member ancher] size.
## [br][br]
## [b]WARNING[b]: Is this can cause crashes if misused. Try to use [PaddingContainer] instead, unless required.
## The method this node will change in proportion of its [member ancher] size.
enum PROPORTION_MODE {
NONE = 0b000, ## No action. Minimum size will be set at [constant Vector2.ZERO].
WIDTH = 0b101, ## Same as [WIDTH_PROPORTION], but also sets children height to be equal to the [member ancher]'s size's height.
WIDTH_PROPORTION = 0b001, ## Sets the minimum width to be equal to the [member ancher] width multipled by [member horizontal_ratio].
HEIGHT = 0b110, ## Same as [HEIGHT_PROPORTION], but also sets children height to be equal to the [member ancher]'s size's width.
HEIGHT_PROPORTION = 0b010, ## Sets the minimum height to be equal to the [member ancher] height multipled by [member vertical_ratio].
BOTH = 0b011 ## Sets the minimum size to be equal to the [member ancher] size multipled by [member horizontal_ratio] and [member vertical_ratio] respectively.
}
@export_group("Ancher")
## The ancher node this container proportions itself to. Is used if [member ancher_to_parent] is [code]false[/code].
## [br][br]
## If [code]null[/code], then this container proportions itself to it's parent control size.
@export var ancher : Control:
set(val):
if ancher != val:
if ancher && ancher.resized.is_connected(_sort_children):
ancher.resized.disconnect(_sort_children)
if val && !val.resized.is_connected(_sort_children):
val.resized.connect(_sort_children)
ancher = val
queue_sort()
@export_group("Proportion")
## The proportion mode used to scale itself to the [member ancher].
@export var mode : PROPORTION_MODE = PROPORTION_MODE.NONE:
set(val):
if mode != val:
mode = val
notify_property_list_changed()
queue_sort()
## The multiplicative of this node's width to the [member ancher] width.
@export_range(0., 1., 0.001, "or_greater") var horizontal_ratio : float = 1.:
set(val):
if horizontal_ratio != val:
horizontal_ratio = val
queue_sort()
## The multiplicative of this node's height to the [member ancher] height.
@export_range(0., 1., 0.001, "or_greater") var vertical_ratio : float = 1.:
set(val):
if vertical_ratio != val:
vertical_ratio = val
queue_sort()
var _min_size : Vector2
var _ignore_resize : bool
func _init() -> void:
layout_mode = 0
sort_children.connect(_sort_children)
func _ready() -> void:
_sort_children()
func _validate_property(property: Dictionary) -> void:
if property.name in [
"layout_mode",
"size",
]:
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name == "horizontal_ratio":
if !(mode & PROPORTION_MODE.WIDTH_PROPORTION):
property.usage |= PROPERTY_USAGE_READ_ONLY
elif property.name == "vertical_ratio":
if !(mode & PROPORTION_MODE.HEIGHT_PROPORTION):
property.usage |= PROPERTY_USAGE_READ_ONLY
func _get_minimum_size() -> Vector2:
return _min_size
func _sort_children() -> void:
if _ignore_resize: return
_ignore_resize = true
if mode != PROPORTION_MODE.NONE:
# Sets the min size according to it's dimentions and proportion mode
var ancher_size : Vector2 = get_parent_area_size()
if ancher: ancher_size = ancher.size
var child_min_size := _get_children_min_size()
var _old_min_size := _min_size
if mode & PROPORTION_MODE.WIDTH != 0:
_min_size.x = ancher_size.x * horizontal_ratio
else:
_min_size.x = child_min_size.x
if mode & PROPORTION_MODE.HEIGHT != 0:
_min_size.y = ancher_size.y * vertical_ratio
else:
_min_size.y = child_min_size.y
if _min_size != _old_min_size:
update_minimum_size()
elif _min_size != Vector2.ZERO:
# Sets min size to default
_min_size = Vector2.ZERO
update_minimum_size()
_ignore_resize = false
_fit_children()
func _fit_children() -> void:
for child : Control in _get_control_children():
_fit_child(child)
func _fit_child(child : Control) -> void:
var child_size := child.get_minimum_size()
var ancher_size : Vector2 = ancher.size if ancher else get_parent_area_size()
var set_pos : Vector2
# Gets the ancher_size according to this node's dimentions and proportion mode
if mode & PROPORTION_MODE.WIDTH_PROPORTION > 0:
ancher_size.x = ancher_size.x * horizontal_ratio
# Expands or repositions child, according to ancher and size flages
match child.size_flags_horizontal & ~SIZE_EXPAND:
SIZE_FILL:
child_size.x = ancher_size.x
set_pos.x = 0
SIZE_SHRINK_BEGIN:
child_size.x = max(child_size.x, ancher_size.x)
set_pos.x = 0
SIZE_SHRINK_CENTER:
child_size.x = max(child_size.x, ancher_size.x)
set_pos.x = max((ancher_size.x - child_size.x) * 0.5, 0)
SIZE_SHRINK_END:
child_size.x = max(child_size.x, ancher_size.x)
set_pos.x = max(ancher_size.x - child_size.x, 0)
if mode == PROPORTION_MODE.WIDTH:
child_size.y = size.y
# Gets the ancher_size according to this node's dimentions and proportion mode
if mode & PROPORTION_MODE.HEIGHT_PROPORTION > 0:
ancher_size.y = ancher_size.y * vertical_ratio
# Expands or repositions child, according to ancher and size flages
match child.size_flags_vertical & ~SIZE_EXPAND:
SIZE_FILL:
child_size.y = ancher_size.y
set_pos.y = 0
SIZE_SHRINK_BEGIN:
child_size.y = max(child_size.y, ancher_size.y)
set_pos.y = 0
SIZE_SHRINK_CENTER:
child_size.y = max(child_size.y, ancher_size.y)
set_pos.y = max((size.y - child_size.y) * 0.5, 0)
SIZE_SHRINK_END:
child_size.y = max(child_size.y, ancher_size.y)
set_pos.y = max(size.y - child_size.y, 0)
if mode == PROPORTION_MODE.HEIGHT:
child_size.x = size.x
fit_child_in_rect(child, Rect2(set_pos, child_size))
func _get_children_min_size() -> Vector2:
var ret := Vector2.ZERO
for child : Control in _get_control_children():
ret = ret.max(child.get_combined_minimum_size())
return ret
func _get_control_children() -> Array[Control]:
var ret : Array[Control]
ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible))
return ret
func _get_allowed_size_flags_horizontal() -> PackedInt32Array:
return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END]
func _get_allowed_size_flags_vertical() -> PackedInt32Array:
return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END]
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,77 @@
@tool
class_name Page extends Container
## A standardized [Container] node for Routers to use, such as [RouterStack].
## Emits when an event is requested to the attached Router parent.
## [br][br]
## If this Router is a decedent of another [Page], connect that [Page]'s
## [method emit_event] with this [Signal].
signal event_action(event_name : String, args : Variant)
@warning_ignore("unused_signal")
## Emits when this page is added as a child and finished animation by a Router.
signal entered
@warning_ignore("unused_signal")
## Emits when this page is added as a child.
signal entering
@warning_ignore("unused_signal")
## Emits when this page is about to be removed as a child and finished animation by a Router.
signal exited
@warning_ignore("unused_signal")
## Emits when this page is marked to be removed as a child.
signal exiting
## Requests an event to the attached Router parent.
func emit_event(event_name : String, args : Variant) -> void:
event_action.emit(event_name, args)
func _enter_tree() -> void:
if !Engine.is_editor_hint():
clip_contents = true
func _init() -> void:
sort_children.connect(_sort_children)
func _sort_children() -> void:
for child : Node in get_children():
if child is Control: _update_child(child)
func _update_child(child : Control):
var child_min_size := child.get_minimum_size()
var result_size := child_min_size
var set_pos : Vector2
match child.size_flags_horizontal & ~SIZE_EXPAND:
SIZE_FILL:
result_size.x = max(result_size.x, size.x)
set_pos.x = (size.x - result_size.x) * 0.5
SIZE_SHRINK_BEGIN:
set_pos.x = 0
SIZE_SHRINK_CENTER:
set_pos.x = (size.x - result_size.x) * 0.5
SIZE_SHRINK_END:
set_pos.x = size.x - result_size.x
match child.size_flags_vertical & ~SIZE_EXPAND:
SIZE_FILL:
result_size.y = max(result_size.y, size.y)
set_pos.y = (size.y - result_size.y) * 0.5
SIZE_SHRINK_BEGIN:
set_pos.y = 0
SIZE_SHRINK_CENTER:
set_pos.y = (size.y - result_size.y) * 0.5
SIZE_SHRINK_END:
set_pos.y = size.y - result_size.y
fit_child_in_rect(child, Rect2(set_pos, result_size))
func _get_allowed_size_flags_horizontal() -> PackedInt32Array:
return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END]
func _get_allowed_size_flags_vertical() -> PackedInt32Array:
return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END]

View file

@ -0,0 +1,43 @@
@tool
class_name PageInfo extends Resource
## A [Resource] for keeping stack of [Page] information for a Router, such as [RouterStack].
var _page : Control
var _auto_clean : bool
var _enter_animate : SwapContainer.ANIMATION_TYPE
var _exit_animate : SwapContainer.ANIMATION_TYPE
## Static create function for this [Resource].
static func create(
page: Page,
enter_animate : SwapContainer.ANIMATION_TYPE,
exit_animate : SwapContainer.ANIMATION_TYPE,
auto_clean : bool
) -> PageInfo:
var info = PageInfo.new()
info._page = page
info._enter_animate = enter_animate
info._exit_animate = exit_animate
info._auto_clean = auto_clean
return info
## Gets the current [Page] held by this [Resource].
func get_page() -> Page: return _page
## Gets the saved enter animation.
func get_enter_animation() -> SwapContainer.ANIMATION_TYPE: return _enter_animate
## Gets the saved exit animation.
func get_exit_animation() -> SwapContainer.ANIMATION_TYPE: return _exit_animate
func _notification(what):
if (
what == NOTIFICATION_PREDELETE &&
_auto_clean &&
_page &&
is_instance_valid(_page)
):
_page.queue_free()
_page = null

View file

@ -0,0 +1,427 @@
@tool
class_name RouterStack extends PanelContainer
## Handles a [Control] stack, between [Page] nodes, using [SwapContainer].
## The Animation type to transition with.
const ANIMATION_TYPE = SwapContainer.ANIMATION_TYPE
## Emits when the current [Page] requests an event.
signal event_action(event : String, args : Variant)
## Emits at the start of a transition.
signal start_animation
## Emits at the end of a transition.
signal end_animation
## The filepath to the [Page] node this to load on ready. If this path is invaild,
## or not to a [PackedScene] with a [Page] node root, nothing will be loaded on ready.
@export_file("*.tscn") var starting_page : String:
set(val):
if val != starting_page:
starting_page = val
if Engine.is_editor_hint() && is_node_ready():
_clear_all_pages()
if ResourceLoader.exists(starting_page) && starting_page.get_extension() == "tscn":
route(starting_page, ANIMATION_TYPE.NONE, ANIMATION_TYPE.NONE)
## The max size of the stack. If the stack is too big, it will clear the oldest on
## the stack first.
@export_range(0, 1000, 1, "or_greater") var max_stack : int = 50:
set(val):
val = max(val, 1)
if max_stack != val:
max_stack = val
@export_group("Animation")
## Starts animation with the [Control] node outside of the visisble screen.
@export var from_outside_screen : bool:
set(val):
if val != from_outside_screen:
from_outside_screen = val
_stack.from_outside_screen = val
## Starts animation an offset of this amount of pixels (away from the center), start
## at the position the [Control] originally would be placed at.
@export var offset : float:
set(val):
if val != offset:
offset = val
_stack.offset = val
@export_group("Easing")
## The [enum Tween.EaseType] that will be used as the new [Control] transitions in.
@export var ease_enter : Tween.EaseType = Tween.EaseType.EASE_IN_OUT:
set(val):
if val != ease_enter:
ease_enter = val
_stack.ease_enter = val
## The [enum Tween.EaseType] that will be used as the current [Control] transitions out.
@export var ease_exit : Tween.EaseType = Tween.EaseType.EASE_IN_OUT:
set(val):
if val != ease_exit:
ease_exit = val
_stack.ease_exit = val
@export_group("Transition")
## The [enum Tween.TransitionType] that will be used as the new [Control] transitions in.
@export var transition_enter : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC:
set(val):
if val != transition_enter:
transition_enter = val
_stack.transition_enter = val
## The [enum Tween.TransitionType] that will be used as the current [Control] transitions out.
@export var transition_exit : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC:
set(val):
if val != transition_exit:
transition_exit = val
_stack.transition_exit = val
@export_group("Duration")
## The duration of the animation used as the new [Control] transitions in.
@export var duration_enter : float = 0.35:
set(val):
if val != duration_enter:
duration_enter = val
_stack.duration_enter = val
## The duration of the animation used as the current [Control] transitions out.
@export var duration_exit : float = 0.35:
set(val):
if val != duration_exit:
duration_exit = val
_stack.duration_exit = val
var _page_stack : Array[PageInfo] = []
var _params : Dictionary = {}
var _stack : SwapContainer
## Emits the [Signal Page.entered] signal on the current [Page] displayed.
## [br][br]
## If this Router is a decedent of another [Page], connect that [Page]'s
## [Signal Page.entered] with this method.
func emit_entered() -> void:
var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page()
if curr_page:
curr_page.entered.emit()
## Emits the [Signal Page.entering] signal on the current [Page] displayed.
## [br][br]
## If this Router is a decedent of another [Page], connect that [Page]'s
## [Signal Page.entering] with this method.
func emit_entering() -> void:
var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page()
if curr_page:
curr_page.entering.emit()
## Emits the [Signal Page.exited] signal on the current [Page] displayed.
## [br][br]
## If this Router is a decedent of another [Page], connect that [Page]'s
## [Signal Page.exited] with this method.
func emit_exited() -> void:
var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page()
if curr_page:
curr_page.exited.emit()
## Emits the [Signal Page.exiting] signal on the current [Page] displayed.
## [br][br]
## If this Router is a decedent of another [Page], connect that [Page]'s
## [Signal Page.exiting] with this method.
func emit_exiting() -> void:
var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page()
if curr_page:
curr_page.exiting.emit()
## Routes to a [Page] node given by a file path to a [PackedScene].
func route(
page_path : String,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
params : Dictionary = {},
args : Dictionary = {}
) -> Page:
var packed : PackedScene = await _ResourceLoader.new(get_tree().process_frame, page_path).finished
if packed == null:
push_error("An error occured while attempting to load file at filepath '", page_path, "'")
return null
return await route_packed(
packed,
enter_animation,
exit_animation,
params,
args
)
## Routes to a [Page] node given by a [PackedScene].
func route_packed(
page_scene : PackedScene,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
params : Dictionary = {},
args : Dictionary = {}
) -> Page:
if page_scene == null:
push_error("page_scene cannot be 'null'")
return null
return await route_node(
page_scene.instantiate(),
enter_animation,
exit_animation,
params,
args
)
## Routes to a given [Page] node.
func route_node(
page : Page,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
params : Dictionary = {},
args : Dictionary = {}
) -> Page:
if !page:
push_error("page cannot be 'null'")
return null
_params = params
var enter_page : PageInfo = PageInfo.create(
page,
enter_animation,
exit_animation,
args.get("auto_clean", true)
)
_stack.set_modifers(args)
_append_to_page_queue(enter_page)
await _handle_swap(
enter_page.get_page(),
enter_animation,
exit_animation
)
return page
## Routes to a [Page] node given by a file path to a [PackedScene]. Clears stack.
func navigate(
page_path : String,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
params : Dictionary = {},
args : Dictionary = {}
) -> Page:
var packed : PackedScene = await _ResourceLoader.new(get_tree().process_frame, page_path).finished
if packed == null:
push_error("An error occured while attempting to load file at filepath '", page_path, "'")
return null
return await navigate_packed(
packed,
enter_animation,
exit_animation,
params,
args
)
## Routes to a [Page] node given by a [PackedScene]. Clears stack.
func navigate_packed(
page_scene : PackedScene,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
params : Dictionary = {},
args : Dictionary = {}
) -> Page:
if page_scene == null:
push_error("page_scene cannot be 'null'")
return null
return await navigate_node(
page_scene.instantiate(),
enter_animation,
exit_animation,
params,
args
)
## Routes to a given [Page] node. Clears stack.
func navigate_node(
page : Page,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
params : Dictionary = {},
args : Dictionary = {}
) -> Page:
if !page:
push_error("page cannot be 'null'")
return null
_params = params
var enter_info : PageInfo = PageInfo.create(
page,
enter_animation,
exit_animation,
args.get("auto_clean", true)
)
_stack.set_modifers(args)
_append_to_page_queue(enter_info)
await _handle_swap(
enter_info.get_page(),
enter_animation,
exit_animation
)
_clear_stack()
return page
## Routes to the previous [Control] on the stack. If the stack is empty, nothing will happen.
func back(
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
params : Dictionary = {},
args : Dictionary = {}
) -> void:
if is_empty(): return
_params = params
_stack.set_modifers(args)
var exit_page : PageInfo = _page_stack.pop_back()
var enter_page : PageInfo = _page_stack.back()
enter_page.get_page().event_action.connect(event_action.emit)
if enter_animation == ANIMATION_TYPE.DEFAULT:
enter_animation = _reverse_animate(exit_page.get_exit_animation())
if exit_animation == ANIMATION_TYPE.DEFAULT:
exit_animation = _reverse_animate(exit_page.get_enter_animation())
await _handle_swap(
enter_page.get_page(),
enter_animation,
exit_animation,
false
)
## Gets the parameters of the last route.
func get_params() -> Dictionary:
return _params
## Gets the current stack size.
func stack_size() -> int:
return _page_stack.size()
## Returns if the stack is empty.
func is_empty() -> bool:
return _page_stack.size() <= 1
## Returns the current [Page] on display.
func get_current_page() -> PageInfo:
return null if _page_stack.is_empty() else _page_stack.back()
func _handle_swap(
enter_page : Page,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
front : bool = true
) -> void:
var exit_page : Page = _stack.get_current()
if enter_page:
enter_page.entering.emit()
if exit_page:
exit_page.exiting.emit()
await _stack.swap_control(
enter_page,
enter_animation,
exit_animation,
front
)
if enter_page:
enter_page.entered.emit()
if exit_page:
exit_page.exited.emit()
func _append_to_page_queue(page_node: PageInfo) -> void:
if !_page_stack.is_empty():
_page_stack.back().get_page().event_action.disconnect(event_action.emit)
if _page_stack.size() > max_stack:
_page_stack.pop_front()
_page_stack.append(page_node)
var page := page_node.get_page()
if !page.event_action.is_connected(event_action.emit):
page.event_action.connect(event_action.emit)
func _reverse_animate(animation : ANIMATION_TYPE) -> ANIMATION_TYPE:
match animation:
ANIMATION_TYPE.NONE:
return ANIMATION_TYPE.NONE
ANIMATION_TYPE.LEFT:
return ANIMATION_TYPE.RIGHT
ANIMATION_TYPE.RIGHT:
return ANIMATION_TYPE.LEFT
ANIMATION_TYPE.TOP:
return ANIMATION_TYPE.BOTTOM
ANIMATION_TYPE.BOTTOM:
return ANIMATION_TYPE.TOP
return ANIMATION_TYPE.NONE
func _clear_stack() -> void:
_page_stack = [_page_stack.back()]
func _clear_all_pages() -> void:
_page_stack = []
func _init() -> void:
_stack = SwapContainer.new()
add_child(_stack)
_stack.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_stack.start_animation.connect(start_animation.emit)
_stack.end_animation.connect(end_animation.emit)
_stack.from_outside_screen = from_outside_screen
_stack.offset = offset
_stack.ease_enter = ease_enter
_stack.ease_exit = ease_exit
_stack.transition_enter = transition_enter
_stack.transition_exit = transition_exit
_stack.duration_enter = duration_enter
_stack.duration_exit = duration_exit
class _ResourceLoader:
signal finished(scene : PackedScene)
var _resource_name : String
func _init(check_signal : Signal, path : StringName) -> void:
_resource_name = path
if !ResourceLoader.exists(_resource_name):
push_error("Error - Invaild Resource Loaded")
check_signal.connect(_delay_failsave, CONNECT_ONE_SHOT)
return
check_signal.connect(_on_signal)
ResourceLoader.load_threaded_request(
_resource_name,
"PackedScene"
)
func _on_signal() -> void:
match ResourceLoader.load_threaded_get_status(_resource_name):
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE, ResourceLoader.THREAD_LOAD_FAILED:
finished.emit(null)
ResourceLoader.THREAD_LOAD_LOADED:
finished.emit(ResourceLoader.load_threaded_get(_resource_name))
func _delay_failsave() -> void:
finished.emit(null)

View file

@ -0,0 +1,77 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name MaxRatioContainer extends MaxSizeContainer
## A container that limits an axis of it's size, to a maximum value, relative
## to the value of it's other axis.
## The behavior this node will exhibit based on an axis.
enum MAX_RATIO_MODE {
NONE, ## No maximum value for either axis on this container.
WIDTH, ## Sets and expands children height to be proportionate of width.
WIDTH_PROPORTION, ## Sets the maximum height value of this container to be proportionate of width.
HEIGHT, ## Sets and expands children width to be proportionate of height.
HEIGHT_PROPORTION ## Sets the maximum width value of this container to be proportionate of height.
}
## The ratio mode used to expand and limit children.
@export var mode : MAX_RATIO_MODE = MAX_RATIO_MODE.NONE:
set(val):
if val != mode:
mode = val
queue_sort()
## The ratio value used to expand and limit children.
@export_range(0.001, 10, 0.001, "or_greater") var ratio : float = 1.0:
set(val):
if val != ratio:
ratio = val
queue_sort()
func _validate_property(property: Dictionary) -> void:
if property.name == "max_size":
property.usage |= PROPERTY_USAGE_READ_ONLY
func _get_minimum_size() -> Vector2:
var parent := get_parent_area_size()
var min_size := super()
var current_size := min_size.max(size)
match mode:
MAX_RATIO_MODE.NONE:
current_size = Vector2(0, 0)
MAX_RATIO_MODE.WIDTH, MAX_RATIO_MODE.WIDTH_PROPORTION:
current_size = Vector2(0, minf(current_size.x * ratio, parent.y))
MAX_RATIO_MODE.HEIGHT, MAX_RATIO_MODE.HEIGHT_PROPORTION:
current_size = Vector2(minf(current_size.y * ratio, parent.x), 0)
min_size = min_size.max(current_size)
return min_size
## Updates the _max_size according to the ratio mode and current dimentions
func _update_children() -> void:
var parent := get_parent_area_size()
var min_size := get_combined_minimum_size()
# Adjusts max_size itself accouring to the ratio mode and current dimentions
match mode:
MAX_RATIO_MODE.NONE:
_max_size = Vector2(-1, -1)
MAX_RATIO_MODE.WIDTH:
_max_size = Vector2(-1, minf(size.x * ratio, parent.y))
MAX_RATIO_MODE.WIDTH_PROPORTION:
_max_size = Vector2(-1, min(size.x * ratio, parent.y, min_size.y))
MAX_RATIO_MODE.HEIGHT:
_max_size = Vector2(minf(size.y * ratio, parent.x), -1)
MAX_RATIO_MODE.HEIGHT_PROPORTION:
_max_size = Vector2(min(size.y * ratio, parent.x, min_size.x), -1)
var new_size := size
if _max_size.x >= 0:
new_size.x = _max_size.x
if _max_size.y >= 0:
new_size.y = _max_size.y
set_deferred("size", new_size)
super()
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,78 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name MaxSizeContainer extends Container
## A container that limits it's size to a maximum value.
var _max_size := -Vector2.ONE
## The maximum size this container can possess.
## [br][br]
## If one of the axis is [code]-1[/code], then it is boundless.
@export var max_size : Vector2 = -Vector2.ONE:
get: return _max_size
set(val):
_max_size = val
queue_sort()
func _init() -> void:
sort_children.connect(_handle_sort, CONNECT_DEFERRED)
func _set(property: StringName, value: Variant) -> bool:
if property == "size":
return true
return false
func _get_minimum_size() -> Vector2:
var min_size : Vector2 = Vector2.ZERO
for child : Control in _get_control_children():
min_size = min_size.max(child.get_combined_minimum_size())
return min_size
func _get_control_children() -> Array[Control]:
var ret : Array[Control]
ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible))
return ret
## A helper function that should be called whenever this node's size needs to be changed, or when it's children are changed.
func _handle_sort() -> void:
update_minimum_size()
_update_children()
func _update_children() -> void:
for child : Control in _get_control_children():
_update_child(child)
func _update_child(child : Control):
var child_min_size := child.get_minimum_size()
var set_pos : Vector2
var result_size := Vector2(
size.x if _max_size.x < 0 else minf(size.x, _max_size.x),
size.y if _max_size.y < 0 else minf(size.y, _max_size.y)
)
if child.size_flags_horizontal & SIZE_EXPAND_FILL:
result_size.x = maxf(result_size.x, child_min_size.x)
else:
result_size.x = minf(result_size.x, child_min_size.x)
if child.size_flags_vertical & SIZE_EXPAND_FILL:
result_size.y = maxf(result_size.y, child_min_size.y)
else:
result_size.y = minf(result_size.y, child_min_size.y)
match child.size_flags_horizontal & ~SIZE_EXPAND:
SIZE_SHRINK_CENTER, SIZE_FILL:
set_pos.x = (size.x - result_size.x) * 0.5
SIZE_SHRINK_BEGIN:
set_pos.x = 0
SIZE_SHRINK_END:
set_pos.x = size.x - result_size.x
match child.size_flags_vertical & ~SIZE_EXPAND:
SIZE_SHRINK_CENTER, SIZE_FILL:
set_pos.y = (size.y - result_size.y) * 0.5
SIZE_SHRINK_BEGIN:
set_pos.y = 0
SIZE_SHRINK_END:
set_pos.y = size.y - result_size.y
child.position = set_pos
child.size = result_size
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,297 @@
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.
@tool
class_name SwapContainer extends Container
## A [Container] node that provides transitions between different [Control] nodes.
## The Animation type to transition with.
enum ANIMATION_TYPE {
DEFAULT, ## The same as [constant LEFT].
NONE, ## No Transition
LEFT, ## Either moves towards or away the left
RIGHT, ## Either moves towards or away the right
TOP, ## Either moves towards or away the top
BOTTOM ## Either moves towards or away the bottom
}
## Emits at the start of a transition.
signal start_animation
## Emits at the end of a transition.
signal end_animation
var _enter_tween : Tween = null
var _exit_tween : Tween = null
var _current_node : Control
@export_group("Animation")
## Starts animation with the [Control] node outside of the visisble screen.
@export var from_outside_screen : bool
## Starts animation an offset of this amount of pixels (away from the center), start
## at the position the [Control] originally would be placed at.
@export var offset : float
@export_group("Easing")
## The [enum Tween.EaseType] that will be used as the new [Control] transitions in.
@export var ease_enter : Tween.EaseType = Tween.EaseType.EASE_IN_OUT
## The [enum Tween.EaseType] that will be used as the current [Control] transitions out.
@export var ease_exit : Tween.EaseType = Tween.EaseType.EASE_IN_OUT
@export_group("Transition")
## The [enum Tween.TransitionType] that will be used as the new [Control] transitions in.
@export var transition_enter : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC
## The [enum Tween.TransitionType] that will be used as the current [Control] transitions out.
@export var transition_exit : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC
@export_group("Duration")
## The duration of the animation used as the new [Control] transitions in.
@export var duration_enter : float = 0.35
## The duration of the animation used as the current [Control] transitions out.
@export var duration_exit : float = 0.35
## Causes the current [Control] node to transition out and [param node]
## to transition in.
func swap_control(
node : Control,
enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
front : bool = true
) -> Control:
var _old_node := _current_node
_parent_control(node, front)
start_animation.emit()
_current_node = node
await _perform_animations(
node,
_old_node,
enter_animation,
exit_animation
)
if _old_node: _unparent_control(_old_node)
end_animation.emit()
return _old_node
## Sets all export members with a simple [Dictionary].
func set_modifers(args : Dictionary) -> void:
if args.has("ease_enter"):
ease_enter = args.get("ease_enter")
if args.has("ease_exit"):
ease_exit = args.get("ease_exit")
if args.has("transition_enter"):
transition_enter = args.get("transition_enter")
if args.has("transition_exit"):
transition_exit = args.get("transition_exit")
if args.has("duration_enter"):
duration_enter = args.get("duration_enter")
if args.has("duration_exit"):
duration_exit = args.get("duration_exit")
## Gets the current [Control] displayed. [code]null[/code] if there is currently no such
## [Control].
func get_current() -> Control:
return _current_node
func _parent_control(node: Control, front : bool) -> void:
if !node: return
if !node.get_parent():
add_child(node)
if is_inside_tree():
node.hide()
get_tree().process_frame.connect(node.show, CONNECT_ONE_SHOT)
if front:
node.move_to_front()
else:
move_child(node, 0)
node.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
func _unparent_control(node: Control) -> void:
remove_child(node)
func _perform_animations(
enter_node: Control,
exit_node: Control,
enter_animation: ANIMATION_TYPE,
exit_animation: ANIMATION_TYPE
) -> void:
if enter_animation == ANIMATION_TYPE.NONE && exit_animation == ANIMATION_TYPE.NONE:
if enter_node:
enter_node.position = Vector2.ZERO
return
_tween_settup()
if enter_node:
_handle_enter_animation(
enter_node,
_enter_tween,
enter_animation
)
else:
_enter_tween.finished.emit()
_enter_tween.kill()
_enter_tween = null
if exit_node:
_handle_exit_animation(
exit_node,
_exit_tween,
exit_animation,
)
else:
_exit_tween.finished.emit()
_exit_tween.kill()
_exit_tween = null
await _await_animations()
func _handle_enter_animation(
node: Control,
animation_tween : Tween,
animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
) -> void:
var border := _get_border()
match animation:
ANIMATION_TYPE.DEFAULT, ANIMATION_TYPE.LEFT:
animation_tween.tween_method(
func (val): node.position.x = val,
border.position.x,
0,
duration_enter
)
ANIMATION_TYPE.RIGHT:
animation_tween.tween_method(
func (val): node.position.x = val,
border.size.x - border.position.x,
0,
duration_enter
)
ANIMATION_TYPE.TOP:
animation_tween.tween_method(
func (val): node.position.y = val,
border.position.y,
0,
duration_enter
)
ANIMATION_TYPE.BOTTOM:
animation_tween.tween_method(
func (val): node.position.y = val,
border.size.y - border.position.y,
0,
duration_enter
)
_:
node.position = Vector2.ZERO
return
animation_tween.play()
func _handle_exit_animation(
node: Control,
animation_tween : Tween,
animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT,
) -> void:
var border : Rect2 = _get_border()
match animation:
ANIMATION_TYPE.DEFAULT, ANIMATION_TYPE.LEFT:
animation_tween.tween_method(
func (val): node.position.x = val,
0,
border.size.x - border.position.x,
duration_enter
)
ANIMATION_TYPE.RIGHT:
animation_tween.tween_method(
func (val): node.position.x = val,
0,
border.position.x,
duration_enter
)
ANIMATION_TYPE.TOP:
animation_tween.tween_method(
func (val): node.position.y = val,
0,
border.size.y - border.position.y,
duration_enter
)
ANIMATION_TYPE.BOTTOM:
animation_tween.tween_method(
func (val): node.position.y = val,
0,
border.position.y,
duration_enter
)
_:
node.position = Vector2.ZERO
return
animation_tween.play()
func _tween_settup() -> void:
if _enter_tween && _enter_tween.is_running():
_enter_tween.finished.emit()
_enter_tween.kill()
_enter_tween = create_tween()
_enter_tween.set_ease(ease_enter)
_enter_tween.set_trans(transition_enter)
_enter_tween.stop()
if _exit_tween && _exit_tween.is_running():
_exit_tween.finished.emit()
_exit_tween.kill()
_exit_tween = create_tween()
_exit_tween.set_ease(ease_exit)
_exit_tween.set_trans(transition_exit)
_exit_tween.stop()
func _get_border() -> Rect2:
var boarder : Rect2
if from_outside_screen:
boarder.position = -global_position - size
boarder.size = get_viewport_rect().size - size
else:
boarder.position = -size
boarder.size = Vector2.ZERO
boarder.position -= Vector2(offset, offset)
return boarder
func _await_animations() -> void:
@warning_ignore("incompatible_ternary")
await _SignalMerge.new(
_enter_tween.finished if _enter_tween && _enter_tween.is_running() else null,
_exit_tween.finished if _exit_tween && _exit_tween.is_running() else null
) .finished
class _SignalMerge:
signal finished
var _activate : bool = false
func _init(enter, exit) -> void:
_register(enter)
_register(exit)
func _register(arg) -> void:
if arg is Signal:
arg.connect(_unleash)
return
_unleash()
func _unleash() -> void:
if _activate: finished.emit()
_activate = true
# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon.

View file

@ -0,0 +1,109 @@
@tool
class_name ModulateTransitionContainer extends Container
## A [Control] node with changable that allows easy [member CanvasItem.modulate] animation between colors.
@export_group("Alpha Override")
## The colors to animate between.
@export var colors : PackedColorArray = [Color.WHITE, Color(1.0, 1.0, 1.0, 0.5)]:
set(val):
if colors != val:
colors = val
focused_color = focused_color
force_color(_focused_color)
var _focused_color : int = 0
## The index of currently used color from [member colors].
## This member is [code]-1[/code] if [member colors] is empty.
@export var focused_color : int:
get: return _focused_color
set(val):
if colors.size() == 0:
_focused_color = -1
return
val = clampi(val, 0, colors.size() - 1)
if _focused_color != val:
_focused_color = val
_on_set_color()
## If [code]true[/code] this node will only animate over [member CanvasItem.self_modulate]. Otherwise,
## it will animate over [member CanvasItem.modulate].
@export var modulate_self : bool = false
@export_group("Tween Override")
## The duration of color animations.
@export var transitionTime : float = 0.2
## The [Tween.EaseType] of color animations.
@export var easeType : Tween.EaseType = Tween.EaseType.EASE_OUT_IN
## The [Tween.TransitionType] of color animations.
@export var transition : Tween.TransitionType = Tween.TransitionType.TRANS_CIRC
## If [code]true[/code] animations can be interupted midway. Otherwise, any change in the [param focused_color]
## will be queued to be reflected after any currently running animation.
@export var can_cancle : bool = true
var _color_tween : Tween = null
var _current_focused_color : int
## Sets the current color index.
## [br][br]
## Also see: [member focused_color].
func set_color(color: int) -> void:
focused_color = color
## Sets the current color index. Performing this will ignore any animation and instantly set the color.
## [br][br]
## Also see: [member focused_color].
func force_color(color: int) -> void:
if _color_tween && _color_tween.is_running():
if !can_cancle: return
_color_tween.kill()
_current_focused_color = color
modulate = colors[color]
## Gets the current color attributed to the current color index.
func get_current_color() -> Color:
if _focused_color == -1: return 1
return colors[_focused_color]
func _on_set_color():
if _focused_color == _current_focused_color:
return
if can_cancle:
if _color_tween: _color_tween.kill()
elif _color_tween && _color_tween.is_running():
return
_current_focused_color = _focused_color
_color_tween = create_tween()
_color_tween.tween_property(
self,
"self_modulate" if modulate_self else "modulate",
get_current_color(),
transitionTime
)
_color_tween.finished.connect(_on_set_color, CONNECT_ONE_SHOT)
func _init() -> void:
_current_focused_color = _focused_color
sort_children.connect(_handle_children)
func _property_can_revert(property: StringName) -> bool:
if property == "colors":
return colors.size() == 2 && colors[0] == Color.WHITE && colors[1] == Color(1.0, 1.0, 1.0, 0.5)
return false
func _handle_children() -> void:
for child in get_children():
fit_child_in_rect(child, Rect2(Vector2.ZERO, size))
func _get_minimum_size() -> Vector2:
var min_size : Vector2
for child : Node in get_children():
if child is Control:
min_size = min_size.max(child.get_combined_minimum_size())
return min_size

View file

@ -0,0 +1,126 @@
@tool
class_name StyleTransitionContainer extends Container
## A [Container] node that add a [StyleTransitionPanel] node as the background.
@export_group("Appearence Override")
## The stylebox used by [StyleTransitionPanel].
@export var background : StyleBox:
set(val):
if _panel:
_panel.add_theme_stylebox_override("panel", val)
background = val
elif background != val:
background = val
@export_group("Colors Override")
## The colors to animate between.
@export var colors : PackedColorArray = [
Color.WEB_GRAY,
Color.DIM_GRAY
]:
set(val):
if _panel:
_panel.colors = val
colors = val
elif colors != val:
colors = val
## The index of currently used color from [member colors].
## This member is [code]-1[/code] if [member colors] is empty.
@export var focused_color : int:
set(val):
if _panel:
_panel.focused_color = val
focused_color = val
elif focused_color != val:
focused_color = val
@export_group("Tween Override")
## The duration of color animations.
@export var transitionTime : float = 0.2:
set(val):
if _panel:
_panel.transitionTime = val
transitionTime = val
elif transitionTime != val:
transitionTime = val
## The [Tween.EaseType] of color animations.
@export var easeType : Tween.EaseType = Tween.EaseType.EASE_OUT_IN:
set(val):
if _panel:
_panel.easeType = val
easeType = val
elif easeType != val:
easeType = val
## The [Tween.TransitionType] of color animations.
@export var transition : Tween.TransitionType = Tween.TransitionType.TRANS_CIRC:
set(val):
if _panel:
_panel.transition = val
transition = val
elif transition != val:
transition = val
## If [code]true[/code] animations can be interupted midway. Otherwise, any change in the [param focused_color]
## will be queued to be reflected after any currently running animation.
@export var can_cancle : bool = true:
set(val):
if _panel:
_panel.can_cancle = val
can_cancle = val
elif can_cancle != val:
can_cancle = val
var _panel : StyleTransitionPanel
## Sets the current color index.
## [br][br]
## Also see: [member focused_color].
func set_color(color: int) -> void:
if !_panel: return
_panel.set_color(color)
## Sets the current color index. Performing this will ignore any animation and instantly set the color.
## [br][br]
## Also see: [member focused_color].
func force_color(color: int) -> void:
if !_panel: return
_panel.force_color(color)
## Gets the current color attributed to the current color index.
func get_current_color() -> Color:
if !_panel: return Color.BLACK
return _panel.get_current_color()
func _init() -> void:
_panel = StyleTransitionPanel.new()
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
_panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(_panel)
move_child(_panel, 0)
sort_children.connect(_handle_children)
func _ready() -> void:
if background:
_panel.add_theme_stylebox_override("panel", background)
return
background = _panel.get_theme_stylebox("panel")
func _handle_children() -> void:
for child in get_children():
fit_child_in_rect(child, Rect2(Vector2.ZERO, size))
func _get_minimum_size() -> Vector2:
var min_size : Vector2
for child : Node in get_children():
if child is Control:
min_size = min_size.max(child.get_combined_minimum_size())
return min_size
func _property_can_revert(property: StringName) -> bool:
if property == "colors":
return colors.size() == 2 && colors[0] == Color.WEB_GRAY && colors[1] == Color.DIM_GRAY
return false

View file

@ -0,0 +1,106 @@
@tool
class_name StyleTransitionPanel extends Panel
## A [Panel] node with changable that allows easy [member CanvasItem.self_modulate] animation between colors.
@export_group("Colors Override")
## The colors to animate between.
@export var colors : PackedColorArray = [
Color.WEB_GRAY,
Color.DIM_GRAY
]:
set(val):
if colors != val:
colors = val
focused_color = focused_color
force_color(_focused_color)
var _focused_color : int = 0
## The index of currently used color from [member colors].
## This member is [code]-1[/code] if [member colors] is empty.
@export var focused_color : int:
get: return _focused_color
set(val):
if colors.size() == 0:
_focused_color = -1
return
val = clampi(val, 0, colors.size() - 1)
if _focused_color != val:
_focused_color = val
_on_set_color()
@export_group("Tween Override")
## The duration of color animations.
@export var transitionTime : float = 0.2
## The [Tween.EaseType] of color animations.
@export var easeType : Tween.EaseType = Tween.EaseType.EASE_OUT_IN
## The [Tween.TransitionType] of color animations.
@export var transition : Tween.TransitionType = Tween.TransitionType.TRANS_CIRC
## If [code]true[/code] animations can be interupted midway. Otherwise, any change in the [param focused_color]
## will be queued to be reflected after any currently running animation.
@export var can_cancle : bool = true
var _color_tween : Tween = null
var _current_focused_color : int
## Sets the current color index.
## [br][br]
## Also see: [member focused_color].
func set_color(color: int) -> void:
focused_color = color
## Sets the current color index. Performing this will ignore any animation and instantly set the color.
## [br][br]
## Also see: [member focused_color].
func force_color(color: int) -> void:
if _color_tween && _color_tween.is_running():
if !can_cancle: return
_color_tween.kill()
_focused_color = color
_safe_base_set_background()
self_modulate = get_current_color()
## Gets the current color attributed to the current color index.
func get_current_color() -> Color:
if _focused_color == -1: return Color.BLACK
return colors[_focused_color]
func _safe_base_set_background() -> void:
if has_theme_stylebox_override("panel"): return
var background = StyleBoxFlat.new()
background.resource_local_to_scene = true
background.bg_color = Color.WHITE
add_theme_stylebox_override("panel", background)
func _on_set_color():
if _focused_color == _current_focused_color:
return
if can_cancle:
if _color_tween: _color_tween.kill()
elif _color_tween && _color_tween.is_running():
return
_current_focused_color = _focused_color
_safe_base_set_background()
_color_tween = create_tween()
_color_tween.tween_property(
self,
"self_modulate",
get_current_color(),
transitionTime
)
_color_tween.finished.connect(_on_set_color, CONNECT_ONE_SHOT)
func _init() -> void:
_current_focused_color = _focused_color
_safe_base_set_background()
func _property_can_revert(property: StringName) -> bool:
if property == "colors":
return colors.size() == 2 && colors[0] == Color.WEB_GRAY && colors[1] == Color.DIM_GRAY
return false

201
godot/addons/LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View file

@ -1,5 +1,9 @@
class_name Utils
static func remove_children(node: Node):
for i in range(node.get_child_count()):
node.remove_child(node.get_child(i))
static func get_class_name(value: Object) -> String:
match value.get_script():
var script:
@ -26,3 +30,20 @@ static func to_str(value: Variant) -> String:
var props: Dictionary = inst_to_dict(value)
return "%s %s" % [name, to_str(props)]
_: return str(value)
static func propagate(node: Node, fn: StringName, args: Array, call_on_self: bool = true):
if call_on_self and node.has_method(fn):
node.callv(fn, args)
for child in node.get_children():
propagate(child, fn, args)
static func propagate_input_event(node: Node, fn: StringName, event: InputEvent, call_on_self: bool = true):
if node.get_viewport().is_input_handled() or event.is_canceled():
return
if call_on_self and node.has_method(fn):
node.callv(fn, [event])
for child in node.get_children():
propagate_input_event(child, fn, event)

View file

@ -21,7 +21,7 @@ PhantomCameraManager="*res://addons/phantom_camera/scripts/managers/phantom_came
[editor_plugins]
enabled=PackedStringArray("res://addons/godot_object_serializer/plugin.cfg", "res://addons/phantom_camera/plugin.cfg")
enabled=PackedStringArray("res://addons/FreeControl/plugin.cfg", "res://addons/godot_object_serializer/plugin.cfg", "res://addons/phantom_camera/plugin.cfg")
[global_group]
@ -103,12 +103,18 @@ reload={
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null)
]
}
inventory={
open_inventory={
"deadzone": 0.2,
"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":true,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
ui_close_inventory={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
]
}
[layer_names]

View file

@ -1,10 +1,8 @@
[gd_resource type="Resource" script_class="Inventory" load_steps=4 format=3 uid="uid://bllq6ri54q3ne"]
[gd_resource type="Resource" script_class="Inventory" load_steps=2 format=3 uid="uid://bllq6ri54q3ne"]
[ext_resource type="Script" uid="uid://bqprls343ue6e" path="res://src/item.gd" id="1_uptie"]
[ext_resource type="Script" uid="uid://dh4ytedxidq0x" path="res://src/inventory.gd" id="2_1njko"]
[ext_resource type="Resource" uid="uid://cqfnwpmo4fyv4" path="res://resources/items/key.tres" id="2_85a8j"]
[resource]
script = ExtResource("2_1njko")
items = Array[ExtResource("1_uptie")]([ExtResource("2_85a8j"), null, null, null, null, null])
max_capacity = null
metadata/_custom_type_script = "uid://dh4ytedxidq0x"

View file

@ -15,8 +15,10 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
focus_mode = 2
script = ExtResource("1_qw0r6")
action_name = &"inventory"
open_action = &"inventory"
close_action = &"ui_close_inventory"
[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
@ -27,7 +29,7 @@ grow_horizontal = 2
grow_vertical = 2
color = Color(0.10748, 0.10748, 0.10748, 1)
[node name="VBoxContainer" type="VBoxContainer" parent="." node_paths=PackedStringArray("item_list")]
[node name="VBoxContainer" type="VBoxContainer" parent="."]
clip_contents = true
layout_mode = 1
anchors_preset = 15
@ -37,25 +39,16 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2_hj2ta")
inventory = ExtResource("3_ty45s")
item_list = NodePath("ItemNavigation/HItemList")
[node name="ItemNavigation" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="MoveLeft" type="Button" parent="VBoxContainer/ItemNavigation"]
layout_mode = 2
text = "<"
[node name="HItemList" type="HBoxContainer" parent="VBoxContainer/ItemNavigation"]
layout_mode = 2
size_flags_horizontal = 3
script = ExtResource("4_yyk2a")
item_scene = ExtResource("5_uae8j")
[node name="MoveRight" type="Button" parent="VBoxContainer/ItemNavigation"]
layout_mode = 2
text = ">"
[node name="Details" type="HBoxContainer" parent="VBoxContainer" node_paths=PackedStringArray("icon_image", "description_label")]
layout_mode = 2
size_flags_vertical = 3
@ -91,7 +84,7 @@ grow_horizontal = 2
grow_vertical = 2
autowrap_mode = 2
[connection signal="pressed" from="." to="." method="set_visible"]
[connection signal="pressed" from="VBoxContainer/ItemNavigation/MoveLeft" to="VBoxContainer/ItemNavigation/HItemList" method="move_left"]
[connection signal="closed" from="." to="." method="hide"]
[connection signal="opened" from="." to="." method="show"]
[connection signal="opened" from="." to="." method="grab_focus"]
[connection signal="selected" from="VBoxContainer/ItemNavigation/HItemList" to="VBoxContainer/Details" method="_on_updated"]
[connection signal="pressed" from="VBoxContainer/ItemNavigation/MoveRight" to="VBoxContainer/ItemNavigation/HItemList" method="move_right"]

View file

@ -0,0 +1,116 @@
[gd_scene load_steps=10 format=3 uid="uid://cn7tgd4y67wnd"]
[ext_resource type="Script" uid="uid://qvoqvonnxwfc" path="res://src/inventory_ui.gd" id="1_a0rpf"]
[ext_resource type="Script" uid="uid://bx4wxlm5mv268" path="res://src/root_control.gd" id="1_as33y"]
[ext_resource type="Resource" uid="uid://bllq6ri54q3ne" path="res://resources/player_inventory.tres" id="2_as33y"]
[ext_resource type="PackedScene" uid="uid://gn8k2ir47n1m" path="res://scenes/inventory_item.tscn" id="3_tg4gd"]
[ext_resource type="Script" uid="uid://13lxe4c4fmrp" path="res://addons/FreeControl/src/CustomClasses/Carousel/Carousel.gd" id="4_usnyx"]
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_as33y"]
size = Vector2(128, 128)
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_tg4gd"]
size = Vector2(128, 128)
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_usnyx"]
size = Vector2(128, 64)
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_sebc8"]
size = Vector2(256, 256)
[node name="Inventory" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
focus_mode = 2
script = ExtResource("1_as33y")
open_action = &"open_inventory"
close_action = &"ui_close_inventory"
[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.147672, 0.147672, 0.147672, 1)
[node name="Contents" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="InventoryUI" type="Control" parent="Contents" node_paths=PackedStringArray("carousel")]
layout_mode = 2
size_flags_vertical = 3
script = ExtResource("1_a0rpf")
inventory = ExtResource("2_as33y")
item_scene = ExtResource("3_tg4gd")
carousel = NodePath("Carousel")
metadata/_custom_type_script = "uid://qvoqvonnxwfc"
[node name="Carousel" type="Container" parent="Contents/InventoryUI"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -104.0
offset_top = 5.0
offset_right = -104.0
offset_bottom = 5.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("4_usnyx")
display_range = 4
snap_behavior = 2
paging_requirement = 100
metadata/_custom_type_script = "uid://13lxe4c4fmrp"
[node name="ItemDetails" type="HBoxContainer" parent="Contents"]
layout_mode = 2
size_flags_vertical = 3
[node name="PlayerInfo" type="VBoxContainer" parent="Contents/ItemDetails"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TextureRect" type="TextureRect" parent="Contents/ItemDetails/PlayerInfo"]
layout_mode = 2
size_flags_horizontal = 4
texture = SubResource("PlaceholderTexture2D_as33y")
[node name="TextureRect2" type="TextureRect" parent="Contents/ItemDetails/PlayerInfo"]
layout_mode = 2
size_flags_horizontal = 4
texture = SubResource("PlaceholderTexture2D_tg4gd")
[node name="TextureRect3" type="TextureRect" parent="Contents/ItemDetails/PlayerInfo"]
layout_mode = 2
size_flags_horizontal = 4
texture = SubResource("PlaceholderTexture2D_usnyx")
[node name="Display" type="VBoxContainer" parent="Contents/ItemDetails"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TextureRect" type="TextureRect" parent="Contents/ItemDetails/Display"]
layout_mode = 2
size_flags_horizontal = 4
texture = SubResource("PlaceholderTexture2D_sebc8")
[node name="Description" type="VBoxContainer" parent="Contents/ItemDetails"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="Contents/ItemDetails/Description"]
layout_mode = 2
text = "a description"

View file

@ -84,6 +84,7 @@ shape = SubResource("BoxShape3D_mx8sn")
script = ExtResource("3_w8frs")
[node name="CanvasLayer" type="CanvasLayer" parent="."]
follow_viewport_enabled = true
[node name="Control" type="Control" parent="CanvasLayer"]
layout_mode = 3
@ -110,6 +111,7 @@ text = "Load
"
[node name="Control2" parent="CanvasLayer" instance=ExtResource("8_b121j")]
visible = true
[connection signal="interacted" from="Door/Interactable" to="Door" method="_on_interact"]
[connection signal="pressed" from="CanvasLayer/Control/HBoxContainer/SaveButton" to="CanvasLayer/Control/Persistence" method="save" binds= ["save1.sav"]]

View file

@ -1,15 +1,29 @@
class_name InputListener extends Node
class_name InputListener extends Control
signal pressed(toggle_state: bool)
signal released(toggle_state: bool)
enum ActionEvent {
Pressed,
Released
}
@export var toggle_state: bool
@export var action_name: StringName
signal opened
signal closed
func _input(event: InputEvent) -> void:
if event.is_action_pressed(action_name):
self.toggle_state = !toggle_state
pressed.emit(toggle_state)
@export var input_event: ActionEvent
@export var open_action: StringName
@export var close_action: StringName
if event.is_action_released(action_name):
released.emit(toggle_state)
func _is_active(event: InputEvent, action: StringName) -> bool:
match input_event:
ActionEvent.Pressed: return event.is_action_pressed(action)
ActionEvent.Released: return event.is_action_released(action)
_: return false
func _unhandled_input(event: InputEvent) -> void:
if _is_active(event, open_action):
accept_event()
opened.emit()
func _gui_input(event: InputEvent) -> void:
if _is_active(event, close_action):
accept_event()
closed.emit()

View file

@ -1,17 +1,63 @@
class_name Inventory extends Resource
@export var items: Array[Item]
var initial_items: Array[ItemInstance]
var items: Dictionary[RID, ItemInstance]
@export var max_capacity: int = 6
var size: int:
get: return items.size()
signal item_added(item: Item, quantity: int)
signal item_removed(item: Item, remaining: int)
signal updated
func _init():
call_deferred("_ready")
func _ready():
print(initial_items.size())
for item in initial_items:
items[item.get_rid()] = item
print('setting', item)
func _item_eq(a: Item, b: ItemInstance) -> bool:
return a.item == b
func has_item(item: Item) -> bool:
return items.has(item.get_rid())
func find(item: Item) -> Option:
match items.get(item.get_rid()):
null: return Option.none
var found: return Option.some(found)
func add_item(item: Item, quantity: int = 1):
var rid = item.get_rid()
var inst = items.get_or_add(rid)
inst.quantity += quantity
item_added.emit(item, inst.quantity)
updated.emit()
func remove_item(item: Item, quantity: int = 1):
if find(item):
item.quantity -= quantity
if item.quantity <= 0:
items.erase(item.get_rid())
item_removed.emit(item, item.quantity)
updated.emit()
func _iter_continue(iter: Array) -> bool:
return iter[0] < len(items)
return iter[0].size()
func _iter_init(iter: Array) -> bool:
iter[0] = 0
iter[0] = items.keys()
return _iter_continue(iter)
func _iter_next(iter: Array) -> bool:
iter[0] += 1
return _iter_continue(iter)
func _iter_get(iter: Variant) -> Item:
return items[iter]
func _iter_get(iter: Variant) -> ItemInstance:
var rid = iter[0]
iter.remove_at(0)
return items.get(rid)

View file

@ -1,13 +1,41 @@
class_name InventoryUI extends VBoxContainer
class_name InventoryUI extends Control
@export var inventory: Inventory
@export var item_list: HItemList
@export var item_scene: PackedScene
@export var carousel: Carousel
func _ready() -> void:
item_list.add_items(inventory.items)
_build_carousel()
inventory.updated.connect(_build_carousel)
func _input(event: InputEvent) -> void:
func _build_carousel():
Utils.remove_children(carousel)
for instance in inventory:
print('pussy', instance)
var scene = create_item()
bind_item(scene, Option.from(instance.item))
carousel.add_child(scene)
func create_item() -> Node:
return item_scene.instantiate()
func bind_item(node: Node, item: Option):
if node.has_method('bind'):
node.bind(item)
func _gui_input(event: InputEvent) -> void:
if event.is_action_pressed(PlayerInput.UIAction.Right):
item_list.move_right()
move_right()
elif event.is_action_pressed(PlayerInput.UIAction.Left):
item_list.move_left()
move_left()
func move_by(delta: int):
var next_index = carousel.get_carousel_index() + delta
carousel.go_to_index(next_index)
func move_right():
move_by(1)
func move_left():
move_by(-1)

View file

@ -0,0 +1,24 @@
class_name ItemInstance extends Resource
@export var item: Item
@export var quantity: int
@warning_ignore("shadowed_variable")
func _init(item: Item = null, quantity: int = 1) -> void:
self.item = item
self.quantity = quantity
@warning_ignore("shadowed_variable")
func add_quantity(quantity: int = 1) -> int:
self.quantity += quantity
return self.quantity
@warning_ignore("shadowed_variable")
func remove_quantity(quantity: int = 1) -> int:
self.quantity -= quantity
return self.quantity
@warning_ignore("shadowed_variable")
func set_quantity(quantity: int) -> int:
self.quantity = quantity
return self.quantity

View file

@ -6,7 +6,7 @@ class_name ItemUI extends Control
var _default_text: String
var _default_icon: Texture2D
func _ready() -> void:
func _init() -> void:
_default_text = name_label.text
_default_icon = icon_texture.texture
@ -19,5 +19,6 @@ func bind(_item: Option):
icon_texture.texture = item.icon
func unbind():
print(_default_text)
name_label.text = _default_text
icon_texture.texture = _default_icon

View file

@ -107,7 +107,7 @@ func _get_device(event: InputEvent) -> Device:
else:
return Device.Unknown
func _input(event: InputEvent) -> void:
func _unhandled_input(event: InputEvent) -> void:
last_known_device = _get_device(event)
if event.is_action_pressed(GameAction.Interact):

23
godot/src/root_control.gd Normal file
View file

@ -0,0 +1,23 @@
class_name RootControl extends InputListener
func _ready() -> void:
if self.focus_mode == Control.FOCUS_NONE:
self.focus_mode = Control.FOCUS_ALL
opened.connect(open)
closed.connect(close)
if visible:
grab_focus()
func open():
show()
grab_focus()
func close():
hide()
release_focus()
func _gui_input(event: InputEvent) -> void:
super(event)
Utils.propagate_input_event(self, "_gui_input", event, false)