diff --git a/.gitattributes b/.gitattributes index 0583c83..753fc59 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,3 +15,6 @@ *.mtl filter=lfs diff=lfs merge=lfs -text *.wav filter=lfs diff=lfs merge=lfs -text *.blend filter=lfs diff=lfs merge=lfs -text +*.dll filter=lfs diff=lfs merge=lfs -text +*.so filter=lfs diff=lfs merge=lfs -text +*.dylib filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/build-prod.yml b/.github/workflows/build-prod.yml index 0019dee..d854c53 100644 --- a/.github/workflows/build-prod.yml +++ b/.github/workflows/build-prod.yml @@ -29,6 +29,7 @@ jobs: uses: actions/checkout@v3.3.0 with: lfs: true + fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..30f8d3e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,24 @@ +name: Create new Release + +on: + workflow_dispatch: + inputs: + tag: + description: "New tag name" + required: true + +jobs: + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + lfs: true + fetch-depth: 0 + - name: Create and push new Tag + run: | + git config user.name "Nitwel" + git config user.email "mail@nitwel.de" + + git tag ${{ github.event.inputs.tag }} + git push origin ${{ github.event.inputs.tag }} diff --git a/README.md b/README.md index 3f0158d..455dde4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ In order to contribute to this project, you need the following to be setup befor ## Fundamentals -Communication with the Smart Home Environment is done using the `HomeAdapters` global. Each environment is made up of devices and entities. +Communication with the Smart Home Environment is done using the `HomeApi` global. Each environment is made up of devices and entities. A device is a collection of different entities and entities can represent many different things in a smart home. For example, the entity of name `lights.smart_lamp_1` would control the kitchen lamps while `state.smart_lamp_1_temp` would show the current temperature of the lamp. @@ -55,9 +55,9 @@ For example, the entity of name `lights.smart_lamp_1` would control the kitchen └── home_adapters (Code allowing control smart home entities) ``` -### Home Adapters +### Home Api -The `HomeAdapters` global allows to communicate with different backends and offers a set of fundamental functions allowing communication with the Smart Home. +The `HomeApi` global allows to communicate with different backends and offers a set of fundamental functions allowing communication with the Smart Home. ```python Device { @@ -95,25 +95,33 @@ In case that an event of a specific node has to be reacted on, use the `Clickabl It is also possible to bubble up information by returning a dictionary from a function like `_on_click`. -```python -InteractionEvent { - "controller": XRController3D, # The controller that triggered the event - "ray": RayCast3D, # The ray-cast that triggered the event - "target": Node3D, # The node that was hit by the ray-cast -} -``` - | Function called | Args | Description | | -- | -- | -- | -| `_on_click` | `[event: InteractionEvent]` | The back trigger button has been pressed and released | -| `_on_press_down` | `[event: InteractionEvent]` | The back trigger button has been pressed down | -| `_on_press_move` | `[event: InteractionEvent]` | The back trigger button has been moved while pressed down | -| `_on_press_up` | `[event: InteractionEvent]` | The back trigger button has been released | -| `_on_grab_down` | `[event: InteractionEvent]` | The side grab button been pressed down | -| `_on_grab_move` | `[event: InteractionEvent]` | The side grab button been pressed down | -| `_on_grab_up` | `[event: InteractionEvent]` | The side grab button been released | -| `_on_ray_enter` | `[event: InteractionEvent]` | The ray-cast enters the the collision body | -| `_on_ray_leave` | `[event: InteractionEvent]` | The ray-cast leaves the the collision body | +| `_on_click` | `[event: EventRay]` | The back trigger button has been pressed and released | +| `_on_press_down` | `[event: EventRay]` | The back trigger button has been pressed down | +| `_on_press_move` | `[event: EventRay]` | The back trigger button has been moved while pressed down | +| `_on_press_up` | `[event: EventRay]` | The back trigger button has been released | +| `_on_grab_down` | `[event: EventRay]` | The side grab button been pressed down | +| `_on_grab_move` | `[event: EventRay]` | The side grab button been pressed down | +| `_on_grab_up` | `[event: EventRay]` | The side grab button been released | +| `_on_ray_enter` | `[event: EventRay]` | The ray-cast enters the the collision body | +| `_on_ray_leave` | `[event: EventRay]` | The ray-cast leaves the the collision body | +| `_on_key_down` | `[event: EventKey]` | The ray-cast leaves the the collision body | +| `_on_key_up` | `[event: EventKey]` | The ray-cast leaves the the collision body | +| `_on_focus_in` | `[event: EventFocus]` | The node is got focused | +| `_on_focus_out` | `[event: EventFocus]` | The node lost focus | + +After considering using the build in godot event system, I've decided that it would be better to use a custom event system. +The reason being that we would have to check each tick if the event matches the desired one which seems very inefficient compared to using signals like the browser does. +Thus I've decided to use a custom event system that is similar to the one used in the browser. + +### UI Groups + +| Group | Description | +| -- | -- | +| `ui_focus` | The element can be focused | +| `ui_focus_skip` | The focus will not be reset. Useful for keyboard | + ### Functions diff --git a/addons/debug_draw_3d/LICENSE b/addons/debug_draw_3d/LICENSE new file mode 100644 index 0000000..d5bafad --- /dev/null +++ b/addons/debug_draw_3d/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 DmitriySalnikov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the Software), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, andor sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/addons/debug_draw_3d/README.md b/addons/debug_draw_3d/README.md new file mode 100644 index 0000000..a2c272b --- /dev/null +++ b/addons/debug_draw_3d/README.md @@ -0,0 +1,183 @@ +![icon](/images/icon.png) + +# Debug drawing utility for Godot + +This is an add-on for debug drawing in 3D and for some 2D overlays, which is written in `C++` and can be used with `GDScript` or `C#`. + +Based on my previous addon, which was developed only for C# https://github.com/DmitriySalnikov/godot_debug_draw_cs, and which was inspired by Zylann's GDScript addon https://github.com/Zylann/godot_debug_draw + +## [Godot 3 version](https://github.com/DmitriySalnikov/godot_debug_draw_3d/tree/godot_3) + +## Support me + +Your support adds motivation to develop my public projects. + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I53VZ2D) + +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/dmitriysalnikov) + +[qiwi](https://qiwi.com/n/DMITRIYSALNIKOV) + +## Features + +3D: + +* Arrow +* Billboard opaque square +* Box +* Camera Frustum +* Cylinder +* Gizmo +* Grid +* Line +* Line Path +* Line with Arrow +* Points +* Position 3D (3 crossing axes) +* Sphere + +2D: + +* **[Work in progress]** + +Overlay: + +* Text (with grouping and coloring) +* FPS Graph +* Custom Graphs + +Precompiled for: + +* Windows +* Linux +* macOS +* Android + +## Download + +To download, use the [Godot Asset Library](https://godotengine.org/asset-library/asset/1766) or download the archive by clicking the button at the top of the main repository page: `Code -> Download ZIP`, then unzip it to your project folder. Or use one of the stable versions from the [GitHub Releases](https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases) page (just download one of the `Source Codes` in assets). + +## Usage + +* Close editor +* Copy `addons/debug_draw_3d` to your `addons` folder, create it if the folder doesn't exist +* Launch editor + +### C\# + +When you start the engine for the first time, bindings for `C#` will be generated automatically. If this does not happen, you can manually generate them through the `Project - Tools - Debug Draw` menu. + +![project_tools_menu](/images/project_tools_menu.png) + +## Examples + +More examples can be found in the `examples_dd3d/` folder. + +Simple test: + +```gdscript +func _process(delta: float) -> void: + var _time = Time.get_ticks_msec() / 1000.0 + var box_pos = Vector3(0, sin(_time * 4), 0) + var line_begin = Vector3(-1, sin(_time * 4), 0) + var line_end = Vector3(1, cos(_time * 4), 0) + + DebugDraw3D.draw_box(box_pos, Vector3(1, 2, 1), Color(0, 1, 0)) + DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0)) + DebugDraw2D.set_text("Time", _time) + DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn()) + DebugDraw2D.set_text("FPS", Engine.get_frames_per_second()) + DebugDraw2D.set_text("delta", delta) +``` + +```csharp +public override void _Process(float delta) +{ + var _time = Time.GetTicksMsec() / 1000.0f; + var box_pos = new Vector3(0, Mathf.Sin(_time * 4f), 0); + var line_begin = new Vector3(-1, Mathf.Sin(_time * 4f), 0); + var line_end = new Vector3(1, Mathf.Cos(_time * 4f), 0); + + DebugDraw3D.DrawBox(box_pos, new Vector3(1, 2, 1), new Color(0, 1, 0)); + DebugDraw3D.DrawLine(line_begin, line_end, new Color(1, 1, 0)); + DebugDraw2D.SetText("Time", _time); + DebugDraw2D.SetText("Frames drawn", Engine.GetFramesDrawn()); + DebugDraw2D.SetText("FPS", Engine.GetFramesPerSecond()); + DebugDraw2D.SetText("delta", delta); +} +``` + +![screenshot_1](/images/screenshot_1.png) + +## API + +A list of all functions is available in the documentation inside the editor. +![screenshot_4](/images/screenshot_4.png) + +Besides `DebugDraw2D/3D`, you can also use `Dbg2/3`. + +```gdscript + DebugDraw3D.draw_box_xf(Transform3D(), Color.GREEN) + Dbg3.draw_box_xf(Transform3D(), Color.GREEN) + + DebugDraw2D.set_text("delta", delta) + Dbg2.set_text("delta", delta) +``` + +But unfortunately at the moment `GDExtension` does not support adding documentation. + +## Exporting a project + +Most likely, when exporting a release version of a game, you don't want to export the debug library along with it. But since there is still no `Conditional Compilation` in `GDScript`, so I decided to create a `dummy` library that has the same API as a regular library, but has minimal impact on performance, even if calls to its methods occur. The `dummy` library is used by default in the release version. However if you need to use debug rendering in the release version, then you can add the `forced_dd3d` feature when exporting. In this case, the release library with all the functionality will be used. + +![export_features](/images/export_features.png) + +In C#, these tags are not taken into account at compile time, so the Release build will use Runtime checks to disable draw calls. If you want to avoid this, you can manually specify the `FORCED_DD3D` symbol. + +![csharp_compilation_symbols](/images/csharp_compilation_symbols.png) + +## Known issues and limitations + +Enabling occlusion culing can lower fps instead of increasing it. At the moment I do not know how to speed up the calculation of the visibility of objects. + +The text in the keys and values of a text group cannot contain multi-line strings. + +The entire text overlay can only be placed in one corner, unlike `DataGraphs`. + +[Frustum of Camera3D does not take into account the window size from ProjectSettings](https://github.com/godotengine/godot/issues/70362). + +**The version for Godot 4.0 requires explicitly specifying the exact data types, otherwise errors may occur.** + +## More screenshots + +`DebugDrawDemoScene.tscn` in editor +![screenshot_2](/images/screenshot_2.png) + +`DebugDrawDemoScene.tscn` in play mode +![screenshot_3](/images/screenshot_3.png) + +## Build + +As well as for the engine itself, you will need to configure the [environment](https://docs.godotengine.org/en/4.1/contributing/development/compiling/index.html). +And also you need to apply several patches: + +```bash +cd godot-cpp +git apply --ignore-space-change --ignore-whitespace ../patches/always_build_fix.patch +git apply --ignore-space-change --ignore-whitespace ../patches/1165.patch +# Optional +## Build only the necessary classes +git apply --ignore-space-change --ignore-whitespace ../patches/godot_cpp_exclude_unused_classes.patch +## Faster build +git apply --ignore-space-change --ignore-whitespace ../patches/unity_build.patch +``` + +Then you can just run scons as usual: + +```bash +# build for the current system. +# target=editor is used for both the editor and the debug template. +scons target=editor dev_build=yes debug_symbols=yes +# build for the android. ANDROID_NDK_ROOT is required in your environment variables. +scons platform=android target=template_release arch=arm64v8 +``` diff --git a/addons/debug_draw_3d/debug_draw_3d.gdextension b/addons/debug_draw_3d/debug_draw_3d.gdextension new file mode 100644 index 0000000..c914b15 --- /dev/null +++ b/addons/debug_draw_3d/debug_draw_3d.gdextension @@ -0,0 +1,55 @@ +[configuration] + +entry_symbol = "debug_draw_3d_library_init" +compatibility_minimum = "4.1" + +[dependencies] + +# example.x86_64 = { "relative or absolute path to the dependency" : "the path relative to the exported project", } + +macos = { } +windows.x86_64 = { } +linux.x86_64 = { } + +android.arm32 = { } +android.arm64 = { } +android.x86_32 = { } +android.x86_64 = { } + +macos.template_release = { } +windows.template_release.x86_64 = { } +linux.template_release.x86_64 = { } + +android.template_release.arm32 = { } +android.template_release.arm64 = { } +android.template_release.x86_32 = { } +android.template_release.x86_64 = { } + +[libraries] + +macos = "libs/libdd3d.macos.editor.universal.dylib" +windows.x86_64 = "libs/libdd3d.windows.editor.x86_64.dll" +linux.x86_64 = "libs/libdd3d.linux.editor.x86_64.so" + +android.arm32 = "libs/libdd3d.android.template_debug.arm32.so" +android.arm64 = "libs/libdd3d.android.template_debug.arm64.so" +android.x86_32 = "libs/libdd3d.android.template_debug.x86_32.so" +android.x86_64 = "libs/libdd3d.android.template_debug.x86_64.so" + +macos.template_release = "libs/libdd3d.macos.template_release.universal.dylib" +windows.template_release.x86_64 = "libs/libdd3d.windows.template_release.x86_64.dll" +linux.template_release.x86_64 = "libs/libdd3d.linux.template_release.x86_64.so" + +android.template_release.arm32 = "libs/libdd3d.android.template_release.arm32.so" +android.template_release.arm64 = "libs/libdd3d.android.template_release.arm64.so" +android.template_release.x86_32 = "libs/libdd3d.android.template_release.x86_32.so" +android.template_release.x86_64 = "libs/libdd3d.android.template_release.x86_64.so" + +macos.template_release.forced_dd3d = "libs/libdd3d.macos.template_release.universal.enabled.dylib" +windows.template_release.x86_64.forced_dd3d = "libs/libdd3d.windows.template_release.x86_64.enabled.dll" +linux.template_release.x86_64.forced_dd3d = "libs/libdd3d.linux.template_release.x86_64.enabled.so" + +android.template_release.arm32.forced_dd3d = "libs/libdd3d.android.template_release.arm32.enabled.so" +android.template_release.arm64.forced_dd3d = "libs/libdd3d.android.template_release.arm64.enabled.so" +android.template_release.x86_32.forced_dd3d = "libs/libdd3d.android.template_release.x86_32.enabled.so" +android.template_release.x86_64.forced_dd3d = "libs/libdd3d.android.template_release.x86_64.enabled.so" diff --git a/addons/debug_draw_3d/libs/.gdignore b/addons/debug_draw_3d/libs/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so new file mode 100644 index 0000000..a8c5001 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fb41898f762f555f379bc351a0d2d82bc9cbf3b701a1f9fe8035646ddad44b5 +size 2928328 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so new file mode 100644 index 0000000..ae4fe93 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dfdb52ffe2e428ea0a0b9ff951b986546291f383f37c957d7de4c367f79892f +size 2993032 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so new file mode 100644 index 0000000..a6c31e2 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c580b7b4d44342c56d1990ab09358b63fa60cf99846a355c86ceb2cb5023b14 +size 3057172 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so new file mode 100644 index 0000000..59d21d3 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6c2b95269938e60e709539c8166cd9e5f450bd3fbd2b6bdfdf652fca3f594db +size 2977744 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so new file mode 100644 index 0000000..ac25634 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c40f93bd53bc4b132d075f18814b1198e71df6b9f2601c11f0cfbc2494698cf +size 1804756 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so new file mode 100644 index 0000000..267c209 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0181cc7c1a08ecc39de983b1b1e35f66572479da533f744190fa803cbde44612 +size 2001576 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so new file mode 100644 index 0000000..382bfdb --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24497f5a2ad8a7d99e2acb62d6c526017d7c365f8fd7fd6ae3042d96bd6e980a +size 1996072 diff --git a/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so new file mode 100644 index 0000000..a38fb1e --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eab3ff00fc2d7347a6b166e67a3800d6117fa84bcfa3317429d6e0bff896654 +size 2028472 diff --git a/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so new file mode 100644 index 0000000..07a3f4e --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b39c1ca40f0a22e66e742daba8e050884caa7a3ff959824b0c99a7e061fd869 +size 3806144 diff --git a/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so new file mode 100644 index 0000000..619483e --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bfbec2037276c6858cbfc4019bb0381eb9bd83cdcdfcfa3cc802bfc0d5f427b +size 3072224 diff --git a/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so new file mode 100644 index 0000000..d9df68f --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7efcb3de6de7d5a878c4a221931a94a74f3741a6aeb7d7e9a4f19fb07bf1115c +size 2193840 diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.dylib b/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.dylib new file mode 100644 index 0000000..1ba502d --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:818647a1234354725573918ce71972193812dd19d9ffb4bbe35f4a17ff48341f +size 5739144 diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.dylib b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.dylib new file mode 100644 index 0000000..f05a3b9 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bbf87c77f9c9fe6643948257e91f271825a2874172c97cdbd915a6203a6eff3 +size 3339680 diff --git a/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.dylib b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.dylib new file mode 100644 index 0000000..6ac5ee5 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0f280b00b057266534e477e4b33e476f108b41d1927d4db39e8d1e3a781adc5 +size 4689496 diff --git a/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll b/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll new file mode 100644 index 0000000..2206af5 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac3ac3647171c53e2f47564df2e25eff19fbfab8bf763d0078fe1c82476d8761 +size 1291264 diff --git a/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll new file mode 100644 index 0000000..07578b6 --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d3113daaa53fe431ca8ce55f127c0e41ef4243ab5aadd359dc6d2c63b31f057 +size 680448 diff --git a/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll new file mode 100644 index 0000000..c26c24b --- /dev/null +++ b/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af5ae3a80d09e6212d21e4cf608aa38f711d230914918d0f2f427bdcc5488e24 +size 862208 diff --git a/assets/design.afdesign b/assets/design.afdesign index d8ce1f5..0b5c61b 100644 --- a/assets/design.afdesign +++ b/assets/design.afdesign @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bc1c66cc868d68efe828c304c6ed348136cdfd8b814fe0f243954f328648997 -size 3586528 +oid sha256:84daccd9540dfcf964fc4ec224ed88238f3a9fac55a30d1f48161d1ede0a7907 +size 2804209 diff --git a/assets/icons/wifi_white_24dp.svg b/assets/icons/wifi_white_24dp.svg new file mode 100644 index 0000000..0c6dad7 --- /dev/null +++ b/assets/icons/wifi_white_24dp.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:053d1e42a72e988fad4b2ef981eb72ec850f1f7da6963b257c8935e8392cb37b +size 324 diff --git a/assets/icons/wifi_white_24dp.svg.import b/assets/icons/wifi_white_24dp.svg.import new file mode 100644 index 0000000..60fc20e --- /dev/null +++ b/assets/icons/wifi_white_24dp.svg.import @@ -0,0 +1,39 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co5fgm68t4j6o" +path.s3tc="res://.godot/imported/wifi_white_24dp.svg-9edf937c7c00e607b2e1a7211dd6ea49.s3tc.ctex" +path.etc2="res://.godot/imported/wifi_white_24dp.svg-9edf937c7c00e607b2e1a7211dd6ea49.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/icons/wifi_white_24dp.svg" +dest_files=["res://.godot/imported/wifi_white_24dp.svg-9edf937c7c00e607b2e1a7211dd6ea49.s3tc.ctex", "res://.godot/imported/wifi_white_24dp.svg-9edf937c7c00e607b2e1a7211dd6ea49.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +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=0 +svg/scale=8.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/logo.png b/assets/logo.png index e7a0b12..039f438 100644 --- a/assets/logo.png +++ b/assets/logo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f10630e063d8d85238a4487595ec7869c79798ae7547ea0f636d6c78e0dc178 -size 85601 +oid sha256:fc77fb979fd1cec813e7bf6002ad05da443097ca5e04712f36f65a1dbc17cef2 +size 85732 diff --git a/assets/materials/interface.tres b/assets/materials/interface.tres deleted file mode 100644 index 3af2e71..0000000 --- a/assets/materials/interface.tres +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:55d5f30db336a8f8f7633743c23e41077d5c1a1b900c475a6d3811746eba3d1b -size 141 diff --git a/assets/materials/ui_element.material b/assets/materials/ui_element.material new file mode 100644 index 0000000..78953be --- /dev/null +++ b/assets/materials/ui_element.material @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57f8bfca31acba3de3a15a2b9a88b8afd12f8db9c356f14d906840dcee48d6f6 +size 1021 diff --git a/assets/meta/Hero-Cover.png b/assets/meta/Hero-Cover.png index 271c8a0..af382e0 100644 --- a/assets/meta/Hero-Cover.png +++ b/assets/meta/Hero-Cover.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:451fcd3ec43720477df80b56031118ea0b947cfa32198bf47ef095b53beadf95 -size 1656729 +oid sha256:8da6b8e647a21f76a72096dad5eabd775f668ab0e45bdf8321633d99f894b175 +size 1656450 diff --git a/content/entities/light/light.gd b/content/entities/light/light.gd index e3a768b..92f97ea 100644 --- a/content/entities/light/light.gd +++ b/content/entities/light/light.gd @@ -13,10 +13,10 @@ var brightness = 0 # 0-255 # Called when the node enters the scene tree for the first time. func _ready(): - var stateInfo = await HomeAdapters.adapter.get_state(entity_id) + var stateInfo = await HomeApi.get_state(entity_id) set_state(stateInfo["state"] == "on") - await HomeAdapters.adapter.watch_state(entity_id, func(new_state): + await HomeApi.watch_state(entity_id, func(new_state): if (new_state["state"] == "on") == state: return set_state(new_state["state"] == "on") @@ -44,7 +44,7 @@ func _on_click(event): if !state && brightness != null: attributes["brightness"] = int(brightness) - HomeAdapters.adapter.set_state(entity_id, "on" if !state else "off", attributes) + HomeApi.set_state(entity_id, "on" if !state else "off", attributes) set_state(!state, brightness) else: _on_clickable_on_click(event) @@ -71,5 +71,5 @@ func _on_clickable_on_click(event): slider_knob.position = new_pos - HomeAdapters.adapter.set_state(entity_id, "on" if state else "off", {"brightness": int(ratio * 255)}) + HomeApi.set_state(entity_id, "on" if state else "off", {"brightness": int(ratio * 255)}) set_state(state, ratio * 255) diff --git a/content/entities/sensor/sensor.gd b/content/entities/sensor/sensor.gd index 149d7bd..5af77dc 100644 --- a/content/entities/sensor/sensor.gd +++ b/content/entities/sensor/sensor.gd @@ -5,10 +5,10 @@ extends StaticBody3D # Called when the node enters the scene tree for the first time. func _ready(): - var stateInfo = await HomeAdapters.adapter.get_state(entity_id) + var stateInfo = await HomeApi.get_state(entity_id) set_text(stateInfo) - await HomeAdapters.adapter.watch_state(entity_id, func(new_state): + await HomeApi.watch_state(entity_id, func(new_state): set_text(new_state) ) diff --git a/content/entities/switch/switch.gd b/content/entities/switch/switch.gd index 3239121..c2df949 100644 --- a/content/entities/switch/switch.gd +++ b/content/entities/switch/switch.gd @@ -5,7 +5,7 @@ extends StaticBody3D # Called when the node enters the scene tree for the first time. func _ready(): - var stateInfo = await HomeAdapters.adapter.get_state(entity_id) + var stateInfo = await HomeApi.get_state(entity_id) if stateInfo == null: return @@ -14,7 +14,7 @@ func _ready(): else: sprite.set_frame(1) - await HomeAdapters.adapter.watch_state(entity_id, func(new_state): + await HomeApi.watch_state(entity_id, func(new_state): if new_state["state"] == "on": sprite.set_frame(0) else: @@ -23,7 +23,7 @@ func _ready(): func _on_click(event): - HomeAdapters.adapter.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") + HomeApi.set_state(entity_id, "off" if sprite.get_frame() == 0 else "on") if sprite.get_frame() == 0: sprite.set_frame(1) else: diff --git a/content/functions/clickable.gd b/content/functions/clickable.gd index bbf1366..01dcab5 100644 --- a/content/functions/clickable.gd +++ b/content/functions/clickable.gd @@ -1,39 +1,39 @@ extends Function class_name Clickable -signal on_click(event: Dictionary) -signal on_press_down(event: Dictionary) -signal on_press_move(event: Dictionary) -signal on_press_up(event: Dictionary) -signal on_grab_down(event: Dictionary) -signal on_grab_move(event: Dictionary) -signal on_grab_up(event: Dictionary) -signal on_ray_enter(event: Dictionary) -signal on_ray_leave(event: Dictionary) +signal on_click(event: EventRay) +signal on_press_down(event: EventRay) +signal on_press_move(event: EventRay) +signal on_press_up(event: EventRay) +signal on_grab_down(event: EventRay) +signal on_grab_move(event: EventRay) +signal on_grab_up(event: EventRay) +signal on_ray_enter(event: EventRay) +signal on_ray_leave(event: EventRay) -func _on_click(event: Dictionary): +func _on_click(event: EventRay): on_click.emit(event) -func _on_press_down(event: Dictionary): +func _on_press_down(event: EventRay): on_press_down.emit(event) -func _on_press_move(event: Dictionary): +func _on_press_move(event: EventRay): on_press_move.emit(event) -func _on_press_up(event: Dictionary): +func _on_press_up(event: EventRay): on_press_up.emit(event) -func _on_grab_down(event: Dictionary): +func _on_grab_down(event: EventRay): on_grab_down.emit(event) -func _on_grab_move(event: Dictionary): +func _on_grab_move(event: EventRay): on_grab_move.emit(event) -func _on_grab_up(event: Dictionary): +func _on_grab_up(event: EventRay): on_grab_up.emit(event) -func _on_ray_enter(event: Dictionary): +func _on_ray_enter(event: EventRay): on_ray_enter.emit(event) -func _on_ray_leave(event: Dictionary): +func _on_ray_leave(event: EventRay): on_ray_leave.emit(event) \ No newline at end of file diff --git a/content/functions/movable.gd b/content/functions/movable.gd index e0b1cad..07f3bad 100644 --- a/content/functions/movable.gd +++ b/content/functions/movable.gd @@ -4,14 +4,14 @@ class_name Movable var hit_node := Node3D.new() -func _on_grab_down(event): +func _on_grab_down(event: EventRay): event.controller.add_child(hit_node) hit_node.global_transform = get_parent().global_transform -func _on_grab_move(event): +func _on_grab_move(_event: EventRay): get_parent().global_transform = hit_node.global_transform -func _on_grab_up(event): +func _on_grab_up(event: EventRay): event.controller.remove_child(hit_node) func _get_configuration_warnings() -> PackedStringArray: diff --git a/content/main.gd b/content/main.gd index ce68db4..963a577 100644 --- a/content/main.gd +++ b/content/main.gd @@ -12,6 +12,7 @@ var sky_passthrough = preload("res://assets/materials/sky_passthrough.material") func _ready(): # In case we're running on the headset, use the passthrough sky if OS.get_name() == "Android": + OS.request_permissions() environment.environment.sky.set_material(sky_passthrough) house.visible = false else: diff --git a/content/main.tscn b/content/main.tscn index 7609b70..d0c91b9 100644 --- a/content/main.tscn +++ b/content/main.tscn @@ -1,13 +1,13 @@ [gd_scene load_steps=13 format=3 uid="uid://eecv28y6jxk4"] [ext_resource type="PackedScene" uid="uid://clc5dre31iskm" path="res://addons/godot-xr-tools/xr/start_xr.tscn" id="1_i4c04"] -[ext_resource type="Script" path="res://content/raycast.gd" id="1_tsqxc"] [ext_resource type="Script" path="res://content/main.gd" id="1_uvrd4"] -[ext_resource type="PackedScene" uid="uid://b30w6tywfj4fp" path="res://content/controller_left.tscn" id="2_2lraw"] -[ext_resource type="Texture2D" uid="uid://bo55nohs0wsgf" path="res://assets/materials/pointer.png" id="4_wcfej"] +[ext_resource type="PackedScene" uid="uid://b30w6tywfj4fp" path="res://content/system/controller_left/controller_left.tscn" id="2_2lraw"] +[ext_resource type="PackedScene" uid="uid://d3f8glx1xgm5w" path="res://content/system/raycast/raycast.tscn" id="3_67lii"] [ext_resource type="PackedScene" uid="uid://ctltchlf2j2r4" path="res://addons/xr-simulator/XRSimulator.tscn" id="5_3qc8g"] [ext_resource type="Material" uid="uid://bf5ina366dwm6" path="res://assets/materials/sky.material" id="5_wgwf8"] [ext_resource type="PackedScene" uid="uid://83lb5p4e0qk0" path="res://content/scenes/house.tscn" id="8_qkrg7"] +[ext_resource type="PackedScene" uid="uid://lrehk38exd5n" path="res://content/system/keyboard/keyboard.tscn" id="9_e5n3p"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_m58yb"] ao_enabled = true @@ -33,33 +33,20 @@ script = ExtResource("1_uvrd4") [node name="XROrigin3D" type="XROrigin3D" parent="."] [node name="XRCamera3D" type="XRCamera3D" parent="XROrigin3D"] -transform = Transform3D(1, 2.18865e-10, 3.7835e-10, 3.85836e-11, 1, 2.08752e-08, -2.91038e-11, 8.6402e-12, 1, 0.0356344, 0.79808, 0.202806) +transform = Transform3D(1, 1.8976e-10, 4.07454e-10, 6.76872e-11, 1, 2.08738e-08, -5.82077e-11, 1.04592e-11, 1, 0.0356618, 0.71033, 0.00564247) [node name="XRControllerLeft" parent="XROrigin3D" instance=ExtResource("2_2lraw")] transform = Transform3D(0.999999, -1.39633e-11, 0, 9.48075e-12, 1, 0, 0, 0, 1, -0.355145, 0.550439, -0.477945) [node name="XRControllerRight" type="XRController3D" parent="XROrigin3D"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.488349, 0.559219, -0.2988) +transform = Transform3D(0.999999, -1.39633e-11, 0, 9.48075e-12, 1, 0, 0, 0, 1, 0.272616, 0.559282, -0.468369) tracker = &"right_hand" pose = &"aim" [node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/XRControllerRight"] mesh = SubResource("BoxMesh_ir3co") -[node name="Raycast" type="Node3D" parent="XROrigin3D/XRControllerRight" node_paths=PackedStringArray("ray")] -script = ExtResource("1_tsqxc") -ray = NodePath("RayCast3D") - -[node name="RayCast3D" type="RayCast3D" parent="XROrigin3D/XRControllerRight/Raycast"] -transform = Transform3D(-4.62364e-10, 4.3714e-08, 0.999998, 0.999999, -4.37194e-08, 9.2768e-12, 4.37103e-08, 0.999999, -4.3714e-08, -0.000467122, 0.00228411, -0.0016689) -target_position = Vector3(0, -5, 0) - -[node name="Decal" type="Decal" parent="XROrigin3D/XRControllerRight/Raycast"] -transform = Transform3D(0.999999, -0.000567105, -2.5179e-05, -2.51789e-05, 4.39886e-08, -0.999999, 0.000567105, 1, 2.97064e-08, -0.000775784, -1.09076e-05, -2.46767) -size = Vector3(0.02, 4.91995, 0.02) -texture_albedo = ExtResource("4_wcfej") -upper_fade = 0.000985425 -lower_fade = 0.000919435 +[node name="Raycast" parent="XROrigin3D/XRControllerRight" instance=ExtResource("3_67lii")] [node name="StartXR" parent="." instance=ExtResource("1_i4c04")] enable_passthrough = true @@ -77,3 +64,6 @@ xr_origin = NodePath("../XROrigin3D") [node name="House" parent="." instance=ExtResource("8_qkrg7")] transform = Transform3D(0.404247, 0.000180645, 0.914648, 0.00017221, 0.999999, -0.000273614, -0.914648, 0.00026812, 0.404247, -0.343479, 0.000110551, 1.91547) visible = false + +[node name="Keyboard" parent="." instance=ExtResource("9_e5n3p")] +transform = Transform3D(0.499999, -6.98142e-12, 0, 4.74065e-12, 0.5, -2.27374e-13, 0, 2.27374e-13, 0.5, -0.125313, 0.424282, -0.263966) diff --git a/content/raycast.gd b/content/raycast.gd deleted file mode 100644 index 530e703..0000000 --- a/content/raycast.gd +++ /dev/null @@ -1,121 +0,0 @@ -extends Node3D - -@onready var _controller := XRHelpers.get_xr_controller(self) -@export var ray: RayCast3D -@export var timespan_click = 200.0 - -# Called when the node enters the scene tree for the first time. -func _ready(): - _controller.button_pressed.connect(_on_button_pressed) - _controller.button_released.connect(_on_button_released) - -var _last_collided: Object = null -var _is_pressed := false -var _is_grabbed := false -var _time_pressed := 0.0 -var _moved := false -var _click_point := Vector3.ZERO - -func _physics_process(delta): - _handle_enter_leave() - _handle_move() - -func _handle_move(): - var time_passed = Time.get_ticks_msec() - _time_pressed - if time_passed <= timespan_click || (_is_pressed == false && _is_grabbed == false): - return - - _moved = true - - if _is_pressed: - _call_fn(_last_collided, "_on_press_move") - - if _is_grabbed: - _call_fn(_last_collided, "_on_grab_move") - -func _handle_enter_leave(): - var collider = ray.get_collider() - - if collider == _last_collided || _is_grabbed || _is_pressed: - return - - _call_fn(collider, "_on_ray_enter") - _call_fn(_last_collided, "_on_ray_leave") - - _last_collided = collider - -func _on_button_pressed(button): - var collider = ray.get_collider() - - if collider == null: - return - - match button: - "trigger_click": - _is_pressed = true - _time_pressed = Time.get_ticks_msec() - _click_point = ray.get_collision_point() - _call_fn(collider, "_on_press_down") - "grip_click": - _is_grabbed = true - _click_point = ray.get_collision_point() - _call_fn(collider, "_on_grab_down") - -func _on_button_released(button): - if _last_collided == null: - return - - match button: - "trigger_click": - if _is_pressed: - if _moved == false: - _call_fn(_last_collided, "_on_click") - _call_fn(_last_collided, "_on_press_up") - _is_pressed = false - _last_collided = null - _moved = false - "grip_click": - if _is_grabbed: - _call_fn(_last_collided, "_on_grab_up") - _is_grabbed = false - _last_collided = null - _moved = false - -func _call_fn(collider: Variant, fn_name: String, node: Node3D = null, event = null): - if collider == null: - return - - if node == null: - node = collider - event = { - "controller": _controller, - "ray": ray, - "target": collider, - } - - if node.has_method(fn_name): - var result = node.call(fn_name, event) - - if result != null && result is Dictionary: - result.merge(event, true) - event = result - - if result != null && result is bool && result == false: - # Stop the event from bubbling up - return - - for child in node.get_children(): - if child is Function && child.has_method(fn_name): - child.call(fn_name, event) - - - var parent = node.get_parent() - - if parent != null && parent is Node3D: - _call_fn(collider, fn_name, parent, event) - else: - # in case the top has been reached - _call_global_fn(fn_name, event) - -func _call_global_fn(fn_name: String, event = null): - Events.get(fn_name.substr(1)).emit(event) diff --git a/content/controller_left.gd b/content/system/controller_left/controller_left.gd similarity index 88% rename from content/controller_left.gd rename to content/system/controller_left/controller_left.gd index 355bc3b..a8fa9d0 100644 --- a/content/controller_left.gd +++ b/content/system/controller_left/controller_left.gd @@ -34,11 +34,11 @@ var trash_bin_large: bool = false: func _ready(): trash_bin_visible = false - Events.on_grab_down.connect(func(event): + EventSystem.on_grab_down.connect(func(event: EventRay): trash_bin_visible = event.target.is_in_group("entity") ) - Events.on_grab_move.connect(func(event): + EventSystem.on_grab_move.connect(func(event): if !trash_bin_visible: return @@ -53,7 +53,7 @@ func _ready(): ) - Events.on_grab_up.connect(func(event): + EventSystem.on_grab_up.connect(func(_event: EventRay): if !trash_bin_visible: return diff --git a/content/controller_left.tscn b/content/system/controller_left/controller_left.tscn similarity index 91% rename from content/controller_left.tscn rename to content/system/controller_left/controller_left.tscn index de6a854..fb8344e 100644 --- a/content/controller_left.tscn +++ b/content/system/controller_left/controller_left.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=10 format=3 uid="uid://b30w6tywfj4fp"] +[gd_scene load_steps=11 format=3 uid="uid://b30w6tywfj4fp"] -[ext_resource type="Script" path="res://content/controller_left.gd" id="1_2j3qs"] +[ext_resource type="Script" path="res://content/system/controller_left/controller_left.gd" id="1_2j3qs"] [ext_resource type="PackedScene" uid="uid://c3kdssrmv84kv" path="res://content/ui/menu/menu.tscn" id="1_ccbr3"] [ext_resource type="PackedScene" uid="uid://cj42our8uhfq6" path="res://assets/models/trash_bin/trash_bin.gltf" id="3_m33ce"] +[ext_resource type="PackedScene" uid="uid://d3f8glx1xgm5w" path="res://content/system/raycast/raycast.tscn" id="4_n7lao"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_m58yb"] ao_enabled = true @@ -119,3 +120,5 @@ shape = SubResource("CylinderShape3D_x2eyr") libraries = { "": SubResource("AnimationLibrary_hkli8") } + +[node name="Raycast" parent="." instance=ExtResource("4_n7lao")] diff --git a/content/system/keyboard/keyboard.gd b/content/system/keyboard/keyboard.gd new file mode 100644 index 0000000..1eb2ca4 --- /dev/null +++ b/content/system/keyboard/keyboard.gd @@ -0,0 +1,101 @@ +@tool +extends StaticBody3D + +const button_scene = preload("res://content/ui/components/button/button.tscn") + +@onready var keys = $Keys +@onready var caps_button = $Caps +@onready var backspace_button = $Backspace +@onready var paste_button = $Paste +var key_list = [ + [KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_ASCIITILDE], + [KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_SLASH], + [KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_COLON, KEY_BACKSLASH], + [KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA , KEY_PERIOD, KEY_MINUS] +] + +var caps = false : + set(value): + caps = value + update_labels() + +func _ready(): + for row in key_list: + for key in row: + var button = create_key(key) + keys.add_child(button) + + if Engine.is_editor_hint(): + continue + + button.on_button_down.connect(func(): + _emit_event("key_down", key) + ) + button.on_button_up.connect(func(): + _emit_event("key_up", key) + ) + + keys.columns = key_list[0].size() + + if Engine.is_editor_hint(): + return + + backspace_button.on_button_down.connect(func(): + _emit_event("key_down", KEY_BACKSPACE) + ) + + backspace_button.on_button_up.connect(func(): + _emit_event("key_up", KEY_BACKSPACE) + ) + + caps_button.on_button_down.connect(func(): + caps = true + _emit_event("key_down", KEY_CAPSLOCK) + ) + + caps_button.on_button_up.connect(func(): + caps = false + _emit_event("key_up", KEY_CAPSLOCK) + ) + + paste_button.on_button_down.connect(func(): + # There is no KEY_PASTE obviously, so we use KEY_INSERT for now + _emit_event("key_down", KEY_INSERT) + ) + + paste_button.on_button_up.connect(func(): + _emit_event("key_up", KEY_INSERT) + ) + +func create_key(key: Key): + var button = button_scene.instantiate() + + var label = Label3D.new() + label.text = EventKey.key_to_string(key, caps) + label.pixel_size = 0.001 + label.position = Vector3(0, 0.012, 0) + label.rotate_x(deg_to_rad(-90)) + label.add_to_group("button_label") + + button.set_meta("key", key) + button.add_to_group("ui_focus_skip") + button.add_child(label) + + return button + +func update_labels(): + for key_button in keys.get_children(): + var label = key_button.get_children()[key_button.get_children().size() - 1] + if caps: + label.text = label.text.to_upper() + else: + label.text = label.text.to_lower() + +func _emit_event(type: String, key: Key): + var event = EventKey.new() + event.key = key + event.shift_pressed = caps + + EventSystem.emit(type, event) + print("Emitting event: " + type + " " + EventKey.key_to_string(key, caps)) + diff --git a/content/system/keyboard/keyboard.tscn b/content/system/keyboard/keyboard.tscn new file mode 100644 index 0000000..83da9bc --- /dev/null +++ b/content/system/keyboard/keyboard.tscn @@ -0,0 +1,52 @@ +[gd_scene load_steps=6 format=3 uid="uid://lrehk38exd5n"] + +[ext_resource type="Script" path="res://content/system/keyboard/keyboard.gd" id="1_maojw"] +[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="1_xdpwr"] +[ext_resource type="Script" path="res://content/ui/menu/grid.gd" id="3_mx544"] +[ext_resource type="Script" path="res://content/functions/movable.gd" id="4_86fct"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_k5ib7"] +size = Vector3(0.84, 0.0402036, 0.296009) + +[node name="Keyboard" type="StaticBody3D" groups=["ui_focus_skip"]] +transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) +script = ExtResource("1_maojw") + +[node name="Backspace" parent="." groups=["ui_focus_skip"] instance=ExtResource("1_xdpwr")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.66, 0, 0.02) +metadata/key = 4194308 + +[node name="Label3D" type="Label3D" parent="Backspace"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.012, 0) +pixel_size = 0.001 +text = "back" + +[node name="Caps" parent="." groups=["ui_focus_skip"] instance=ExtResource("1_xdpwr")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.06, 0, 0.15) +toggleable = true + +[node name="Label3D" type="Label3D" parent="Caps"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.012, 0) +pixel_size = 0.001 +text = "caps" + +[node name="Paste" parent="." groups=["ui_focus_skip"] instance=ExtResource("1_xdpwr")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.66, 0, 0.18) + +[node name="Label3D" type="Label3D" parent="Paste"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.012, 0) +pixel_size = 0.001 +text = "paste" + +[node name="Keys" type="Node3D" parent="."] +script = ExtResource("3_mx544") +columns = 11 +depth_gap = 0.06 +size = Vector3(0.6, 1, 1) + +[node name="Movable" type="Node" parent="."] +script = ExtResource("4_86fct") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.296719, -0.025645, 0.0928761) +shape = SubResource("BoxShape3D_k5ib7") diff --git a/content/system/raycast/raycast.gd b/content/system/raycast/raycast.gd new file mode 100644 index 0000000..5c4cca9 --- /dev/null +++ b/content/system/raycast/raycast.gd @@ -0,0 +1,94 @@ +extends RayCast3D + +@export var is_right: bool = true + +var controller: XRController3D +var timespan_click = 200.0 + +var last_collided: Object = null +var is_pressed := false +var is_grabbed := false +var time_pressed := 0.0 +var moved := false +var click_point := Vector3.ZERO + +func _ready(): + controller = get_parent() + assert(controller is XRController3D, "XRController3D is not found in parent") + + controller.button_pressed.connect(_on_button_pressed) + controller.button_released.connect(_on_button_released) + +func _physics_process(_delta): + _handle_enter_leave() + _handle_move() + +func _handle_move(): + var time_passed = Time.get_ticks_msec() - time_pressed + if time_passed <= timespan_click || (is_pressed == false && is_grabbed == false): + return + + moved = true + + if is_pressed: + _emit_event("press_move", last_collided ) + + if is_grabbed: + _emit_event("grab_move", last_collided ) + +func _handle_enter_leave(): + var collider = get_collider() + + if collider == last_collided || is_grabbed || is_pressed: + return + + _emit_event("ray_enter", collider ) + _emit_event("ray_leave", last_collided ) + + last_collided = collider + +func _on_button_pressed(button: String): + var collider = get_collider() + + if collider == null: + return + + match button: + "trigger_click": + is_pressed = true + time_pressed = Time.get_ticks_msec() + click_point = get_collision_point() + _emit_event("press_down", collider ) + "grip_click": + is_grabbed = true + click_point = get_collision_point() + _emit_event("grab_down", collider ) + +func _on_button_released(button: String): + if last_collided == null: + return + + match button: + "trigger_click": + if is_pressed: + if moved == false: + _emit_event("click", last_collided ) + _emit_event("press_up", last_collided ) + is_pressed = false + last_collided = null + moved = false + "grip_click": + if is_grabbed: + _emit_event("grab_up", last_collided ) + is_grabbed = false + last_collided = null + moved = false + +func _emit_event(type: String, target): + var event = EventRay.new() + event.controller = controller + event.target = target + event.ray = self + event.is_right_controller = is_right + + EventSystem.emit(type, event) diff --git a/content/system/raycast/raycast.tscn b/content/system/raycast/raycast.tscn new file mode 100644 index 0000000..1d448b3 --- /dev/null +++ b/content/system/raycast/raycast.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=3 format=3 uid="uid://d3f8glx1xgm5w"] + +[ext_resource type="Texture2D" uid="uid://bo55nohs0wsgf" path="res://assets/materials/pointer.png" id="1_2f2iv"] +[ext_resource type="Script" path="res://content/system/raycast/raycast.gd" id="1_gp8nv"] + +[node name="Raycast" type="RayCast3D"] +transform = Transform3D(0.999999, -1.39624e-11, 0, 9.48108e-12, 0.999999, 0, 0, 4.54747e-13, 0.999998, -0.000467122, 0.00228411, -0.0016689) +target_position = Vector3(0, 0, -5) +script = ExtResource("1_gp8nv") + +[node name="Decal" type="Decal" parent="."] +transform = Transform3D(1, -0.000567106, -2.5179e-05, -2.5179e-05, 4.39886e-08, -1, 0.000567106, 1, 2.97068e-08, -0.000308663, -0.00229502, -2.46601) +size = Vector3(0.02, 4.91995, 0.02) +texture_albedo = ExtResource("1_2f2iv") +upper_fade = 0.000985425 +lower_fade = 0.000919435 diff --git a/content/ui/components/button/button.gd b/content/ui/components/button/button.gd index 960eef3..1d3f351 100644 --- a/content/ui/components/button/button.gd +++ b/content/ui/components/button/button.gd @@ -1,12 +1,15 @@ extends StaticBody3D class_name Button3D +signal on_button_down() +signal on_button_up() + @export var toggleable: bool = false @export var disabled: bool = false +@export var external_state: bool = false @export var initial_active: bool = false var active: bool = false : set(value): - print("set active", value) animation_player.stop() if value == active: return @@ -17,45 +20,43 @@ var active: bool = false : animation_player.play("down") else: animation_player.play_backwards("down") - get: - return active @onready var animation_player: AnimationPlayer = $AnimationPlayer -@onready var click_sound: AudioStreamPlayer = $ClickSound func _ready(): if initial_active: active = true -func _on_click(_event): +func _on_press_down(event): if disabled: - return false - - if !toggleable: + event.bubbling = false return - active = !active AudioPlayer.play_effect("click") - return { - "active": active - } - -func _on_press_down(_event): - if disabled: - return false - - if toggleable: + if external_state || toggleable: return + + active = true + on_button_down.emit() + - AudioPlayer.play_effect("click") - animation_player.play("down") -func _on_press_up(_event): +func _on_press_up(event): if disabled: - return false - - if toggleable: + event.bubbling = false return - animation_player.play_backwards("down") + if external_state: + return + + if toggleable: + active = !active + + if active: + on_button_down.emit() + else: + on_button_up.emit() + else: + active = false + on_button_up.emit() diff --git a/content/ui/components/button/button.tscn b/content/ui/components/button/button.tscn index 11dae36..a2c6572 100644 --- a/content/ui/components/button/button.tscn +++ b/content/ui/components/button/button.tscn @@ -1,13 +1,11 @@ [gd_scene load_steps=8 format=3 uid="uid://bsjqdvkt0u87c"] [ext_resource type="Script" path="res://content/ui/components/button/button.gd" id="1_74x7g"] - -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_peqek"] -albedo_color = Color(0.151534, 0.211909, 0.619523, 1) +[ext_resource type="Material" uid="uid://bujy3egn1oqac" path="res://assets/materials/ui_element.material" id="2_h7ln4"] [sub_resource type="BoxMesh" id="BoxMesh_jwpm5"] resource_local_to_scene = true -material = SubResource("StandardMaterial3D_peqek") +material = ExtResource("2_h7ln4") size = Vector3(0.05, 0.02, 0.05) [sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_o4j7g"] @@ -81,7 +79,7 @@ _data = { "down": SubResource("Animation_iu2ed") } -[node name="Button" type="StaticBody3D"] +[node name="Button" type="StaticBody3D" groups=["ui_focus"]] script = ExtResource("1_74x7g") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] diff --git a/content/ui/components/input/input.gd b/content/ui/components/input/input.gd new file mode 100644 index 0000000..5633288 --- /dev/null +++ b/content/ui/components/input/input.gd @@ -0,0 +1,135 @@ +@tool +extends StaticBody3D + +var text_handler = preload("res://content/ui/components/input/text_handler.gd").new() + +@onready var caret: MeshInstance3D = $Label/Caret +@onready var mesh_box: MeshInstance3D = $Box +@onready var collision: CollisionShape3D = $Collision +@onready var animation: AnimationPlayer = $AnimationPlayer +@onready var label: Label3D = $Label + +@export_range(0.1, 2, 0.01, "suffix:m") var width: float = 0.15: + get: + return text_handler.width + set(value): + set_width(value) + +@export var text: String: + get: + return text_handler.text + set(value): + var focused = Engine.is_editor_hint() == false && EventSystem.is_focused(self) == false + text_handler.set_text(value, focused) + + if label != null: + label.text = text_handler.get_display_text() + +var keyboard_input: bool = false + +var input_plane = Plane(Vector3.UP, Vector3.ZERO) + +func _ready(): + text_handler.label = label + text = text + width = width + + if Engine.is_editor_hint(): + return + + EventSystem.on_key_down.connect(func(event): + if EventSystem.is_focused(self) == false: + return + + text = EventKey.key_to_string(event.key, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length()) + caret.position.x = text_handler.get_caret_position() + label.text = text_handler.get_display_text() + ) + +func _input(event): + if event is InputEventKey && EventSystem.is_focused(self) && event.pressed: + if event.keycode == KEY_F1: + keyboard_input = !keyboard_input + return + + if keyboard_input: + text = EventKey.key_to_string(event.keycode, event.shift_pressed, text.substr(0, text_handler.caret_position)) + text.substr(text_handler.caret_position, text.length()) + caret.position.x = text_handler.get_caret_position() + +func _process(_delta): + if Engine.is_editor_hint(): + return + + if get_tree().debug_collisions_hint && OS.get_name() != "Android": + _draw_debug_text_gaps() + +func set_width(value: float): + text_handler.width = value + + if mesh_box == null || collision == null || label == null: + return + + mesh_box.mesh.size.x = value + collision.shape.size.x = value + label.position.x = -value / 2 + 0.002 + +func _on_press_move(event): + var ray_pos = event.ray.global_position + var ray_dir = -event.ray.global_transform.basis.z + + var local_pos = label.to_local(ray_pos) + var local_dir = label.global_transform.basis.inverse() * ray_dir + + var intersection_point = input_plane.intersects_ray(local_pos, local_dir) + + if intersection_point == null: + return + + var pos_x = intersection_point.x + text_handler.update_caret_position(pos_x) + + caret.position.x = text_handler.get_caret_position() + label.text = text_handler.get_display_text() + +func _on_focus_in(event): + var pos_x = label.to_local(event.ray.get_collision_point()).x + text_handler.update_caret_position(pos_x) + + caret.position.x = text_handler.get_caret_position() + label.text = text_handler.get_display_text() + caret.show() + animation.play("blink") + +func update_caret_position(event): + var ray_pos = event.ray.global_position + var ray_dir = -event.ray.global_transform.basis.z + + var local_pos = label.to_local(ray_pos) + var local_dir = label.global_transform.basis.inverse() * ray_dir + + var intersection_point = input_plane.intersects_ray(local_pos, local_dir) + + if intersection_point == null: + return + + var pos_x = intersection_point.x + text_handler.update_caret_position(pos_x) + + caret.position.x = text_handler.get_caret_position() + + +func _on_focus_out(_event): + animation.stop() + caret.hide() + +func _draw_debug_text_gaps(): + if text_handler.gap_offsets == null: + return + + for i in range(text_handler.gap_offsets.size()): + var offset = text_handler.gap_offsets[i] - text_handler.gap_offsets[text_handler.char_offset] + DebugDraw3D.draw_line( + label.to_global(Vector3(offset, -0.01, 0)), + label.to_global(Vector3(offset, 0.01, 0)), + Color(1, 0, 0) if i != text_handler.overflow_index else Color(0, 1, 0) + ) diff --git a/content/ui/components/input/input.tscn b/content/ui/components/input/input.tscn new file mode 100644 index 0000000..f5a390e --- /dev/null +++ b/content/ui/components/input/input.tscn @@ -0,0 +1,84 @@ +[gd_scene load_steps=10 format=3 uid="uid://blrhy2uccrdn4"] + +[ext_resource type="Material" uid="uid://bujy3egn1oqac" path="res://assets/materials/ui_element.material" id="1_0kd7r"] +[ext_resource type="Script" path="res://content/ui/components/input/input.gd" id="1_uml3t"] + +[sub_resource type="BoxMesh" id="BoxMesh_kjbca"] +resource_local_to_scene = true +size = Vector3(0.2, 0.006, 0.03) + +[sub_resource type="BoxShape3D" id="BoxShape3D_x4yp8"] +resource_local_to_scene = true +size = Vector3(0.2, 0.006, 0.03) + +[sub_resource type="SystemFont" id="SystemFont_nbea0"] + +[sub_resource type="BoxMesh" id="BoxMesh_2736g"] +size = Vector3(0.001, 0.02, 0.001) + +[sub_resource type="Animation" id="Animation_65tpe"] +length = 0.001 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Label/Caret:visible") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [false] +} + +[sub_resource type="Animation" id="Animation_8ny1h"] +resource_name = "blink" +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Label/Caret:visible") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 0.5, 1), +"transitions": PackedFloat32Array(1, 1, 1), +"update": 0, +"values": [true, false, true] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_1sy4t"] +_data = { +"RESET": SubResource("Animation_65tpe"), +"blink": SubResource("Animation_8ny1h") +} + +[node name="Input" type="StaticBody3D" groups=["ui_focus"]] +script = ExtResource("1_uml3t") +width = 0.2 + +[node name="Box" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.003, 0) +material_override = ExtResource("1_0kd7r") +mesh = SubResource("BoxMesh_kjbca") + +[node name="Collision" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.003, 0) +shape = SubResource("BoxShape3D_x4yp8") + +[node name="Label" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.098, 0.00618291, 0) +pixel_size = 0.0004 +text = "Hello World" +font = SubResource("SystemFont_nbea0") +horizontal_alignment = 0 + +[node name="Caret" type="MeshInstance3D" parent="Label"] +visible = false +mesh = SubResource("BoxMesh_2736g") +skeleton = NodePath("../..") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +"": SubResource("AnimationLibrary_1sy4t") +} diff --git a/content/ui/components/input/text_handler.gd b/content/ui/components/input/text_handler.gd new file mode 100644 index 0000000..8a8bf35 --- /dev/null +++ b/content/ui/components/input/text_handler.gd @@ -0,0 +1,90 @@ +extends Object + +var label: Label3D + +var text: String = "" +var width: float = 0.2 +var gap_offsets = null +var overflow_index: int = -1 +var char_offset: int = 0 +var caret_position: int = 3: + set(value): + caret_position = clampi(value, 0, text.length()) + +func set_width(value: float): + width = value + +func set_text(value: String, insert: bool = false): + var old_text = text + text = value + + if label == null: + return + + gap_offsets = _calculate_text_gaps() + if insert == false: + caret_position += text.length() - old_text.length() + else: + caret_position = 0 + + overflow_index = _calculate_overflow_index() + focus_caret() + +func get_display_text(): + # In case all chars fit, return the whole text. + if overflow_index == -1: + return text.substr(char_offset) + return text.substr(char_offset, overflow_index - char_offset) + +func focus_caret(): + if overflow_index == -1: + char_offset = 0 + return + + while caret_position > overflow_index: + char_offset += caret_position - overflow_index + overflow_index = _calculate_overflow_index() + if overflow_index == -1: + break + + while caret_position < char_offset: + char_offset = caret_position + overflow_index = _calculate_overflow_index() + if overflow_index == -1: + break + +func get_caret_position(): + return gap_offsets[caret_position] - gap_offsets[char_offset] + +func update_caret_position(click_pos_x: float): + caret_position = _calculate_caret_position(click_pos_x) + focus_caret() + +func _calculate_caret_position(click_pos_x: float): + for i in range(1, gap_offsets.size()): + var left = gap_offsets[i] - gap_offsets[char_offset] + + if click_pos_x < left: + return i - 1 + + return gap_offsets.size() - 1 + +func _calculate_text_gaps(): + var font = label.get_font() + var offsets = [0.0] + var offset = 0.0 + + for i in range(text.length()): + var character = text[i] + var size = font.get_string_size(character, HORIZONTAL_ALIGNMENT_CENTER, -1, label.font_size) + + offset += size.x * label.pixel_size + offsets.append(offset) + + return offsets + +func _calculate_overflow_index(): + for i in range(char_offset, gap_offsets.size()): + if gap_offsets[i] - gap_offsets[char_offset] >= width: + return i - 1 + return gap_offsets.size() - 1 diff --git a/content/ui/device/device.tscn b/content/ui/device/device.tscn index ee30614..2c47541 100644 --- a/content/ui/device/device.tscn +++ b/content/ui/device/device.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://dbe8slnyhro2n"] [ext_resource type="Script" path="res://content/ui/device/device.gd" id="1_rbo86"] -[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/button/button.tscn" id="2_go2es"] +[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="2_go2es"] [ext_resource type="Script" path="res://content/functions/clickable.gd" id="3_6wicx"] [node name="Device" type="Node3D"] diff --git a/content/ui/keyboard/keyboard.gd b/content/ui/keyboard/keyboard.gd deleted file mode 100644 index ca9b60e..0000000 --- a/content/ui/keyboard/keyboard.gd +++ /dev/null @@ -1,51 +0,0 @@ -extends Node3D - -const button_scene = preload("res://content/ui/components/button/button.tscn") - -@onready var keys = $Keys -@onready var caps_button = $Caps -var key_list = [ - ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "~"], - ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "/"], - ["A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\\"], - ["Z", "X", "C", "V", "B", "N", "M", ",", ".", "-"] -] - -var caps = false - -func _ready(): - for row in key_list: - for key in row: - print(key) - var button = create_key(key) - keys.add_child(button) - - keys.columns = key_list[0].size() - -func _on_click(event): - if event.target == caps_button: - caps = event.active - return - - var code = event.target.get_children()[event.target.get_child_count() - 1].text - - if caps: - code = code.to_upper() - else: - code = code.to_lower() - - Events.typed.emit(code) - print(code) - -func create_key(key: String): - var button = button_scene.instantiate() - - var label = Label3D.new() - label.text = key - label.pixel_size = 0.001 - label.position = Vector3(0, 0.012, 0) - label.rotate_x(deg_to_rad(-90)) - - button.add_child(label) - - return button diff --git a/content/ui/keyboard/keyboard.tscn b/content/ui/keyboard/keyboard.tscn deleted file mode 100644 index 721e0bd..0000000 --- a/content/ui/keyboard/keyboard.tscn +++ /dev/null @@ -1,23 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://lrehk38exd5n"] - -[ext_resource type="Script" path="res://content/ui/keyboard/keyboard.gd" id="1_maojw"] -[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="1_xdpwr"] -[ext_resource type="Script" path="res://content/ui/menu/grid.gd" id="3_mx544"] - -[node name="Keyboard" type="Node3D"] -script = ExtResource("1_maojw") - -[node name="Caps" parent="." instance=ExtResource("1_xdpwr")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0668889, 0, 0.03) -toggleable = true - -[node name="Label3D" type="Label3D" parent="Caps"] -transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0.012, 0) -pixel_size = 0.001 -text = "caps" - -[node name="Keys" type="Node3D" parent="."] -script = ExtResource("3_mx544") -columns = 1 -depth_gap = 0.06 -size = Vector3(0.6, 1, 1) diff --git a/content/ui/menu/edit/edit_menu.gd b/content/ui/menu/edit/edit_menu.gd index 164589e..4804e7e 100644 --- a/content/ui/menu/edit/edit_menu.gd +++ b/content/ui/menu/edit/edit_menu.gd @@ -10,7 +10,7 @@ const Sensor = preload("res://content/entities/sensor/sensor.tscn") @onready var next_page_button = $Buttons/NextPageButton @onready var previous_page_button = $Buttons/PreviousPageButton @onready var page_number_label = $PageNumberLabel -var devices +var devices = [] var page = 0 var last_device_page = 0 var page_size = 20 @@ -19,8 +19,6 @@ var pages = 0 var selected_device = null # Called when the node enters the scene tree for the first time. func _ready(): - devices = await HomeAdapters.adapter.get_devices() - next_page_button.get_node("Clickable").on_click.connect(func(_event): print("next page") next_page() @@ -30,7 +28,25 @@ func _ready(): previous_page() ) - render() +func _enter_tree(): + if HomeApi.has_connected(): + load_devices() + else: + HomeApi.on_connect.connect(func(): + if is_inside_tree(): + load_devices() + ) + +func load_devices(): + if devices.size() == 0: + devices = await HomeApi.get_devices() + render() + + HomeApi.on_disconnect.connect(func(): + devices = [] + if is_inside_tree(): + render() + ) func update_pages(): if selected_device == null: @@ -62,6 +78,9 @@ func previous_page(): render() func render(): + if devices.size() == 0: + return + update_pages() page_number_label.set_text(str(page + 1) + " / " + str(pages)) @@ -91,7 +110,7 @@ func render_devices(): _on_device_click(device_instance.id) ) devices_node.add_child(device_instance) - device_instance.set_device_name(info["name"]) + device_instance.set_device_name.call_deferred(info["name"]) devices_node._update_container() @@ -158,6 +177,7 @@ func _on_entity_click(entity_name): func clear_menu(): for child in devices_node.get_children(): devices_node.remove_child(child) + child.queue_free() # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): diff --git a/content/ui/menu/edit/edit_menu.tscn b/content/ui/menu/edit/edit_menu.tscn index ebd6feb..aea0caf 100644 --- a/content/ui/menu/edit/edit_menu.tscn +++ b/content/ui/menu/edit/edit_menu.tscn @@ -24,6 +24,7 @@ font_size = 36 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.19, 0.01, 0.27) [node name="NextPageButton" parent="Buttons" instance=ExtResource("4_tvimg")] +focusable = true [node name="Decal" type="Decal" parent="Buttons/NextPageButton"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.02, 0) @@ -35,6 +36,7 @@ script = ExtResource("6_pf8jy") [node name="PreviousPageButton" parent="Buttons" instance=ExtResource("4_tvimg")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.08, 0, 0) +focusable = true [node name="Decal" type="Decal" parent="Buttons/PreviousPageButton"] transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0.02, 0) diff --git a/content/ui/menu/menu.gd b/content/ui/menu/menu.gd index ffabd62..0d4aca1 100644 --- a/content/ui/menu/menu.gd +++ b/content/ui/menu/menu.gd @@ -16,18 +16,9 @@ extends Node3D @onready var nav = $AnimationContainer/Navigation @onready var animation_player = $AnimationPlayer -enum Menu { - VIEW, - EDIT, - ROOM, - AUTOMATE, - SETTINGS -} +var selected_nav = null -var selected_menu := Menu.EDIT var show_menu := true: - get: - return show_menu set(value): show_menu = value if value: @@ -39,67 +30,47 @@ var show_menu := true: func _ready(): _controller.button_pressed.connect(func(button): - print(button) if button == "by_button": show_menu = !show_menu ) - select_menu(selected_menu) + select_menu(nav_edit) func _on_click(event): - if event.target == nav_view: - select_menu(Menu.VIEW) - elif event.target == nav_edit: - select_menu(Menu.EDIT) - elif event.target == nav_room: - select_menu(Menu.ROOM) - elif event.target == nav_automate: - select_menu(Menu.AUTOMATE) - elif event.target == nav_settings: - select_menu(Menu.SETTINGS) + select_menu(event.target) + +func select_menu(nav): + if _is_valid_nav(nav) == false || selected_nav == nav: + return -func select_menu(menu: Menu): - selected_menu = menu for child in content.get_children(): content.remove_child(child) - var menu_node = enum_to_menu(menu) - var nav_node = enum_to_nav(menu) + if selected_nav != null: + selected_nav.active = false - if nav_node != null: - nav_node.disabled = true + selected_nav = nav - if menu_node != null: - menu_node.visible = true - content.add_child(menu_node) + if selected_nav != null: + selected_nav.active = true + var menu = _nav_to_menu(selected_nav) + if menu != null: + content.add_child(menu) + menu.visible = true - for child in nav.get_children(): - if child.active && child != nav_node: - child.active = false - child.disabled = false - -func enum_to_nav(menu: Menu): - match menu: - Menu.VIEW: - return nav_view - Menu.EDIT: - return nav_edit - Menu.ROOM: - return nav_room - Menu.AUTOMATE: - return nav_automate - Menu.SETTINGS: - return nav_settings +func _is_valid_nav(nav): + return nav == nav_view || nav == nav_edit || nav == nav_room || nav == nav_automate || nav == nav_settings -func enum_to_menu(menu: Menu): - match menu: - Menu.VIEW: +func _nav_to_menu(nav): + match nav: + nav_view: return null - Menu.EDIT: + nav_edit: return menu_edit - Menu.ROOM: + nav_room: return menu_room - Menu.AUTOMATE: + nav_automate: return null - Menu.SETTINGS: + nav_settings: return menu_settings + return null diff --git a/content/ui/menu/menu.tscn b/content/ui/menu/menu.tscn index 6f80042..cb0a976 100644 --- a/content/ui/menu/menu.tscn +++ b/content/ui/menu/menu.tscn @@ -168,6 +168,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0, 0) [node name="View" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.03) toggleable = true +external_state = true [node name="Sprite3D" type="Sprite3D" parent="AnimationContainer/Navigation/View"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.012, 0) @@ -178,7 +179,7 @@ texture = ExtResource("5_8o1rb") [node name="Edit" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.09) toggleable = true -initial_active = true +external_state = true [node name="Sprite3D" type="Sprite3D" parent="AnimationContainer/Navigation/Edit"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.012, 0) @@ -189,6 +190,7 @@ texture = ExtResource("6_344ot") [node name="Room" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.15) toggleable = true +external_state = true [node name="Sprite3D" type="Sprite3D" parent="AnimationContainer/Navigation/Room"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.012, 0) @@ -199,6 +201,7 @@ texture = ExtResource("7_wvovx") [node name="Automate" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.21) toggleable = true +external_state = true [node name="Sprite3D" type="Sprite3D" parent="AnimationContainer/Navigation/Automate"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.012, 0) @@ -209,6 +212,7 @@ texture = ExtResource("8_3d082") [node name="Settings" parent="AnimationContainer/Navigation" instance=ExtResource("5_w4i01")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, 0, 0.27) toggleable = true +external_state = true [node name="Sprite3D" type="Sprite3D" parent="AnimationContainer/Navigation/Settings"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.012, 0) @@ -220,12 +224,12 @@ texture = ExtResource("9_mel13") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06, 0, 0) [node name="EditMenu" parent="AnimationContainer/Content" instance=ExtResource("4_r2raj")] +visible = false [node name="RoomMenu" parent="AnimationContainer/Content" instance=ExtResource("10_u4i1x")] visible = false [node name="SettingsMenu" parent="AnimationContainer/Content" instance=ExtResource("11_7wm6b")] -visible = false [node name="ImmersiveHomePanels" type="MeshInstance3D" parent="."] transform = Transform3D(-4.37114e-10, 0, 0.01, 0, 0.01, 0, -0.01, 0, -4.37114e-10, 0.32, 0, -0.0500001) diff --git a/content/ui/menu/room/room_menu.gd b/content/ui/menu/room/room_menu.gd index 144b8f2..df3f0e3 100644 --- a/content/ui/menu/room/room_menu.gd +++ b/content/ui/menu/room/room_menu.gd @@ -25,8 +25,8 @@ func _ready(): add_corner(event.ray.get_collision_point()) ) - toggle_edit_button.get_node("Clickable").on_click.connect(func(event): - edit_enabled = event.active + toggle_edit_button.get_node("Clickable").on_press_up.connect(func(event): + edit_enabled = event.target.active if edit_enabled == false: wall_corners.visible = false @@ -131,7 +131,7 @@ func add_corner(position: Vector3): if moving == null: return - var direction = (event.ray.to_global(event.ray.target_position) - event.ray.global_position).normalized() + var direction = -event.ray.global_transform.basis.z var new_position = ground_plane.intersects_ray(event.ray.global_position, direction) if new_position == null: diff --git a/content/ui/menu/settings/ball.tscn b/content/ui/menu/settings/ball.tscn index 5158fa8..03a7b90 100644 --- a/content/ui/menu/settings/ball.tscn +++ b/content/ui/menu/settings/ball.tscn @@ -8,7 +8,7 @@ radius = 0.08 height = 0.16 [node name="Ball" type="RigidBody3D"] -angular_damp = 39.224 +angular_damp = 4.0 [node name="CollisionShape3D" type="CollisionShape3D" parent="."] shape = SubResource("SphereShape3D_orlq6") diff --git a/content/ui/menu/settings/settings_menu.gd b/content/ui/menu/settings/settings_menu.gd index ff0fad1..1138dfd 100644 --- a/content/ui/menu/settings/settings_menu.gd +++ b/content/ui/menu/settings/settings_menu.gd @@ -5,6 +5,10 @@ const ball_scene = preload("res://content/ui/menu/settings/ball.tscn") @onready var clickable = $Content/Button/Clickable @onready var connection_status = $Content/ConnectionStatus +@onready var input_url = $Content/InputURL +@onready var input_token = $Content/InputToken +@onready var button_connect = $Content/Connect + func _ready(): clickable.on_click.connect(func(event): var ball = ball_scene.instantiate() @@ -13,11 +17,31 @@ func _ready(): get_tree().root.add_child(ball) ) - HomeAdapters.adapter.adapter.on_connect.connect(func(): + var config = ConfigData.load_config() + + if config.has("url"): + input_url.text = config["url"] + if config.has("token"): + input_token.text = config["token"] + + button_connect.on_button_down.connect(func(): + var url = input_url.text + "/api/websocket" + var token = input_token.text + + HomeApi.start_adapter("hass_ws", url, token) + + ConfigData.save_config({ + "api_type": "hass_ws", + "url": input_url.text, + "token": input_token.text + }) + ) + + HomeApi.on_connect.connect(func(): connection_status.text = "Connected" ) - HomeAdapters.adapter.adapter.on_disconnect.connect(func(): + HomeApi.on_disconnect.connect(func(): connection_status.text = "Disconnected" ) diff --git a/content/ui/menu/settings/settings_menu.tscn b/content/ui/menu/settings/settings_menu.tscn index 07e790c..b4a7f00 100644 --- a/content/ui/menu/settings/settings_menu.tscn +++ b/content/ui/menu/settings/settings_menu.tscn @@ -1,8 +1,10 @@ -[gd_scene load_steps=5 format=3 uid="uid://c6r4higceibif"] +[gd_scene load_steps=7 format=3 uid="uid://c6r4higceibif"] [ext_resource type="Script" path="res://content/ui/menu/settings/settings_menu.gd" id="1_0lte6"] [ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="1_faxng"] [ext_resource type="Script" path="res://content/functions/clickable.gd" id="3_qmg6q"] +[ext_resource type="PackedScene" uid="uid://blrhy2uccrdn4" path="res://content/ui/components/input/input.tscn" id="4_q3x6k"] +[ext_resource type="Texture2D" uid="uid://co5fgm68t4j6o" path="res://assets/icons/wifi_white_24dp.svg" id="5_muw54"] [sub_resource type="BoxMesh" id="BoxMesh_e51x8"] size = Vector3(0.3, 0.01, 0.3) @@ -18,17 +20,56 @@ mesh = SubResource("BoxMesh_e51x8") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0102286, 0) [node name="Label3D" type="Label3D" parent="Content"] -transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.184377, 0, 0.0435752) +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.184377, 0, 0.253575) pixel_size = 0.001 text = "Spawn Ball" [node name="Button" parent="Content" instance=ExtResource("1_faxng")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0458097, 0, 0.0435752) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0458097, 0, 0.253575) [node name="Clickable" type="Node" parent="Content/Button"] script = ExtResource("3_qmg6q") [node name="ConnectionStatus" type="Label3D" parent="Content"] -transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.26, 0, 0.29) +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.250698, 0, 0.161303) pixel_size = 0.0003 text = "Disconnected" + +[node name="LabelURL" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.01, 0, 0.03) +pixel_size = 0.0005 +text = "url: +" +font_size = 36 +horizontal_alignment = 0 + +[node name="InputURL" parent="Content" instance=ExtResource("4_q3x6k")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.18, 0, 0.03) +text = "ws://192.168.33.33:8123" + +[node name="LabelToken" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.01, 0, 0.07) +pixel_size = 0.0005 +text = "token:" +font_size = 36 +horizontal_alignment = 0 + +[node name="InputToken" parent="Content" instance=ExtResource("4_q3x6k")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.18, 0, 0.07) +text = "..." + +[node name="LabelConnect" type="Label3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.14, 0, 0.12) +pixel_size = 0.0005 +text = "Connect" +font_size = 36 +horizontal_alignment = 0 + +[node name="Connect" parent="Content" instance=ExtResource("1_faxng")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0, 0.12) + +[node name="Sprite3D" type="Sprite3D" parent="Content"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0.012, 0.12) +pixel_size = 0.0002 +axis = 1 +texture = ExtResource("5_muw54") diff --git a/export_presets.cfg b/export_presets.cfg index db9013a..b664e50 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -142,7 +142,7 @@ permissions/process_outgoing_calls=false permissions/read_calendar=false permissions/read_call_log=false permissions/read_contacts=false -permissions/read_external_storage=false +permissions/read_external_storage=true permissions/read_frame_buffer=false permissions/read_history_bookmarks=false permissions/read_input_state=false @@ -193,7 +193,7 @@ permissions/write_apn_settings=false permissions/write_calendar=false permissions/write_call_log=false permissions/write_contacts=false -permissions/write_external_storage=false +permissions/write_external_storage=true permissions/write_gservices=false permissions/write_history_bookmarks=false permissions/write_profile=false diff --git a/export_presets_prod.cfg b/export_presets_prod.cfg index bb1d8c0..c12f50f 100644 --- a/export_presets_prod.cfg +++ b/export_presets_prod.cfg @@ -142,7 +142,7 @@ permissions/process_outgoing_calls=false permissions/read_calendar=false permissions/read_call_log=false permissions/read_contacts=false -permissions/read_external_storage=false +permissions/read_external_storage=true permissions/read_frame_buffer=false permissions/read_history_bookmarks=false permissions/read_input_state=false @@ -193,7 +193,7 @@ permissions/write_apn_settings=false permissions/write_calendar=false permissions/write_call_log=false permissions/write_contacts=false -permissions/write_external_storage=false +permissions/write_external_storage=true permissions/write_gservices=false permissions/write_history_bookmarks=false permissions/write_profile=false diff --git a/lib/events/event.gd b/lib/events/event.gd new file mode 100644 index 0000000..33779f8 --- /dev/null +++ b/lib/events/event.gd @@ -0,0 +1,9 @@ +extends Resource +class_name Event + +func merge(event: Event): + assert(self.is_class(event.get_class()), "Can only merge events of the same type.") + + for prop in event.get_property_list(): + if prop.name in self: + self.set(prop.name, event.get(prop.name)) diff --git a/lib/events/event_bubble.gd b/lib/events/event_bubble.gd new file mode 100644 index 0000000..a72ca89 --- /dev/null +++ b/lib/events/event_bubble.gd @@ -0,0 +1,5 @@ +extends Event +class_name EventBubble + +var bubbling := true +var target: Node \ No newline at end of file diff --git a/lib/events/event_focus.gd b/lib/events/event_focus.gd new file mode 100644 index 0000000..7c88f49 --- /dev/null +++ b/lib/events/event_focus.gd @@ -0,0 +1,6 @@ +extends Event +class_name EventFocus + +var ray: RayCast3D +var target: Node +var previous_target: Node \ No newline at end of file diff --git a/lib/events/event_key.gd b/lib/events/event_key.gd new file mode 100644 index 0000000..9f6b535 --- /dev/null +++ b/lib/events/event_key.gd @@ -0,0 +1,22 @@ +extends EventWithModifiers +class_name EventKey + +var key: Key +var echo: bool + +static func key_to_string(key: Key, caps: bool = false, apply_to: String = "") -> String: + match key: + KEY_INSERT: apply_to += DisplayServer.clipboard_get() + KEY_BACKSPACE: apply_to = apply_to.substr(0, apply_to.length() - 1) + KEY_SPACE: apply_to += " " + KEY_ASCIITILDE: apply_to += "~" + KEY_SLASH: apply_to += "/" + KEY_BACKSLASH: apply_to += "\\" + KEY_COLON: apply_to += ";" + KEY_COMMA: apply_to += "," + KEY_PERIOD: apply_to += "." + KEY_MINUS: apply_to += "-" + KEY_CAPSLOCK: return apply_to + _: apply_to += OS.get_keycode_string(key).to_upper() if caps else OS.get_keycode_string(key).to_lower() + + return apply_to \ No newline at end of file diff --git a/lib/events/event_ray.gd b/lib/events/event_ray.gd new file mode 100644 index 0000000..aa2765d --- /dev/null +++ b/lib/events/event_ray.gd @@ -0,0 +1,6 @@ +extends EventBubble +class_name EventRay + +var controller: XRController3D +var is_right_controller: bool +var ray: RayCast3D \ No newline at end of file diff --git a/lib/events/event_with_modifiers.gd b/lib/events/event_with_modifiers.gd new file mode 100644 index 0000000..14a7389 --- /dev/null +++ b/lib/events/event_with_modifiers.gd @@ -0,0 +1,7 @@ +extends Event +class_name EventWithModifiers + +var alt_pressed := false +var shift_pressed := false +var control_pressed := false +var meta_pressed := false \ No newline at end of file diff --git a/lib/globals/config_data.gd b/lib/globals/config_data.gd new file mode 100644 index 0000000..7d75ccc --- /dev/null +++ b/lib/globals/config_data.gd @@ -0,0 +1,23 @@ +extends Node + +var file_url: String = "user://config.json" + +func save_config(data: Dictionary): + var file := FileAccess.open(file_url, FileAccess.WRITE) + + if file == null: + return + + var json_data := JSON.stringify(data) + file.store_string(json_data) + +func load_config(): + var file := FileAccess.open(file_url, FileAccess.READ) + + if file == null: + return {} + + var json_data := file.get_as_text() + var data = JSON.parse_string(json_data) + + return data \ No newline at end of file diff --git a/lib/globals/event_system.gd b/lib/globals/event_system.gd new file mode 100644 index 0000000..fed2f1b --- /dev/null +++ b/lib/globals/event_system.gd @@ -0,0 +1,89 @@ +extends Node + +const FN_PREFIX = "_on_" +const SIGNAL_PREFIX = "on_" + +# Interaction Events +signal on_click(event: EventRay) +signal on_press_down(event: EventRay) +signal on_press_move(event: EventRay) +signal on_press_up(event: EventRay) +signal on_grab_down(event: EventRay) +signal on_grab_move(event: EventRay) +signal on_grab_up(event: EventRay) +signal on_ray_enter(event: EventRay) +signal on_ray_leave(event: EventRay) + +signal on_key_down(event: EventKey) +signal on_key_up(event: EventKey) + +signal on_focus_in(event: EventFocus) +signal on_focus_out(event: EventFocus) + +var _active_node: Node = null + +func emit(type: String, event: Event): + if event is EventBubble: + _bubble_call(type, event.target, event) + if type == "press_down": + _handle_focus(event) + else: + _root_call(type, event) + +func is_focused(node: Node): + return _active_node == node + +func _handle_focus(event: EventRay): + if event.target != null && event.target.is_in_group("ui_focus_skip"): + return + + var event_focus = EventFocus.new() + event_focus.previous_target = _active_node + event_focus.target = event.target + event_focus.ray = event.ray + + if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_out"): + _active_node.call(FN_PREFIX + "focus_out", event_focus) + on_focus_out.emit(event_focus) + + if event.target == null || event.target.is_in_group("ui_focus") == false: + _active_node = null + return + + _active_node = event.target + + if _active_node != null && _active_node.has_method(FN_PREFIX + "focus_in"): + _active_node.call(FN_PREFIX + "focus_in", event_focus) + on_focus_in.emit(event_focus) + +func _bubble_call(type: String, target: Variant, event: EventBubble): + if target == null: + return false + + if target.has_method(FN_PREFIX + type): + var updated_event = target.call(FN_PREFIX + type, event) + + if updated_event is EventBubble: + updated_event.merge(event) + event = updated_event + + if event.bubbling == false: + return false + + for child in target.get_children(): + if child is Function && child.has_method(FN_PREFIX + type): + child.call(FN_PREFIX + type, event) + + var parent = target.get_parent() + + if parent != null && parent is Node: + _bubble_call(type, parent, event) + else: + # in case the top has been reached + _root_call(type, event) + + return true + + +func _root_call(type: String, event: Event): + get(SIGNAL_PREFIX + type).emit(event) diff --git a/lib/globals/events.gd b/lib/globals/events.gd deleted file mode 100644 index 6fa7e02..0000000 --- a/lib/globals/events.gd +++ /dev/null @@ -1,15 +0,0 @@ -# Global event bus -extends Node - -# Interaction Events -signal on_click(event: Dictionary) -signal on_press_down(event: Dictionary) -signal on_press_move(event: Dictionary) -signal on_press_up(event: Dictionary) -signal on_grab_down(event: Dictionary) -signal on_grab_move(event: Dictionary) -signal on_grab_up(event: Dictionary) -signal on_ray_enter(event: Dictionary) -signal on_ray_leave(event: Dictionary) - -signal typed(key: String) \ No newline at end of file diff --git a/lib/globals/home_adapters.gd b/lib/globals/home_adapters.gd deleted file mode 100644 index 275327c..0000000 --- a/lib/globals/home_adapters.gd +++ /dev/null @@ -1,11 +0,0 @@ -extends Node - -const Adapter = preload("res://lib/home_adapters/adapter.gd") - -var adapter = Adapter.new(Adapter.ADAPTER_TYPES.HASS_WS) -# var adapter_http = Adapter.new(Adapter.ADAPTER_TYPES.HASS) - -func _ready(): - add_child(adapter) - # add_child(adapter_http) - diff --git a/lib/globals/home_api.gd b/lib/globals/home_api.gd new file mode 100644 index 0000000..245c052 --- /dev/null +++ b/lib/globals/home_api.gd @@ -0,0 +1,94 @@ +extends Node + +const Hass = preload("res://lib/home_apis/hass/hass.gd") +const HassWebSocket = preload("res://lib/home_apis/hass_ws/hass.gd") + + +const apis = { + "hass": Hass, + "hass_ws": HassWebSocket +} + +const methods = [ + "get_devices", + "get_device", + "get_state", + "set_state", + "watch_state" +] + +signal on_connect() +signal on_disconnect() +var api: Node + +func _ready(): + print("HomeApi ready") + + var config = ConfigData.load_config() + + if config.has("api_type") && config.has("url") && config.has("token"): + var type = config["api_type"] + var url = config["url"] + "/api/websocket" + var token = config["token"] + + start_adapter(type, url, token) + + +func start_adapter(type: String, url: String, token: String): + print("Starting adapter: %s" % type) + if api != null: + api.on_connect.disconnect(_on_connect) + api.on_disconnect.disconnect(_on_disconnect) + remove_child(api) + api.queue_free() + api = null + + api = apis[type].new(url, token) + add_child(api) + + api.on_connect.connect(func(): + on_connect.emit() + ) + + api.on_disconnect.connect(func(): + on_disconnect.emit() + ) + + for method in methods: + assert(api.has_method(method), "%s Api does not implement method: %s" % [type, method]) + +func _on_connect(): + on_connect.emit() + +func _on_disconnect(): + on_disconnect.emit() + +func has_connected(): + if api == null: + return false + return api.has_connected() + +## Get a list of all devices +func get_devices(): + assert(has_connected(), "Not connected") + return await api.get_devices() + +## Get a single device by id +func get_device(id: String): + assert(has_connected(), "Not connected") + return await api.get_device(id) + +## Returns the current state of an entity +func get_state(entity: String): + assert(has_connected(), "Not connected") + return await api.get_state(entity) + +## Updates the state of the entity and returns the resulting state +func set_state(entity: String, state: String, attributes: Dictionary = {}): + assert(has_connected(), "Not connected") + return await api.set_state(entity, state, attributes) + +## Watches the state and each time it changes, calls the callback with the changed state, returns a function to stop watching the state +func watch_state(entity: String, callback: Callable): + assert(has_connected(), "Not connected") + return api.watch_state(entity, callback) diff --git a/lib/home_adapters/adapter.gd b/lib/home_adapters/adapter.gd deleted file mode 100644 index e330a4d..0000000 --- a/lib/home_adapters/adapter.gd +++ /dev/null @@ -1,60 +0,0 @@ -extends Node - -const Hass = preload("res://lib/home_adapters/hass/hass.gd") -const HassWebSocket = preload("res://lib/home_adapters/hass_ws/hass.gd") - -enum ADAPTER_TYPES { - HASS, - HASS_WS -} - -const adapters = { - ADAPTER_TYPES.HASS: Hass, - ADAPTER_TYPES.HASS_WS: HassWebSocket -} - -const methods = [ - "get_devices", - "get_device", - "get_state", - "set_state", - "watch_state" -] - -var adapter: Node - -func _init(type: ADAPTER_TYPES): - - var clipboard := DisplayServer.clipboard_get() - - if clipboard != null && clipboard.find(" ") != -1: - var clip_url = clipboard.split(" ")[0] - var clip_token = clipboard.split(" ")[1] - adapter = adapters[type].new(clip_url, clip_token) - else: - adapter = adapters[type].new() - - add_child(adapter) - - for method in methods: - assert(adapter.has_method(method), "Adapter does not implement method: " + method) - -## Get a list of all devices -func get_devices(): - return await adapter.get_devices() - -## Get a single device by id -func get_device(id: String): - return await adapter.get_device(id) - -## Returns the current state of an entity -func get_state(entity: String): - return await adapter.get_state(entity) - -## Updates the state of the entity and returns the resulting state -func set_state(entity: String, state: String, attributes: Dictionary = {}): - return await adapter.set_state(entity, state, attributes) - -## Watches the state and each time it changes, calls the callback with the changed state, returns a function to stop watching the state -func watch_state(entity: String, callback: Callable): - return adapter.watch_state(entity, callback) diff --git a/lib/home_adapters/hass/hass.gd b/lib/home_apis/hass/hass.gd similarity index 98% rename from lib/home_adapters/hass/hass.gd rename to lib/home_apis/hass/hass.gd index 3924522..88e101c 100644 --- a/lib/home_adapters/hass/hass.gd +++ b/lib/home_apis/hass/hass.gd @@ -4,7 +4,7 @@ var url: String = "http://192.168.33.33:8123" var token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzZjQ0ZGM2N2Y3YzY0MDc1OGZlMWI2ZjJlNmIxZjRkNSIsImlhdCI6MTY5ODAxMDcyOCwiZXhwIjoyMDEzMzcwNzI4fQ.K6ydLUC-4Q7BNIRCU1nWlI2s6sg9UCiOu-Lpedw2zJc" var headers: PackedStringArray = PackedStringArray([]) -var devices_template = FileAccess.get_file_as_string("res://lib/home_adapters/hass/templates/devices.j2") +var devices_template = FileAccess.get_file_as_string("res://lib/home_apis/hass/templates/devices.j2") func _init(url := self.url, token := self.token): self.url = url @@ -32,8 +32,6 @@ func get_state(entity: String): return json - - func set_state(entity: String, state: String, attributes: Dictionary = {}): var type = entity.split('.')[0] var response diff --git a/lib/home_adapters/hass/templates/devices.j2 b/lib/home_apis/hass/templates/devices.j2 similarity index 100% rename from lib/home_adapters/hass/templates/devices.j2 rename to lib/home_apis/hass/templates/devices.j2 diff --git a/lib/home_adapters/hass_ws/callback_map.gd b/lib/home_apis/hass_ws/callback_map.gd similarity index 100% rename from lib/home_adapters/hass_ws/callback_map.gd rename to lib/home_apis/hass_ws/callback_map.gd diff --git a/lib/home_adapters/hass_ws/hass.gd b/lib/home_apis/hass_ws/hass.gd similarity index 95% rename from lib/home_adapters/hass_ws/hass.gd rename to lib/home_apis/hass_ws/hass.gd index 7479829..b163183 100644 --- a/lib/home_adapters/hass_ws/hass.gd +++ b/lib/home_apis/hass_ws/hass.gd @@ -1,6 +1,10 @@ extends Node -var devices_template := FileAccess.get_file_as_string("res://lib/home_adapters/hass_ws/templates/devices.j2") +signal on_connect() +signal on_disconnect() +var connected := false + +var devices_template := FileAccess.get_file_as_string("res://lib/home_apis/hass_ws/templates/devices.j2") var socket := WebSocketPeer.new() # in seconds var request_timeout := 10.0 @@ -13,16 +17,14 @@ var token := "" var LOG_MESSAGES := false var authenticated := false -var loading := true + var id := 1 var entities: Dictionary = {} +var retries := 5 var entitiy_callbacks := CallbackMap.new() var packet_callbacks := CallbackMap.new() -signal on_connect() -signal on_disconnect() - func _init(url := self.url, token := self.token): self.url = url self.token = token @@ -31,6 +33,14 @@ func _init(url := self.url, token := self.token): connect_ws() func connect_ws(): + if url == "" || token == "": + return + + retries -= 1 + if retries < 0: + print("Failed to connect to %s" % self.url) + return + print("Connecting to %s" % self.url) socket.connect_to_url(self.url) set_process(true) @@ -105,7 +115,7 @@ func start_subscriptions(): "attributes": packet.event.a[entity]["a"] } entitiy_callbacks.call_key(entity, [entities[entity]]) - loading = false + connected = true on_connect.emit() if packet.event.has("c"): @@ -192,10 +202,10 @@ func decode_packet(packet: PackedByteArray): func encode_packet(packet: Dictionary): return JSON.stringify(packet) -func get_devices(): - if loading: - await on_connect +func has_connected(): + return connected +func get_devices(): var result = await send_request_packet({ "type": "render_template", "template": devices_template, @@ -209,18 +219,12 @@ func get_device(id: String): pass func get_state(entity: String): - if loading: - await on_connect - if entities.has(entity): return entities[entity] return null func watch_state(entity: String, callback: Callable): - if loading: - await on_connect - entitiy_callbacks.add(entity, callback) return func(): @@ -228,8 +232,6 @@ func watch_state(entity: String, callback: Callable): func set_state(entity: String, state: String, attributes: Dictionary = {}): - assert(!loading, "Still loading") - var domain = entity.split(".")[0] var service: String diff --git a/lib/home_adapters/hass_ws/templates/devices.j2 b/lib/home_apis/hass_ws/templates/devices.j2 similarity index 100% rename from lib/home_adapters/hass_ws/templates/devices.j2 rename to lib/home_apis/hass_ws/templates/devices.j2 diff --git a/privacy_policy.html b/privacy_policy.html deleted file mode 100644 index 630faef..0000000 --- a/privacy_policy.html +++ /dev/null @@ -1,145 +0,0 @@ -

