in-world ui textboxes

This commit is contained in:
2026-06-12 09:08:13 -05:00
parent a4d47d7f00
commit b364fae1c7
16 changed files with 265 additions and 27 deletions
+58 -10
View File
@@ -1,6 +1,10 @@
class_name VNTextbox extends PanelContainer
class_name ChatBox extends PanelContainer
const VN_REVEAL_TIME = 0.01
const _DEFAULT_ANCHOR_TOP = 1.0
const _DEFAULT_ANCHOR_RIGHT = 1.0
const _DEFAULT_ANCHOR_BOTTOM = 1.0
const _DEFAULT_OFFSET_TOP = -58.0
var label:AdvancedRichText;
var parsedOutText = ""
@@ -12,6 +16,7 @@ var currentViewScrolled = true;
var isSpeedupDown = false;
var hasLetGoOfInteract:bool = true;
var worldTarget:Node3D = null
var isClosed:bool = false:
get():
@@ -19,20 +24,63 @@ var isClosed:bool = false:
set(value):
self.visible = !value;
signal textboxClosing
signal chatboxClosing
func _ready() -> void:
label = $MarginContainer/Label
isClosed = true
func _setWorldLayout(hasTarget:bool) -> void:
if hasTarget:
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 0.0
offset_top = 0.0
offset_right = 0.0
offset_bottom = 0.0
custom_minimum_size = Vector2(90, 0)
else:
if label != null:
label.fit_content = false
anchor_left = 0.0
anchor_top = _DEFAULT_ANCHOR_TOP
anchor_right = _DEFAULT_ANCHOR_RIGHT
anchor_bottom = _DEFAULT_ANCHOR_BOTTOM
offset_left = 0.0
offset_top = _DEFAULT_OFFSET_TOP
offset_right = 0.0
offset_bottom = 0.0
custom_minimum_size = Vector2.ZERO
func _updateWorldPosition() -> void:
if worldTarget == null:
return
var camera:Camera3D = get_viewport().get_camera_3d()
if camera == null:
return
var screenPos:Vector2 = camera.unproject_position(worldTarget.global_position + Vector3(0, 2.5, 0))
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
position = screenPos - size * 0.5
position.x = clamp(position.x, 0.0, viewportSize.x - size.x)
position.y = clamp(position.y, 0.0, viewportSize.y - size.y)
func _process(delta: float) -> void:
if isClosed:
return;
_updateWorldPosition()
# _recalcText always resets fit_content to false; re-apply so the
# PanelContainer can grow to its content height in world-space mode.
if worldTarget != null and !label.fit_content:
label.fit_content = true
if label.getFinalText() == "":
isClosed = true;
return;
if Input.is_action_just_released("interact") && !hasLetGoOfInteract:
hasLetGoOfInteract = true;
return
@@ -51,7 +99,7 @@ func _process(delta: float) -> void:
else:
# On last page
if Input.is_action_just_released("interact") && hasLetGoOfInteract:
textboxClosing.emit();
chatboxClosing.emit();
isClosed = true;
currentViewScrolled = true
return;
@@ -73,15 +121,15 @@ func _process(delta: float) -> void:
revealTimer = 0;
label.visible_characters += 1;
func setText(text:String) -> void:
# Prepare textbox for scrolling
func setText(text:String, target:Node3D = null) -> void:
worldTarget = target
_setWorldLayout(worldTarget != null)
# Resets scroll
revealTimer = 0;
currentViewScrolled = false;
label.startLine = 0;
# I had a frame wait here before.
label.text = text;
label.visible_characters = 0;
@@ -90,7 +138,7 @@ func setText(text:String) -> void:
isSpeedupDown = false
isClosed = false;
func setTextAndWait(text:String) -> void:
self.setText(text);
await self.textboxClosing
func setTextAndWait(text:String, target:Node3D = null) -> void:
self.setText(text, target);
await self.chatboxClosing
await get_tree().process_frame
+1 -1
View File
@@ -4,7 +4,7 @@
[ext_resource type="Script" uid="uid://h8lw23ypcfty" path="res://ui/component/VNTextbox.gd" id="2_uo1gm"]
[ext_resource type="Script" uid="uid://bjj6upgk1uvxd" path="res://ui/component/advancedrichtext/AdvancedRichText.gd" id="3_m60k3"]
[node name="VNTextbox" type="PanelContainer"]
[node name="ChatBox" type="PanelContainer"]
clip_contents = true
anchors_preset = 12
anchor_top = 1.0
+62
View File
@@ -0,0 +1,62 @@
class_name WorldChatBox extends PanelContainer
enum Mode { ADVANCEABLE, TIMED }
const SCENE = preload("res://ui/component/WorldChatBox.tscn")
signal chatboxClosed
var mode:Mode = Mode.TIMED
var worldTarget:Node3D = null
var _timer:float = 0.0
var _label:Label
func _ready() -> void:
_label = $MarginContainer/Label
UI.addChatBox(self)
visible = false
func showTimed(text:String, duration:float) -> void:
mode = Mode.TIMED
_timer = duration
_label.text = text
visible = true
func showAdvanceable(text:String) -> void:
mode = Mode.ADVANCEABLE
_label.text = text
visible = true
func showAndWait(text:String) -> void:
showAdvanceable(text)
await chatboxClosed
func close() -> void:
UI.removeChatBox(self)
chatboxClosed.emit()
queue_free()
func _updateWorldPosition() -> void:
if worldTarget == null:
return
var camera:Camera3D = get_viewport().get_camera_3d()
if camera == null:
return
var screenPos:Vector2 = camera.unproject_position(worldTarget.global_position + Vector3(0, 2.5, 0))
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
position = screenPos - size * 0.5
position.x = clamp(position.x, 0.0, viewportSize.x - size.x)
position.y = clamp(position.y, 0.0, viewportSize.y - size.y)
func _process(delta:float) -> void:
if !visible:
return
_updateWorldPosition()
match mode:
Mode.TIMED:
_timer -= delta
if _timer <= 0.0:
close()
Mode.ADVANCEABLE:
if Input.is_action_just_pressed("interact"):
close()
+1
View File
@@ -0,0 +1 @@
uid://m1keb2pw8bqq
+21
View File
@@ -0,0 +1,21 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Theme" uid="uid://dm7ee4aqjr2dl" path="res://ui/UI Theme.tres" id="1_wx4lp"]
[ext_resource type="Script" path="res://ui/component/WorldChatBox.gd" id="2_wcb"]
[node name="WorldChatBox" type="PanelContainer"]
custom_minimum_size = Vector2(90, 0)
mouse_filter = 2
theme = ExtResource("1_wx4lp")
script = ExtResource("2_wcb")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 3
[node name="Label" type="Label" parent="MarginContainer"]
layout_mode = 2
autowrap_mode = 3