diff --git a/README.md b/README.md index 4208d74..a7acafb 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ It is also possible to bubble up information by returning a dictionary from a fu | `_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 | +| `_on_notify` | `[event: EventNotify]` | The ui notification system | 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. diff --git a/assets/design.afdesign b/assets/design.afdesign index 507d4a3..e80316b 100644 --- a/assets/design.afdesign +++ b/assets/design.afdesign @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3782adb5391dac4872890ccd7c7820c6c4eb20d56031598c1f3c4d978438a914 -size 12684397 +oid sha256:5a691fa84af96f6cb9243d817266e0a4064cee5384eb97bf2bf45ce799b859ae +size 16818156 diff --git a/assets/materials/pri-500.material b/assets/materials/pri-500.material index d726374..9b00b4d 100644 --- a/assets/materials/pri-500.material +++ b/assets/materials/pri-500.material @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92f36b94bc49caee6ea06bae49983840ce37d27c0e38309038c20163c8b1c7b4 -size 1035 +oid sha256:130236cec1c40b3b471db1c7099d86383a83b28d79316dd2ac45bc445e4a9bc5 +size 1038 diff --git a/content/system/keyboard/keyboard.tscn b/content/system/keyboard/keyboard.tscn index 86db653..de85526 100644 --- a/content/system/keyboard/keyboard.tscn +++ b/content/system/keyboard/keyboard.tscn @@ -2,7 +2,7 @@ [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/ui/components/grid_container/grid_container.gd" id="3_mx544"] [ext_resource type="Script" path="res://content/functions/movable.gd" id="4_86fct"] [ext_resource type="Material" uid="uid://bnwimm214q67g" path="res://assets/materials/sec-500.material" id="5_8c8rc"] [ext_resource type="Script" path="res://content/functions/occludable.gd" id="6_y4sdl"] diff --git a/content/ui/menu/container3d.gd b/content/ui/components/container/container3d.gd similarity index 100% rename from content/ui/menu/container3d.gd rename to content/ui/components/container/container3d.gd diff --git a/content/ui/menu/grid.gd b/content/ui/components/grid_container/grid_container.gd similarity index 100% rename from content/ui/menu/grid.gd rename to content/ui/components/grid_container/grid_container.gd diff --git a/content/ui/components/notification/notification.gd b/content/ui/components/notification/notification.gd new file mode 100644 index 0000000..5b8e6b6 --- /dev/null +++ b/content/ui/components/notification/notification.gd @@ -0,0 +1,54 @@ +@tool +extends Node3D + +@onready var label: Label3D = $AnimationNode/Text +@onready var icon_label: Label3D = $AnimationNode/Icon +@onready var mesh: MeshInstance3D = $AnimationNode/MeshInstance3D +@onready var collision: CollisionShape3D = $AnimationNode/CollisionShape3D +@onready var animation_player: AnimationPlayer = $AnimationNode/AnimationPlayer +@onready var button = $AnimationNode/Button +@onready var timer = $AnimationNode/Timer + +@export var type: EventNotify.Type = EventNotify.Type.INFO: + set(value): + type = value + if !is_node_ready(): await ready + print(value, " ", _type_to_string(value)) + icon_label.text = _type_to_string(value) +@export var text: String = "": + set(value): + text = value + if !is_node_ready(): await ready + label.text = value + +func _ready(): + button.on_button_down.connect(fade_out) + fade_in() + + timer.timeout.connect(func(): + fade_out() + ) + +func fade_in(): + if !is_node_ready(): await ready + + animation_player.play("fade_in") + +func fade_out(): + animation_player.play_backwards("fade_in") + + await animation_player.animation_finished + queue_free() + +func _type_to_string(type: EventNotify.Type) -> String: + match type: + EventNotify.Type.INFO: + return "info" + EventNotify.Type.SUCCESS: + return "check_circle" + EventNotify.Type.WARNING: + return "error" + EventNotify.Type.DANGER: + return "warning" + _: + return "info" diff --git a/content/ui/components/notification/notification.tscn b/content/ui/components/notification/notification.tscn new file mode 100644 index 0000000..2d4b1a7 --- /dev/null +++ b/content/ui/components/notification/notification.tscn @@ -0,0 +1,202 @@ +[gd_scene load_steps=10 format=3 uid="uid://bqj7qwj5mgd30"] + +[ext_resource type="Script" path="res://content/ui/components/notification/notification.gd" id="1_yw3yb"] +[ext_resource type="Material" uid="uid://bujy3egn1oqac" path="res://assets/materials/pri-500.material" id="2_5b8oo"] +[ext_resource type="FontVariation" uid="uid://sshfnckriqxn" path="res://assets/icons/icons.tres" id="3_1ljpc"] +[ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="4_ocg5j"] + +[sub_resource type="BoxMesh" id="BoxMesh_s37oj"] +size = Vector3(0.25, 0.01, 0.05) + +[sub_resource type="BoxShape3D" id="BoxShape3D_m4d21"] +size = Vector3(0.25, 0.01, 0.05) + +[sub_resource type="Animation" id="Animation_bkual"] +length = 0.001 +tracks/0/type = "bezier" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:scale:x") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"handle_modes": PackedInt32Array(0), +"points": PackedFloat32Array(1, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0) +} +tracks/1/type = "bezier" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:scale:y") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"handle_modes": PackedInt32Array(0), +"points": PackedFloat32Array(1, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0) +} +tracks/2/type = "bezier" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath(".:scale:z") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"handle_modes": PackedInt32Array(0), +"points": PackedFloat32Array(1, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0) +} +tracks/3/type = "bezier" +tracks/3/imported = false +tracks/3/enabled = true +tracks/3/path = NodePath(".:position:x") +tracks/3/interp = 1 +tracks/3/loop_wrap = true +tracks/3/keys = { +"handle_modes": PackedInt32Array(0), +"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0) +} +tracks/4/type = "bezier" +tracks/4/imported = false +tracks/4/enabled = true +tracks/4/path = NodePath(".:position:y") +tracks/4/interp = 1 +tracks/4/loop_wrap = true +tracks/4/keys = { +"handle_modes": PackedInt32Array(0), +"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0) +} +tracks/5/type = "bezier" +tracks/5/imported = false +tracks/5/enabled = true +tracks/5/path = NodePath(".:position:z") +tracks/5/interp = 1 +tracks/5/loop_wrap = true +tracks/5/keys = { +"handle_modes": PackedInt32Array(0), +"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0) +} + +[sub_resource type="Animation" id="Animation_r1tka"] +resource_name = "fade_in" +length = 0.3 +step = 0.01 +tracks/0/type = "bezier" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:scale:x") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(0.01, -0.25, 0, 0.25, 0, 1, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.3) +} +tracks/1/type = "bezier" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".:scale:y") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(1, -0.25, 0, 0.25, 0, 1, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.3) +} +tracks/2/type = "bezier" +tracks/2/imported = false +tracks/2/enabled = true +tracks/2/path = NodePath(".:scale:z") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(1, -0.25, 0, 0.25, 0, 1, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.3) +} +tracks/3/type = "bezier" +tracks/3/imported = false +tracks/3/enabled = true +tracks/3/path = NodePath(".:position:x") +tracks/3/interp = 1 +tracks/3/loop_wrap = true +tracks/3/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(-0.13, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.3) +} +tracks/4/type = "bezier" +tracks/4/imported = false +tracks/4/enabled = true +tracks/4/path = NodePath(".:position:y") +tracks/4/interp = 1 +tracks/4/loop_wrap = true +tracks/4/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.3) +} +tracks/5/type = "bezier" +tracks/5/imported = false +tracks/5/enabled = true +tracks/5/path = NodePath(".:position:z") +tracks/5/interp = 1 +tracks/5/loop_wrap = true +tracks/5/keys = { +"handle_modes": PackedInt32Array(0, 0), +"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.25, 0), +"times": PackedFloat32Array(0, 0.3) +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_kbhuj"] +_data = { +"RESET": SubResource("Animation_bkual"), +"fade_in": SubResource("Animation_r1tka") +} + +[node name="Notification" type="Node3D"] +script = ExtResource("1_yw3yb") + +[node name="AnimationNode" type="StaticBody3D" parent="."] + +[node name="MeshInstance3D" type="MeshInstance3D" parent="AnimationNode"] +material_override = ExtResource("2_5b8oo") +mesh = SubResource("BoxMesh_s37oj") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="AnimationNode"] +shape = SubResource("BoxShape3D_m4d21") + +[node name="Text" type="Label3D" parent="AnimationNode"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.08, 0.006, 0) +pixel_size = 0.001 +text = "Example Text" +font_size = 10 +outline_size = 0 +horizontal_alignment = 0 +autowrap_mode = 3 +width = 190.0 + +[node name="Icon" type="Label3D" parent="AnimationNode"] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.1, 0.006, 0) +pixel_size = 0.001 +text = "check_circle" +font = ExtResource("3_1ljpc") +font_size = 24 +outline_size = 0 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="AnimationNode"] +libraries = { +"": SubResource("AnimationLibrary_kbhuj") +} + +[node name="Button" parent="AnimationNode" instance=ExtResource("4_ocg5j")] +transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0.12, -0.003, -0.02) +label = "close" +icon = true + +[node name="Timer" type="Timer" parent="AnimationNode"] +wait_time = 3.0 +autostart = true diff --git a/content/ui/menu/edit/edit_menu.tscn b/content/ui/menu/edit/edit_menu.tscn index 0c1c898..7be08fb 100644 --- a/content/ui/menu/edit/edit_menu.tscn +++ b/content/ui/menu/edit/edit_menu.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://crrb0l3ekuotj"] [ext_resource type="Script" path="res://content/ui/menu/edit/edit_menu.gd" id="1_34cbn"] -[ext_resource type="Script" path="res://content/ui/menu/grid.gd" id="3_0xvyw"] +[ext_resource type="Script" path="res://content/ui/components/grid_container/grid_container.gd" id="3_0xvyw"] [ext_resource type="PackedScene" uid="uid://bsjqdvkt0u87c" path="res://content/ui/components/button/button.tscn" id="4_tvimg"] [node name="EditMenu" type="Node3D"] diff --git a/content/ui/menu/menu.gd b/content/ui/menu/menu.gd index 061bb0d..e1b4f5b 100644 --- a/content/ui/menu/menu.gd +++ b/content/ui/menu/menu.gd @@ -1,6 +1,7 @@ extends Node3D const Proxy = preload("res://lib/utils/proxy.gd") +const Notification = preload("res://content/ui/components/notification/notification.tscn") @onready var _controller := XRHelpers.get_xr_controller(self) @@ -17,6 +18,7 @@ const Proxy = preload("res://lib/utils/proxy.gd") @onready var content = $AnimationContainer/Content @onready var nav = $AnimationContainer/Navigation @onready var animation_player = $AnimationPlayer +@onready var notify_place = $AnimationContainer/NotifyPlace var selected_nav = null @@ -34,6 +36,21 @@ func _ready(): _controller.button_pressed.connect(func(button): if button == "by_button": show_menu = !show_menu + ) + + EventSystem.on_notify.connect(func(event: EventNotify): + var notification_node = Notification.instantiate() + notification_node.text = event.message + notification_node.type = event.type + + for child in notify_place.get_children(): + child.position += Vector3(0, 0, -0.06) + + notify_place.add_child(notification_node) + + + + ) var nav_buttons = [ diff --git a/content/ui/menu/menu.tscn b/content/ui/menu/menu.tscn index 2305381..c6e45a3 100644 --- a/content/ui/menu/menu.tscn +++ b/content/ui/menu/menu.tscn @@ -213,6 +213,9 @@ visible = false [node name="SettingsMenu" parent="AnimationContainer/Content" instance=ExtResource("11_7wm6b")] visible = false +[node name="NotifyPlace" type="Marker3D" parent="AnimationContainer"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2, 0, -0.05) + [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) visible = false diff --git a/lib/events/event_notify.gd b/lib/events/event_notify.gd new file mode 100644 index 0000000..991a847 --- /dev/null +++ b/lib/events/event_notify.gd @@ -0,0 +1,12 @@ +extends Event +class_name EventNotify + +enum Type { + INFO, + SUCCESS, + WARNING, + DANGER +} + +var message: String +var type: Type \ No newline at end of file diff --git a/lib/globals/event_system.gd b/lib/globals/event_system.gd index 3d3da8b..af4fc18 100644 --- a/lib/globals/event_system.gd +++ b/lib/globals/event_system.gd @@ -24,6 +24,8 @@ signal on_touch_enter(event: EventTouch) signal on_touch_move(event: EventTouch) signal on_touch_leave(event: EventTouch) +signal on_notify(event: EventNotify) + var _active_node: Node = null func emit(type: String, event: Event): @@ -32,6 +34,12 @@ func emit(type: String, event: Event): else: _root_call(type, event) +func notify(message: String, type := EventNotify.Type.INFO): + var event = EventNotify.new() + event.message = message + event.type = type + emit("notify", event) + func is_focused(node: Node): return _active_node == node diff --git a/lib/globals/save_system.gd b/lib/globals/save_system.gd index 73db18c..000a9df 100644 --- a/lib/globals/save_system.gd +++ b/lib/globals/save_system.gd @@ -3,7 +3,7 @@ extends Node const VariantSerializer = preload("res://lib/utils/variant_serializer.gd") func clear(): - _clear_save_tree(get_tree().root.get_node("Main")) + await _clear_save_tree(get_tree().root.get_node("Main")) func save(): if HomeApi.has_connected() == false: @@ -22,7 +22,7 @@ func save(): save_file.store_line(json_text) func load(): - clear() + await clear() if HomeApi.has_connected() == false: return @@ -45,10 +45,11 @@ func load(): func _clear_save_tree(node: Node): for child in node.get_children(): - _clear_save_tree(child) + await _clear_save_tree(child) if node.has_method("_save"): node.queue_free() + await node.tree_exited func _generate_save_tree(node: Node): var children = [] diff --git a/lib/home_apis/hass_ws/hass.gd b/lib/home_apis/hass_ws/hass.gd index b66f9ce..863d935 100644 --- a/lib/home_apis/hass_ws/hass.gd +++ b/lib/home_apis/hass_ws/hass.gd @@ -20,8 +20,6 @@ var authenticated := false var id := 1 var entities: Dictionary = {} -var retries := 5 - var entitiy_callbacks := CallbackMap.new() var packet_callbacks := CallbackMap.new() @@ -36,18 +34,13 @@ 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) # https://github.com/godotengine/godot/issues/84423 # Otherwise the WebSocketPeer will crash when receiving large packets - socket.set_inbound_buffer_size(65535 * 2) + socket.set_inbound_buffer_size(65535 * 4) func _process(delta): socket.poll() @@ -66,7 +59,13 @@ func _process(delta): elif state == WebSocketPeer.STATE_CLOSED: var code = socket.get_close_code() var reason = socket.get_close_reason() - print("WS connection closed with code: %s, reason: %s" % [code, reason]) + + if reason == "": + reason = "Invalid URL" + + var message = "WS connection closed with code: %s, reason: %s" % [code, reason] + EventSystem.notify(message, EventNotify.Type.DANGER) + print(message) handle_disconnect() func handle_packet(packet: Dictionary): @@ -83,6 +82,8 @@ func handle_packet(packet: Dictionary): start_subscriptions() elif packet.type == "auth_invalid": + EventSystem.notify("Failed to authenticate, invalid auth token", EventNotify.Type.DANGER) + print("Failed to authenticate, invalid auth token") handle_disconnect() else: packet_callbacks.call_key(int(packet.id), [packet]) @@ -136,9 +137,6 @@ func handle_disconnect(): set_process(false) on_disconnect.emit() - # Reconnect - connect_ws() - func send_subscribe_packet(packet: Dictionary, callback: Callable): packet.id = id id += 1