From e0ebd57176b074c344da817cdf13ca2d08bda2c9 Mon Sep 17 00:00:00 2001 From: Nitwel Date: Wed, 29 Nov 2023 00:16:25 +0100 Subject: [PATCH] implement slider component --- content/entities/light/light.gd | 65 +++----- content/entities/light/light.tscn | 81 +--------- content/entities/media_player/media_player.gd | 4 + .../entities/media_player/media_player.tscn | 2 +- content/ui/components/input/input.gd | 1 + content/ui/components/slider/slider.gd | 151 ++++++++++++++++++ content/ui/components/slider/slider.tscn | 79 +++++++++ lib/globals/event_system.gd | 4 - 8 files changed, 268 insertions(+), 119 deletions(-) create mode 100644 content/ui/components/slider/slider.gd create mode 100644 content/ui/components/slider/slider.tscn diff --git a/content/entities/light/light.gd b/content/entities/light/light.gd index 92f97ea..2641ead 100644 --- a/content/entities/light/light.gd +++ b/content/entities/light/light.gd @@ -4,11 +4,10 @@ extends StaticBody3D @export var color_off = Color(0.23, 0.23, 0.23) @export var color_on = Color(1.0, 0.85, 0.0) -@onready var shape = $CSGCombiner3D -@onready var rod_top = $RodTop -@onready var rod_bottom = $RodBottom -@onready var slider_knob = $Knob -var state = false +@onready var animation: AnimationPlayer = $AnimationPlayer +@onready var slider: Slider3D = $Slider + +var state = true var brightness = 0 # 0-255 # Called when the node enters the scene tree for the first time. @@ -22,19 +21,30 @@ func _ready(): set_state(new_state["state"] == "on") ) -func set_state(state: bool, brightness = null): - print("set_state ", state, brightness) - self.state = state - self.brightness = brightness + slider.on_value_changed.connect(func(new_value): + var value = new_value / 100 * 255 + HomeApi.set_state(entity_id, "on" if state else "off", {"brightness": int(value)}) + set_state(state, value) + ) + +func set_state(new_state: bool, new_brightness = null): + if state == false && new_state == false: + return + + state = new_state + brightness = new_brightness if state: if brightness == null: - shape.material_override.albedo_color = color_on + animation.speed_scale = 1 + animation.play_backwards("light") else: - shape.material_override.albedo_color = color_off.lerp(color_on, brightness / 255.0) + var duration = animation.get_animation("light").length + animation.speed_scale = 0 + animation.seek(lerpf(0, duration, 1 - (brightness / 255.0)), true) else: - shape.material_override.albedo_color = color_off - + animation.speed_scale = 1 + animation.play("light") func _on_click(event): @@ -45,31 +55,4 @@ func _on_click(event): attributes["brightness"] = int(brightness) HomeApi.set_state(entity_id, "on" if !state else "off", attributes) - set_state(!state, brightness) - else: - _on_clickable_on_click(event) - -func _on_press_move(event): - if event.target != self: - _on_clickable_on_click(event) - - -func _on_request_completed(): - pass - - -func _on_clickable_on_click(event): - var click_pos: Vector3 = to_local(event.ray.get_collision_point()) - var vec_bottom_to_top = rod_top.position - rod_bottom.position - - var length_click = click_pos.y - rod_bottom.position.y - var length_total = vec_bottom_to_top.y - - var ratio = length_click / length_total - - var new_pos = rod_bottom.position.lerp(rod_top.position, ratio) - - slider_knob.position = new_pos - - HomeApi.set_state(entity_id, "on" if state else "off", {"brightness": int(ratio * 255)}) - set_state(state, ratio * 255) + set_state(!state, brightness) \ No newline at end of file diff --git a/content/entities/light/light.tscn b/content/entities/light/light.tscn index 0d76a2f..4602274 100644 --- a/content/entities/light/light.tscn +++ b/content/entities/light/light.tscn @@ -1,28 +1,13 @@ -[gd_scene load_steps=15 format=3 uid="uid://cw86rc42dv2d8"] +[gd_scene load_steps=9 format=3 uid="uid://cw86rc42dv2d8"] [ext_resource type="Script" path="res://content/entities/light/light.gd" id="1_ykxy3"] -[ext_resource type="Texture2D" uid="uid://br3p0c2foputg" path="res://assets/materials/swich_on.png" id="2_6gn2e"] -[ext_resource type="Texture2D" uid="uid://co2ishj2hx57p" path="res://assets/materials/switch_off.png" id="3_qlm62"] [ext_resource type="Script" path="res://content/functions/movable.gd" id="4_4sfxb"] [ext_resource type="Material" uid="uid://vce66e7sbc3n" path="res://content/entities/light/light_on.tres" id="5_50gph"] +[ext_resource type="PackedScene" uid="uid://pk5k1q8bx0rj" path="res://content/ui/components/slider/slider.tscn" id="6_mhjlm"] [sub_resource type="SphereShape3D" id="SphereShape3D_ukj14"] radius = 0.05 -[sub_resource type="SpriteFrames" id="SpriteFrames_ldpuo"] -animations = [{ -"frames": [{ -"duration": 1.0, -"texture": ExtResource("2_6gn2e") -}, { -"duration": 1.0, -"texture": ExtResource("3_qlm62") -}], -"loop": true, -"name": &"default", -"speed": 5.0 -}] - [sub_resource type="Animation" id="Animation_afofi"] length = 0.001 tracks/0/type = "value" @@ -37,18 +22,6 @@ tracks/0/keys = { "update": 0, "values": [Color(1, 0.85098, 0, 1)] } -tracks/1/type = "value" -tracks/1/imported = false -tracks/1/enabled = true -tracks/1/path = NodePath("Knob:position") -tracks/1/interp = 1 -tracks/1/loop_wrap = true -tracks/1/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 0, -"values": [Vector3(0, 0.0492394, -0.0903599)] -} [sub_resource type="Animation" id="Animation_7o31s"] resource_name = "light" @@ -72,24 +45,6 @@ _data = { "light": SubResource("Animation_7o31s") } -[sub_resource type="CylinderMesh" id="CylinderMesh_j3pn3"] -top_radius = 0.004 -bottom_radius = 0.004 -height = 0.1 - -[sub_resource type="CylinderShape3D" id="CylinderShape3D_tysib"] -height = 0.1 -radius = 0.01 - -[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_13uml"] -albedo_color = Color(0.231373, 0.239216, 0.231373, 1) - -[sub_resource type="CylinderMesh" id="CylinderMesh_s8215"] -material = SubResource("StandardMaterial3D_13uml") -top_radius = 0.006 -bottom_radius = 0.006 -height = 0.004 - [node name="Light" type="StaticBody3D" groups=["entity"]] collision_mask = 0 script = ExtResource("1_ykxy3") @@ -97,12 +52,6 @@ script = ExtResource("1_ykxy3") [node name="CollisionShape3D" type="CollisionShape3D" parent="."] shape = SubResource("SphereShape3D_ukj14") -[node name="Icon" type="AnimatedSprite3D" parent="."] -visible = false -pixel_size = 0.0005 -billboard = 1 -sprite_frames = SubResource("SpriteFrames_ldpuo") - [node name="Movable" type="Node" parent="."] script = ExtResource("4_4sfxb") @@ -126,23 +75,9 @@ libraries = { "": SubResource("AnimationLibrary_8a76q") } -[node name="Rod" type="StaticBody3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.0903599) -collision_mask = 0 - -[node name="MeshInstance3D" type="MeshInstance3D" parent="Rod"] -mesh = SubResource("CylinderMesh_j3pn3") -skeleton = NodePath("../..") - -[node name="CollisionShape3D" type="CollisionShape3D" parent="Rod"] -shape = SubResource("CylinderShape3D_tysib") - -[node name="Knob" type="MeshInstance3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0492394, -0.0903599) -mesh = SubResource("CylinderMesh_s8215") - -[node name="RodBottom" type="Marker3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.0501399, -0.090675) - -[node name="RodTop" type="Marker3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0498752, -0.090942) +[node name="Slider" parent="." instance=ExtResource("6_mhjlm")] +transform = Transform3D(-4.37114e-08, 1, 0, 1, 4.37114e-08, 8.74228e-08, 8.74228e-08, 3.82137e-15, -1, 0.00190757, -0.00579122, -0.0914348) +max = 100.0 +value = 100.0 +step = 1.0 +size = Vector3(10, 0.4, 1) diff --git a/content/entities/media_player/media_player.gd b/content/entities/media_player/media_player.gd index 4defe33..26ef9f2 100644 --- a/content/entities/media_player/media_player.gd +++ b/content/entities/media_player/media_player.gd @@ -1,6 +1,7 @@ extends StaticBody3D @export var entity_id = "media_player.bedroomspeaker" +@export var image_width = 0.15 @onready var previous = $Previous @onready var next = $Next @@ -66,9 +67,12 @@ func load_image(url: String): var image = Image.new() var error = image.load_jpg_from_buffer(result[3]) + var pixel_size = image_width / image.get_size().x + if error != OK: print("Error loading image: ", error) return var texture = ImageTexture.create_from_image(image) logo.texture = texture + logo.pixel_size = pixel_size diff --git a/content/entities/media_player/media_player.tscn b/content/entities/media_player/media_player.tscn index 75b72ae..b9ee985 100644 --- a/content/entities/media_player/media_player.tscn +++ b/content/entities/media_player/media_player.tscn @@ -49,7 +49,7 @@ outline_size = 4 horizontal_alignment = 0 [node name="Logo" type="Sprite3D" parent="PlayingInfo"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.178254, 0) pixel_size = 0.001 [node name="HTTPRequest" type="HTTPRequest" parent="PlayingInfo"] diff --git a/content/ui/components/input/input.gd b/content/ui/components/input/input.gd index b069c0d..a543f8d 100644 --- a/content/ui/components/input/input.gd +++ b/content/ui/components/input/input.gd @@ -1,5 +1,6 @@ @tool extends StaticBody3D +class_name Input3D var text_handler = preload("res://content/ui/components/input/text_handler.gd").new() diff --git a/content/ui/components/slider/slider.gd b/content/ui/components/slider/slider.gd new file mode 100644 index 0000000..981ddb7 --- /dev/null +++ b/content/ui/components/slider/slider.gd @@ -0,0 +1,151 @@ +@tool +extends Node3D +class_name Slider3D + +@export var min: float = 0.0: + set(new_value): + min = new_value + if value < min: value = min + if !is_node_ready(): await ready + _update_slider() + +@export var max: float = 1.0: + set(new_value): + max = new_value + if value > max: value = max + if !is_node_ready(): await ready + _update_slider() +@export var value: float = 0.2: + set(new_value): + value = roundi(clamp(new_value, min, max) / step) * step + + if !is_node_ready(): await ready + label.text = str(value) + " " + label_unit + on_value_changed.emit(value) + _update_slider() + +@export var step: float = 0.01 + +@export var show_label: bool = false: + set(value): + show_label = value + if !is_node_ready(): await ready + label.visible = show_label + +@export var label_unit: String = "": + set(new_value): + label_unit = new_value + if !is_node_ready(): await ready + label.text = str(value) + " " + label_unit + +@export var size: Vector3 = Vector3(0.2, 0.01, 0.02): # Warning, units are in cm + set(value): + size = value + if !is_node_ready(): await ready + _update_shape() +@export var cutout_border: float = 0.02: + set(value): + cutout_border = value + if !is_node_ready(): await ready + _update_shape() +@export var cutout_depth: float = 0.05: + set(value): + cutout_depth = value + if !is_node_ready(): await ready + _update_shape() + +@onready var outside_rod: CSGBox3D = $Rod/Outside +@onready var cutout: CSGCombiner3D = $Rod/Cutout +@onready var cutout_box: CSGBox3D = $Rod/Cutout/Length +@onready var cutout_end_left: CSGCylinder3D = $Rod/Cutout/EndLeft +@onready var cutout_end_right: CSGCylinder3D = $Rod/Cutout/EndRight +@onready var label: Label3D = $Label + +@onready var slider_knob: MeshInstance3D = $Knob + +signal on_value_changed(value: float) + +var move_plane: Plane + +func _ready(): + _update_shape() + move_plane = Plane(Vector3.UP, Vector3(0, size.y / 200, 0)) + +func _on_press_down(event: EventPointer): + _handle_press(event) + +func _on_press_move(event: EventPointer): + _handle_press(event) + +func _get_slider_min_max(): + var cutout_radius = (size.z - cutout_border * 2) / 2 + + return Vector2(-size.x / 2 + cutout_border + cutout_radius, size.x / 2 - cutout_border - cutout_radius) / 100 + +func _handle_press(event: EventPointer): + var ray_pos = event.ray.global_position + var ray_dir = -event.ray.global_transform.basis.z + + var local_pos = to_local(ray_pos) + var local_dir = global_transform.basis.inverse() * ray_dir + + var click_pos = move_plane.intersects_ray(local_pos, local_dir) + + if click_pos == null: + return + + var min_max = _get_slider_min_max() + + var pos_x = clamp(click_pos.x, min_max.x, min_max.y) + + var click_percent = inverse_lerp(min_max.x, min_max.y, pos_x) + + value = lerp(min, max, click_percent) + _update_slider() + +func _update_slider(): + var min_max = _get_slider_min_max() + + var click_percent = inverse_lerp(min, max, value) + + slider_knob.position.x = lerp(min_max.x, min_max.y, click_percent) + +func _update_shape(): + outside_rod.size = size + + var cutout_width = size.z - cutout_border * 2 + + cutout_box.size = Vector3( + size.x - cutout_border * 2 - (cutout_width), + cutout_depth, + cutout_width + ) + + cutout.position = Vector3( + 0, + size.y / 2 - cutout_depth / 2 + 0.001, + 0 + ) + + cutout_end_left.radius = cutout_box.size.z / 2 + cutout_end_right.radius = cutout_box.size.z / 2 + cutout_end_left.height = cutout_depth + cutout_end_right.height = cutout_depth + + cutout_end_left.position = Vector3( + -cutout_box.size.x / 2, + 0, + 0 + ) + + cutout_end_right.position = Vector3( + cutout_box.size.x / 2, + 0, + 0 + ) + + label.position = Vector3( + size.x / 200 + 0.005, + 0, + 0 + ) diff --git a/content/ui/components/slider/slider.tscn b/content/ui/components/slider/slider.tscn new file mode 100644 index 0000000..f2a4107 --- /dev/null +++ b/content/ui/components/slider/slider.tscn @@ -0,0 +1,79 @@ +[gd_scene load_steps=6 format=3 uid="uid://pk5k1q8bx0rj"] + +[ext_resource type="Script" path="res://content/ui/components/slider/slider.gd" id="1_ylune"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_eiwwn"] +albedo_color = Color(0.133333, 0.133333, 0.133333, 1) + +[sub_resource type="CylinderMesh" id="CylinderMesh_77ny1"] +material = SubResource("StandardMaterial3D_eiwwn") +top_radius = 0.008 +bottom_radius = 0.008 +height = 0.003 + +[sub_resource type="CylinderMesh" id="CylinderMesh_v34nn"] +top_radius = 0.002 +bottom_radius = 0.002 +height = 0.005 + +[sub_resource type="BoxShape3D" id="BoxShape3D_h1mn1"] +size = Vector3(0.2, 0.004, 0.01) + +[node name="Slider" type="Node3D"] +script = ExtResource("1_ylune") +size = Vector3(20, 0.4, 1) +cutout_border = 0.2 +cutout_depth = 0.36 + +[node name="Rod" type="CSGCombiner3D" parent="."] +transform = Transform3D(0.01, 0, 0, 0, 0.01, 0, 0, 0, 0.01, 0, 0, 0) +snap = 0.0001 + +[node name="Outside" type="CSGBox3D" parent="Rod"] +snap = 0.0001 +size = Vector3(20, 0.4, 1) + +[node name="Cutout" type="CSGCombiner3D" parent="Rod"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.021, 0) +operation = 2 +snap = 0.0001 + +[node name="Length" type="CSGBox3D" parent="Rod/Cutout"] +snap = 0.0001 +size = Vector3(19, 0.36, 0.6) + +[node name="EndRight" type="CSGCylinder3D" parent="Rod/Cutout"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.5, 0, 0) +snap = 0.0001 +radius = 0.3 +height = 0.36 +sides = 36 + +[node name="EndLeft" type="CSGCylinder3D" parent="Rod/Cutout"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.5, 0, 0) +snap = 0.0001 +radius = 0.3 +height = 0.36 +sides = 36 + +[node name="Knob" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.057, 0.00409426, 0) +mesh = SubResource("CylinderMesh_77ny1") + +[node name="Pin" type="MeshInstance3D" parent="Knob"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.00353538, 0) +mesh = SubResource("CylinderMesh_v34nn") + +[node name="CollisionBody" type="StaticBody3D" parent="."] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="CollisionBody"] +shape = SubResource("BoxShape3D_h1mn1") + +[node name="Label" type="Label3D" parent="."] +transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.105, 0, 0) +visible = false +pixel_size = 0.001 +text = "0.2" +font_size = 10 +outline_size = 4 +horizontal_alignment = 0 diff --git a/lib/globals/event_system.gd b/lib/globals/event_system.gd index 3ccabc5..3d3da8b 100644 --- a/lib/globals/event_system.gd +++ b/lib/globals/event_system.gd @@ -36,8 +36,6 @@ func is_focused(node: Node): return _active_node == node func _handle_focus(target: Variant, event: EventBubble): - print("focus ", target, " ", target.get_groups(), " ", event) - if target != null: if target.is_in_group("ui_focus_skip"): return false @@ -58,8 +56,6 @@ func _handle_focus(target: Variant, event: EventBubble): _active_node = target - print("focus", _active_node ) - 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)