ui(chat): 💄 Add new conversation list UI scene and update ChatWindow and ConversationList logic
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
74f9134d48
commit
98fa0f930f
3 changed files with 194 additions and 1 deletions
|
|
@ -3,6 +3,7 @@ extends "res://src/ui/panel_window.gd"
|
|||
|
||||
const ChatDisplayScript = preload("res://src/chat/chat_display.gd")
|
||||
const ChatInputScript = preload("res://src/chat/chat_input.gd")
|
||||
const ConversationListScript = preload("res://src/chat/conversation_list.gd")
|
||||
|
||||
const STATE_LABELS: Dictionary = {
|
||||
"idle": "",
|
||||
|
|
@ -25,6 +26,7 @@ const REPLAY_EMOTIONS: Array[String] = [
|
|||
var _display: VBoxContainer
|
||||
var _input_bar: PanelContainer
|
||||
var _status_label: Label
|
||||
var _conversation_list: VBoxContainer
|
||||
var _miku_streaming: bool = false
|
||||
var _replay_regex: RegEx
|
||||
|
||||
|
|
@ -62,6 +64,10 @@ func _build_ui() -> void:
|
|||
root.add_child(_build_title_bar())
|
||||
root.add_child(_build_divider())
|
||||
|
||||
_conversation_list = ConversationListScript.new()
|
||||
_conversation_list.setup()
|
||||
root.add_child(_conversation_list)
|
||||
|
||||
# Message display — single RichTextLabel with native scrolling and selection
|
||||
var display_margin := MarginContainer.new()
|
||||
display_margin.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
|
|
@ -90,7 +96,28 @@ func _build_title_bar() -> Control:
|
|||
_status_label.text = ""
|
||||
_status_label.add_theme_color_override("font_color", TEXT_MUTED)
|
||||
_status_label.add_theme_font_size_override("font_size", 11)
|
||||
return _build_panel_title_bar("✦ MIKU", [_status_label])
|
||||
|
||||
var list_btn := Button.new()
|
||||
list_btn.text = "☰"
|
||||
list_btn.flat = true
|
||||
list_btn.tooltip_text = "Conversations"
|
||||
list_btn.custom_minimum_size = Vector2(28, 28)
|
||||
list_btn.add_theme_color_override("font_color", TEXT_MUTED)
|
||||
list_btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
|
||||
list_btn.add_theme_font_size_override("font_size", 15)
|
||||
list_btn.pressed.connect(_on_list_toggle)
|
||||
|
||||
var new_btn := Button.new()
|
||||
new_btn.text = "+"
|
||||
new_btn.flat = true
|
||||
new_btn.tooltip_text = "New conversation"
|
||||
new_btn.custom_minimum_size = Vector2(28, 28)
|
||||
new_btn.add_theme_color_override("font_color", TEXT_MUTED)
|
||||
new_btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
|
||||
new_btn.add_theme_font_size_override("font_size", 18)
|
||||
new_btn.pressed.connect(_on_new_conversation)
|
||||
|
||||
return _build_panel_title_bar("✦ MIKU", [_status_label, list_btn, new_btn])
|
||||
|
||||
|
||||
func replay_messages(messages: Array[Dictionary]) -> void:
|
||||
|
|
@ -119,6 +146,15 @@ func show_error(message: String) -> void:
|
|||
_display.add_error_message(message)
|
||||
|
||||
|
||||
func _on_new_conversation() -> void:
|
||||
_conversation_list.collapse()
|
||||
EventBus.conversation_new_requested.emit()
|
||||
|
||||
|
||||
func _on_list_toggle() -> void:
|
||||
_conversation_list.toggle()
|
||||
|
||||
|
||||
func _on_input_message(text: String) -> void:
|
||||
EventBus.text_submitted.emit(text)
|
||||
|
||||
|
|
|
|||
156
shared/godot/chat/conversation_list.gd
Normal file
156
shared/godot/chat/conversation_list.gd
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
extends VBoxContainer
|
||||
## Collapsible conversation history list for the chat window.
|
||||
## Shows recent conversations, highlights the active one, emits switch signals.
|
||||
|
||||
const BG_DARK := Color("#0D1117")
|
||||
const BG_PANEL := Color("#111822")
|
||||
const BG_HOVER := Color("#162230")
|
||||
const MIKU_TEAL := Color("#39C5BB")
|
||||
const TEXT_PRIMARY := Color("#E8F4F3")
|
||||
const TEXT_MUTED := Color("#6B8E8B")
|
||||
const BORDER_COLOR := Color("#1A3330")
|
||||
|
||||
const MAX_VISIBLE: int = 10
|
||||
|
||||
var _item_container: VBoxContainer
|
||||
var _expanded: bool = false
|
||||
|
||||
|
||||
func setup() -> void:
|
||||
visible = false
|
||||
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
var wrapper := PanelContainer.new()
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = BG_PANEL
|
||||
style.set_border_width_all(1)
|
||||
style.border_color = BORDER_COLOR
|
||||
style.content_margin_left = 6
|
||||
style.content_margin_right = 6
|
||||
style.content_margin_top = 6
|
||||
style.content_margin_bottom = 6
|
||||
wrapper.add_theme_stylebox_override("panel", style)
|
||||
add_child(wrapper)
|
||||
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.custom_minimum_size.y = 0
|
||||
scroll.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
wrapper.add_child(scroll)
|
||||
|
||||
_item_container = VBoxContainer.new()
|
||||
_item_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_item_container.add_theme_constant_override("separation", 2)
|
||||
scroll.add_child(_item_container)
|
||||
|
||||
EventBus.conversation_changed.connect(_on_conversation_changed)
|
||||
|
||||
|
||||
func toggle() -> void:
|
||||
if _expanded:
|
||||
collapse()
|
||||
else:
|
||||
expand()
|
||||
|
||||
|
||||
func expand() -> void:
|
||||
_expanded = true
|
||||
refresh()
|
||||
visible = true
|
||||
|
||||
|
||||
func collapse() -> void:
|
||||
_expanded = false
|
||||
visible = false
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
for child: Node in _item_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
var conversations: Array[Dictionary] = _get_conversations()
|
||||
var active_id: String = _get_active_id()
|
||||
|
||||
if conversations.is_empty():
|
||||
var empty_label := Label.new()
|
||||
empty_label.text = "No conversations yet"
|
||||
empty_label.add_theme_color_override("font_color", TEXT_MUTED)
|
||||
empty_label.add_theme_font_size_override("font_size", 11)
|
||||
_item_container.add_child(empty_label)
|
||||
return
|
||||
|
||||
var count: int = 0
|
||||
for conv: Dictionary in conversations:
|
||||
if count >= MAX_VISIBLE:
|
||||
break
|
||||
var id: String = conv.get("id", "")
|
||||
var title: String = conv.get("title", "Untitled")
|
||||
var msg_count: int = conv.get("message_count", 0)
|
||||
var is_active: bool = id == active_id
|
||||
|
||||
_item_container.add_child(_build_item(id, title, msg_count, is_active))
|
||||
count += 1
|
||||
|
||||
|
||||
func _build_item(
|
||||
id: String,
|
||||
title: String,
|
||||
msg_count: int,
|
||||
is_active: bool,
|
||||
) -> Control:
|
||||
var btn := Button.new()
|
||||
btn.flat = true
|
||||
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
btn.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
btn.custom_minimum_size.y = 32
|
||||
|
||||
var display_title := title if not title.is_empty() else "New conversation"
|
||||
var suffix := " (%d)" % msg_count if msg_count > 0 else ""
|
||||
btn.text = display_title + suffix
|
||||
|
||||
if is_active:
|
||||
btn.add_theme_color_override("font_color", MIKU_TEAL)
|
||||
btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
|
||||
else:
|
||||
btn.add_theme_color_override("font_color", TEXT_PRIMARY)
|
||||
btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
|
||||
|
||||
btn.add_theme_font_size_override("font_size", 12)
|
||||
|
||||
var hover_style := StyleBoxFlat.new()
|
||||
hover_style.bg_color = BG_HOVER
|
||||
hover_style.set_corner_radius_all(4)
|
||||
btn.add_theme_stylebox_override("hover", hover_style)
|
||||
|
||||
var normal_style := StyleBoxEmpty.new()
|
||||
btn.add_theme_stylebox_override("normal", normal_style)
|
||||
btn.add_theme_stylebox_override("pressed", hover_style)
|
||||
btn.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||||
|
||||
if not is_active:
|
||||
btn.pressed.connect(_on_item_pressed.bind(id))
|
||||
|
||||
return btn
|
||||
|
||||
|
||||
func _on_item_pressed(id: String) -> void:
|
||||
EventBus.conversation_switch_requested.emit(id)
|
||||
collapse()
|
||||
|
||||
|
||||
func _on_conversation_changed(_id: String) -> void:
|
||||
if _expanded:
|
||||
refresh()
|
||||
|
||||
|
||||
func _get_conversations() -> Array[Dictionary]:
|
||||
var index: Dictionary = AppState.get_section("conversations")
|
||||
var list: Array[Dictionary] = []
|
||||
for entry: Dictionary in index.get("list", []):
|
||||
list.append(entry)
|
||||
return list
|
||||
|
||||
|
||||
func _get_active_id() -> String:
|
||||
var index: Dictionary = AppState.get_section("conversations")
|
||||
return index.get("active_id", "")
|
||||
1
shared/godot/chat/conversation_list.gd.uid
Normal file
1
shared/godot/chat/conversation_list.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://21napfou80ww
|
||||
Loading…
Add table
Reference in a new issue