Merge pull request #43 from Nitwel/testing

Add keyboard, UI Inputs and a lot of bug fixes
This commit is contained in:
Nitwel 2023-11-24 02:00:51 +01:00 committed by GitHub
commit affbb7e91d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 1489 additions and 638 deletions

3
.gitattributes vendored
View File

@ -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

View File

@ -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

24
.github/workflows/release.yml vendored Normal file
View File

@ -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 }}

View File

@ -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

View File

@ -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.

View File

@ -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)
[<img src="https://upload.wikimedia.org/wikipedia/commons/8/8f/QIWI_logo.svg" alt="qiwi" width=90px/>](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
```

View File

@ -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"

View File

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0fb41898f762f555f379bc351a0d2d82bc9cbf3b701a1f9fe8035646ddad44b5
size 2928328

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1dfdb52ffe2e428ea0a0b9ff951b986546291f383f37c957d7de4c367f79892f
size 2993032

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5c580b7b4d44342c56d1990ab09358b63fa60cf99846a355c86ceb2cb5023b14
size 3057172

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6c2b95269938e60e709539c8166cd9e5f450bd3fbd2b6bdfdf652fca3f594db
size 2977744

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c40f93bd53bc4b132d075f18814b1198e71df6b9f2601c11f0cfbc2494698cf
size 1804756

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0181cc7c1a08ecc39de983b1b1e35f66572479da533f744190fa803cbde44612
size 2001576

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24497f5a2ad8a7d99e2acb62d6c526017d7c365f8fd7fd6ae3042d96bd6e980a
size 1996072

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6eab3ff00fc2d7347a6b166e67a3800d6117fa84bcfa3317429d6e0bff896654
size 2028472

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b39c1ca40f0a22e66e742daba8e050884caa7a3ff959824b0c99a7e061fd869
size 3806144

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4bfbec2037276c6858cbfc4019bb0381eb9bd83cdcdfcfa3cc802bfc0d5f427b
size 3072224

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7efcb3de6de7d5a878c4a221931a94a74f3741a6aeb7d7e9a4f19fb07bf1115c
size 2193840

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:818647a1234354725573918ce71972193812dd19d9ffb4bbe35f4a17ff48341f
size 5739144

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5bbf87c77f9c9fe6643948257e91f271825a2874172c97cdbd915a6203a6eff3
size 3339680

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b0f280b00b057266534e477e4b33e476f108b41d1927d4db39e8d1e3a781adc5
size 4689496

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac3ac3647171c53e2f47564df2e25eff19fbfab8bf763d0078fe1c82476d8761
size 1291264

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3d3113daaa53fe431ca8ce55f127c0e41ef4243ab5aadd359dc6d2c63b31f057
size 680448

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:af5ae3a80d09e6212d21e4cf608aa38f711d230914918d0f2f427bdcc5488e24
size 862208

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8bc1c66cc868d68efe828c304c6ed348136cdfd8b814fe0f243954f328648997
size 3586528
oid sha256:84daccd9540dfcf964fc4ec224ed88238f3a9fac55a30d1f48161d1ede0a7907
size 2804209

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:053d1e42a72e988fad4b2ef981eb72ec850f1f7da6963b257c8935e8392cb37b
size 324

View File

@ -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

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f10630e063d8d85238a4487595ec7869c79798ae7547ea0f636d6c78e0dc178
size 85601
oid sha256:fc77fb979fd1cec813e7bf6002ad05da443097ca5e04712f36f65a1dbc17cef2
size 85732

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:55d5f30db336a8f8f7633743c23e41077d5c1a1b900c475a6d3811746eba3d1b
size 141

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:57f8bfca31acba3de3a15a2b9a88b8afd12f8db9c356f14d906840dcee48d6f6
size 1021

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:451fcd3ec43720477df80b56031118ea0b947cfa32198bf47ef095b53beadf95
size 1656729
oid sha256:8da6b8e647a21f76a72096dad5eabd775f668ab0e45bdf8321633d99f894b175
size 1656450

View File

@ -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)

View File

@ -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)
)

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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")]

View File

@ -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))

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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:
return
active = !active
AudioPlayer.play_effect("click")
return {
"active": active
}
func _on_press_down(_event):
if disabled:
return false
if toggleable:
event.bubbling = false
return
AudioPlayer.play_effect("click")
animation_player.play("down")
func _on_press_up(_event):
if disabled:
return false
if toggleable:
if external_state || toggleable:
return
animation_player.play_backwards("down")
active = true
on_button_down.emit()
func _on_press_up(event):
if disabled:
event.bubbling = false
return
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()

View File

@ -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="."]

View File

@ -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)
)

View File

@ -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")
}

View File

@ -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

View File

@ -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"]

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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 _is_valid_nav(nav):
return nav == nav_view || nav == nav_edit || nav == nav_room || nav == nav_automate || nav == nav_settings
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 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

View File

@ -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)

View File

@ -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:

View File

@ -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")

View File

@ -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"
)

View File

@ -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")

View File

@ -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

View File

@ -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

9
lib/events/event.gd Normal file
View File

@ -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))

View File

@ -0,0 +1,5 @@
extends Event
class_name EventBubble
var bubbling := true
var target: Node

View File

@ -0,0 +1,6 @@
extends Event
class_name EventFocus
var ray: RayCast3D
var target: Node
var previous_target: Node

22
lib/events/event_key.gd Normal file
View File

@ -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

6
lib/events/event_ray.gd Normal file
View File

@ -0,0 +1,6 @@
extends EventBubble
class_name EventRay
var controller: XRController3D
var is_right_controller: bool
var ray: RayCast3D

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

94
lib/globals/home_api.gd Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,145 +0,0 @@
<h1>Privacy Policy</h1>
<p>Last updated: November 21, 2023</p>
<p>
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.
</p>
<p>
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
<a
href="https://www.privacypolicies.com/privacy-policy-generator/"
target="_blank"
>Privacy Policy Generator</a
>.
</p>
<h2>Interpretation and Definitions</h2>
<h3>Interpretation</h3>
<p>
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.
</p>
<h3>Definitions</h3>
<p>For the purposes of this Privacy Policy:</p>
<ul>
<li>
<p>
<strong>Account</strong> means a unique account created for You to
access our Service or parts of our Service.
</p>
</li>
<li>
<p>
<strong>Affiliate</strong> means an entity that controls, is
controlled by or is under common control with a party, where
&quot;control&quot; means ownership of 50% or more of the shares,
equity interest or other securities entitled to vote for election of
directors or other managing authority.
</p>
</li>
<li>
<p>
<strong>Application</strong> refers to Immersive Home, the software
program provided by the Company.
</p>
</li>
<li>
<p>
<strong>Company</strong> (referred to as either &quot;the
Company&quot;, &quot;We&quot;, &quot;Us&quot; or &quot;Our&quot; in
this Agreement) refers to Immersive Home.
</p>
</li>
<li>
<p><strong>Country</strong> refers to: Sachsen, Germany</p>
</li>
<li>
<p>
<strong>Device</strong> means any device that can access the Service
such as a computer, a cellphone or a digital tablet.
</p>
</li>
<li>
<p>
<strong>Personal Data</strong> is any information that relates to an
identified or identifiable individual.
</p>
</li>
<li>
<p><strong>Service</strong> refers to the Application.</p>
</li>
<li>
<p>
<strong>Service Provider</strong> means any natural or legal person
who processes the data on behalf of the Company. It refers to
third-party companies or individuals employed by the Company to
facilitate the Service, to provide the Service on behalf of the
Company, to perform services related to the Service or to assist the
Company in analyzing how the Service is used.
</p>
</li>
<li>
<p>
<strong>Usage Data</strong> refers to data collected automatically,
either generated by the use of the Service or from the Service
infrastructure itself (for example, the duration of a page visit).
</p>
</li>
<li>
<p>
<strong>You</strong> means the individual accessing or using the
Service, or the company, or other legal entity on behalf of which
such individual is accessing or using the Service, as applicable.
</p>
</li>
</ul>
<h2>Collecting and Using Your Personal Data</h2>
<h3>Types of Data Collected</h3>
<p>
<strong
>We do not collect any data about you under any circumstances.</strong
>
</p>
<h2>Links to Other Websites</h2>
<p>
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.
</p>
<p>
We have no control over and assume no responsibility for the content,
privacy policies or practices of any third party sites or services.
</p>
<h2>Changes to this Privacy Policy</h2>
<p>
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.
</p>
<p>
We will let You know via email and/or a prominent notice on Our Service,
prior to the change becoming effective and update the &quot;Last
updated&quot; date at the top of this Privacy Policy.
</p>
<p>
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.
</p>
<h2>Contact Us</h2>
<p>If you have any questions about this Privacy Policy, You can contact us:</p>
<ul>
<li>
By visiting this page on our website:
<a
href="https://github.com/Nitwel/Immersive-Home"
rel="external nofollow noopener"
target="_blank"
>https://github.com/Nitwel/Immersive-Home</a
>
</li>
</ul>

View File

@ -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]