diff --git a/shared/godot/chat/chat_display.gd b/shared/godot/chat/chat_display.gd index 1becdf0..dce7a23 100644 --- a/shared/godot/chat/chat_display.gd +++ b/shared/godot/chat/chat_display.gd @@ -3,26 +3,9 @@ extends VBoxContainer ## Handles its own scrolling and text selection — no ScrollContainer needed. ## Supports mark_speaking() to underline the currently-playing TTS sentence. -const SELECTION_BG_COLOR := Color("#1A5C56") - -const EMOTION_HEX: Dictionary = { - "happy": "#4ECDC4", - "sad": "#7B9EBD", - "angry": "#D45F5F", - "surprised": "#00E5FF", - "relaxed": "#80CBC4", -} - -class _Msg: - var role: String # "user" | "assistant" | "error" - var emotion: String # assistant only - var parts: Array[String] # sentences (assistant) or [text] (user/error) - - func _init(p_role: String, p_emotion: String = "") -> void: - role = p_role - emotion = p_emotion - parts = [] - +var message_count: int: + get: + return _messages.size() var _rtl: RichTextLabel var _messages: Array[_Msg] = [] @@ -58,11 +41,6 @@ func setup() -> void: add_child(_rtl) -var message_count: int: - get: - return _messages.size() - - func add_user_message(text: String) -> void: var msg := _Msg.new("user") msg.parts.append(text) @@ -140,7 +118,8 @@ func _render() -> void: ) ) "assistant": - var accent_hex: String = EMOTION_HEX.get(msg.emotion, UiTheme.accent.to_html(false)) + var accent_color := UiTheme.emotion_colors.get(msg.emotion, UiTheme.accent) as Color + var accent_hex: String = accent_color.to_html(false) _rtl.append_text( ( "[color=%s]✦ Miku[/color] [color=%s]● %s[/color]\n" @@ -151,11 +130,17 @@ func _render() -> void: var part: String = msg.parts[j] if i == _speaking_msg and j == _speaking_part: _rtl.append_text( - "[u][color=%s]%s[/color][/u]" % [UiTheme.text_primary.to_html(false), _escape(part)] + ( + "[u][color=%s]%s[/color][/u]" + % [UiTheme.text_primary.to_html(false), _escape(part)] + ) ) else: _rtl.append_text( - "[color=%s]%s[/color]" % [UiTheme.text_primary.to_html(false), _escape(part)] + ( + "[color=%s]%s[/color]" + % [UiTheme.text_primary.to_html(false), _escape(part)] + ) ) "error": _rtl.append_text( @@ -185,7 +170,8 @@ func _append_user_block(text: String) -> void: func _append_miku_header(emotion: String) -> void: if _rtl.get_parsed_text().length() > 0: _rtl.append_text("\n\n") - var accent_hex: String = EMOTION_HEX.get(emotion, UiTheme.accent.to_html(false)) + var accent_color := UiTheme.emotion_colors.get(emotion, UiTheme.accent) as Color + var accent_hex: String = accent_color.to_html(false) _rtl.append_text( ( "[color=%s]✦ Miku[/color] [color=%s]● %s[/color]\n" @@ -210,3 +196,14 @@ func _do_scroll() -> void: func _escape(text: String) -> String: return text.replace("[", "[lb]") + + +class _Msg: + var role: String # "user" | "assistant" | "error" + var emotion: String # assistant only + var parts: Array[String] # sentences (assistant) or [text] (user/error) + + func _init(p_role: String, p_emotion: String = "") -> void: + role = p_role + emotion = p_emotion + parts = [] diff --git a/shared/godot/chat/chat_input.gd b/shared/godot/chat/chat_input.gd index b784fa8..25e28f9 100644 --- a/shared/godot/chat/chat_input.gd +++ b/shared/godot/chat/chat_input.gd @@ -1,85 +1,2 @@ -extends PanelContainer -## Chat input bar — text field with send button, Miku-themed. - -signal message_submitted(text: String) - -var _input: LineEdit - - -func setup() -> void: - var style := StyleBoxFlat.new() - style.bg_color = UiTheme.bg_panel - style.corner_radius_bottom_left = 10 - style.corner_radius_bottom_right = 10 - add_theme_stylebox_override("panel", style) - custom_minimum_size.y = 52 - - var margin := MarginContainer.new() - margin.add_theme_constant_override("margin_left", 12) - margin.add_theme_constant_override("margin_right", 12) - margin.add_theme_constant_override("margin_top", 8) - margin.add_theme_constant_override("margin_bottom", 8) - add_child(margin) - - var hbox := HBoxContainer.new() - hbox.add_theme_constant_override("separation", 8) - margin.add_child(hbox) - - _input = LineEdit.new() - _input.size_flags_horizontal = Control.SIZE_EXPAND_FILL - _input.placeholder_text = "Type a message..." - _input.add_theme_color_override("font_color", UiTheme.text_primary) - _input.add_theme_color_override("font_placeholder_color", UiTheme.text_muted) - _input.add_theme_color_override("caret_color", UiTheme.accent) - - var input_normal := StyleBoxFlat.new() - input_normal.bg_color = UiTheme.input_bg - input_normal.set_border_width_all(1) - input_normal.border_color = UiTheme.border - input_normal.set_corner_radius_all(6) - input_normal.content_margin_left = 10 - input_normal.content_margin_right = 10 - input_normal.content_margin_top = 6 - input_normal.content_margin_bottom = 6 - _input.add_theme_stylebox_override("normal", input_normal) - - var input_focus := input_normal.duplicate() as StyleBoxFlat - input_focus.border_color = UiTheme.accent - _input.add_theme_stylebox_override("focus", input_focus) - _input.text_submitted.connect(_on_text_submitted) - hbox.add_child(_input) - - var send_btn := Button.new() - send_btn.text = "→" - send_btn.custom_minimum_size = Vector2(40, 36) - send_btn.add_theme_color_override("font_color", UiTheme.bg_dark) - send_btn.add_theme_font_size_override("font_size", 18) - - var btn_normal := StyleBoxFlat.new() - btn_normal.bg_color = UiTheme.accent - btn_normal.set_corner_radius_all(6) - send_btn.add_theme_stylebox_override("normal", btn_normal) - - var btn_hover := btn_normal.duplicate() as StyleBoxFlat - btn_hover.bg_color = UiTheme.accent_hover() - send_btn.add_theme_stylebox_override("hover", btn_hover) - - var btn_pressed := btn_normal.duplicate() as StyleBoxFlat - btn_pressed.bg_color = UiTheme.accent_press() - send_btn.add_theme_stylebox_override("pressed", btn_pressed) - - send_btn.pressed.connect(_on_send_pressed) - hbox.add_child(send_btn) - - -func _on_text_submitted(text: String) -> void: - text = text.strip_edges() - if text.is_empty(): - return - _input.text = "" - message_submitted.emit(text) - _input.grab_focus() - - -func _on_send_pressed() -> void: - _on_text_submitted(_input.text) +extends "res://addons/godot-ui/chat/input_bar.gd" +## Chat input bar — delegates to godot-ui InputBar component. diff --git a/shared/godot/chat/chat_window.gd b/shared/godot/chat/chat_window.gd index 77fcc82..7539041 100644 --- a/shared/godot/chat/chat_window.gd +++ b/shared/godot/chat/chat_window.gd @@ -46,6 +46,7 @@ func _ready() -> void: min_size = Vector2i(340, 480) size = Vector2i(380, 560) super._ready() + setup() func _build_ui() -> void: @@ -64,7 +65,7 @@ func _build_ui() -> void: root.add_theme_constant_override("separation", 0) bg.add_child(root) - root.add_child(_build_title_bar()) + root.add_child(_build_chat_title_bar()) root.add_child(_build_divider()) _conversation_list = ConversationListScript.new() @@ -94,7 +95,7 @@ func _build_ui() -> void: root.add_child(_input_bar) -func _build_title_bar() -> Control: +func _build_chat_title_bar() -> Control: _status_label = Label.new() _status_label.text = "" _status_label.add_theme_color_override("font_color", UiTheme.text_muted) @@ -120,7 +121,7 @@ func _build_title_bar() -> Control: 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]) + return _build_title_bar("✦ MIKU", [_status_label, list_btn, new_btn]) func replay_messages(messages: Array[Dictionary]) -> void: diff --git a/shared/godot/chat/conversation_list.gd b/shared/godot/chat/conversation_list.gd index 91558ec..e377c0d 100644 --- a/shared/godot/chat/conversation_list.gd +++ b/shared/godot/chat/conversation_list.gd @@ -2,14 +2,6 @@ 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 @@ -22,9 +14,9 @@ func setup() -> void: var wrapper := PanelContainer.new() var style := StyleBoxFlat.new() - style.bg_color = BG_PANEL + style.bg_color = UiTheme.bg_panel style.set_border_width_all(1) - style.border_color = BORDER_COLOR + style.border_color = UiTheme.border style.content_margin_left = 6 style.content_margin_right = 6 style.content_margin_top = 6 @@ -74,7 +66,7 @@ func refresh() -> void: 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_color_override("font_color", UiTheme.text_muted) empty_label.add_theme_font_size_override("font_size", 11) _item_container.add_child(empty_label) return @@ -109,16 +101,16 @@ func _build_item( 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) + btn.add_theme_color_override("font_color", UiTheme.accent) + btn.add_theme_color_override("font_hover_color", UiTheme.accent) else: - btn.add_theme_color_override("font_color", TEXT_PRIMARY) - btn.add_theme_color_override("font_hover_color", MIKU_TEAL) + btn.add_theme_color_override("font_color", UiTheme.text_primary) + btn.add_theme_color_override("font_hover_color", UiTheme.accent) btn.add_theme_font_size_override("font_size", 12) var hover_style := StyleBoxFlat.new() - hover_style.bg_color = BG_HOVER + hover_style.bg_color = UiTheme.item_hover hover_style.set_corner_radius_all(4) btn.add_theme_stylebox_override("hover", hover_style)