Privacy Policy

-

Last updated: November 21, 2023

-

- This Privacy Policy describes Our policies and procedures on the collection, - use and disclosure of Your information when You use the Service and tells - You about Your privacy rights and how the law protects You. -

-

- We use Your Personal data to provide and improve the Service. By using the - Service, You agree to the collection and use of information in accordance - with this Privacy Policy. This Privacy Policy has been created with the help - of the - Privacy Policy Generator. -

-

Interpretation and Definitions

-

Interpretation

-

- The words of which the initial letter is capitalized have meanings defined - under the following conditions. The following definitions shall have the - same meaning regardless of whether they appear in singular or in plural. -

-

Definitions

-

For the purposes of this Privacy Policy:

- -

Collecting and Using Your Personal Data

-

Types of Data Collected

-

- We do not collect any data about you under any circumstances. -

-

Links to Other Websites

-

- Our Service may contain links to other websites that are not operated by Us. - If You click on a third party link, You will be directed to that third - party's site. We strongly advise You to review the Privacy Policy of every - site You visit. -

-

- We have no control over and assume no responsibility for the content, - privacy policies or practices of any third party sites or services. -

-

Changes to this Privacy Policy

-

- We may update Our Privacy Policy from time to time. We will notify You of - any changes by posting the new Privacy Policy on this page. -

-

- We will let You know via email and/or a prominent notice on Our Service, - prior to the change becoming effective and update the "Last - updated" date at the top of this Privacy Policy. -

-

- You are advised to review this Privacy Policy periodically for any changes. - Changes to this Privacy Policy are effective when they are posted on this - page. -

-

Contact Us

-

If you have any questions about this Privacy Policy, You can contact us:

- diff --git a/project.godot b/project.godot index 73ec25b..69cb30a 100644 --- a/project.godot +++ b/project.godot @@ -18,10 +18,11 @@ config/icon="res://assets/logo.png" [autoload] XRToolsUserSettings="*res://addons/godot-xr-tools/user_settings/user_settings.gd" +ConfigData="*res://lib/globals/config_data.gd" Request="*res://lib/globals/request.gd" -HomeAdapters="*res://lib/globals/home_adapters.gd" +HomeApi="*res://lib/globals/home_api.gd" AudioPlayer="*res://lib/globals/audio_player.gd" -Events="*res://lib/globals/events.gd" +EventSystem="*res://lib/globals/event_system.gd" [editor_plugins]