# UI界面设计学习 - client/ui/app.py ## 📋 模块概述 `client/ui/app.py` 实现了Chat-Room项目的TUI(Terminal User Interface)界面,基于Textual库构建现代化的终端用户界面。这是用户与聊天室交互的主要入口。 ## 🎯 TUI界面设计原理 ### 为什么选择TUI? **TUI的优势**: ```mermaid graph LR A[TUI优势] --> B[跨平台兼容] A --> C[资源占用低] A --> D[部署简单] A --> E[开发效率高] B --> B1[Windows/Linux/macOS] C --> C1[内存占用小] C --> C2[CPU使用低] D --> D1[无需GUI环境] D --> D2[SSH远程访问] E --> E1[快速原型开发] E --> E2[专注功能实现] ``` **与GUI的对比**: - **开发复杂度**:TUI < GUI - **资源消耗**:TUI < GUI - **部署要求**:TUI < GUI - **用户体验**:TUI < GUI(但对开发者友好) ### Textual框架特点 ```python # Textual的核心概念 from textual.app import App from textual.widgets import Header, Footer, Input, RichLog from textual.containers import Container, Horizontal class ChatApp(App): """基于Textual的聊天应用""" # CSS样式文件 CSS_PATH = "themes/default.css" # 应用标题 TITLE = "Chat-Room 聊天室" # 组件组合 def compose(self) -> ComposeResult: yield Header() yield Container(...) yield Footer() ``` **Textual特点**: - **声明式UI**:类似React的组件化设计 - **CSS样式**:支持CSS样式定制 - **响应式布局**:自动适应终端大小 - **事件驱动**:基于事件的交互模型 ## 🏗️ 界面架构设计 ### 布局结构 ```mermaid graph TD A[ChatApp
主应用] --> B[Header
标题栏] A --> C[Container
主容器] A --> D[Footer
状态栏] C --> E[chat_area
聊天区域] C --> F[status_area
状态区域] C --> G[input_area
输入区域] E --> E1[Label
区域标题] E --> E2[RichLog
聊天记录] F --> F1[Label
区域标题] F --> F2[ListView
状态列表] G --> G1[Label
区域标题] G --> G2[Input
消息输入框] ``` ### 主应用类设计 ```python class ChatApp(App): """Chat-Room TUI主应用""" CSS_PATH = "themes/default.css" TITLE = "Chat-Room 聊天室" def __init__(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT): """ 初始化应用 Args: host: 服务器地址 port: 服务器端口 """ super().__init__() self.host = host self.port = port # 网络客户端 self.chat_client: Optional[ChatClient] = None self.command_handler: Optional[CommandHandler] = None # UI组件引用 self.chat_log: Optional[RichLog] = None self.message_input: Optional[Input] = None self.status_list: Optional[ListView] = None # 应用状态 self.is_connected = False self.is_logged_in = False self.login_mode = False self.register_mode = False self.login_step = 0 self.temp_username = "" # 消息历史 self.history_messages = [] self.current_chat_group_id = None ``` **设计特点**: - **状态管理**:清晰的应用状态跟踪 - **组件引用**:保存UI组件的引用便于操作 - **网络集成**:集成网络客户端和命令处理器 - **模式切换**:支持登录、注册等不同模式 ## 🎨 界面布局实现 ### 组件组合方法 ```python def compose(self) -> ComposeResult: """构建UI布局""" yield Header() # 聊天区域 with Container(id="chat_area"): yield Label("聊天记录", classes="area_title") yield RichLog(id="chat_log", highlight=True, markup=True) # 状态区域 with Container(id="status_area"): yield Label("状态信息", classes="area_title") yield ListView(id="status_list") # 输入区域 with Container(id="input_area"): yield Label("消息输入 (输入 /help 查看命令)", classes="area_title") yield Input( placeholder="输入消息或命令...", id="message_input" ) yield Footer() ``` **布局特点**: - **容器组织**:使用Container组织相关组件 - **ID标识**:为组件设置ID便于查询和操作 - **语义化**:清晰的区域划分和标题 - **交互提示**:提供用户操作提示 ### CSS样式定制 ```css /* themes/default.css */ /* 主容器布局 */ #chat_area { width: 70%; height: 80%; border: solid $primary; margin: 1; } #status_area { width: 30%; height: 80%; border: solid $secondary; margin: 1; } #input_area { width: 100%; height: 20%; border: solid $accent; margin: 1; } /* 区域标题样式 */ .area_title { background: $primary; color: $text; text-align: center; text-style: bold; } /* 聊天记录样式 */ #chat_log { scrollbar-background: $surface; scrollbar-color: $primary; scrollbar-corner-color: $surface; } /* 输入框样式 */ #message_input { border: solid $accent; } #message_input:focus { border: solid $warning; } ``` **样式特点**: - **响应式布局**:使用百分比宽度适应不同终端大小 - **颜色主题**:使用变量定义颜色方案 - **交互反馈**:焦点状态的视觉反馈 - **滚动条定制**:自定义滚动条样式 ## 🔄 事件处理系统 ### 应用生命周期 ```python def on_mount(self) -> None: """应用挂载时的初始化""" # 获取组件引用 self.chat_log = self.query_one("#chat_log", RichLog) self.message_input = self.query_one("#message_input", Input) self.status_list = self.query_one("#status_list", ListView) # 设置焦点到输入框 self.message_input.focus() # 显示欢迎信息 self.add_system_message("欢迎使用Chat-Room聊天室!") self.add_system_message("请使用 /login 登录或 /signin 注册") # 尝试连接服务器 self.connect_to_server() def on_ready(self) -> None: """应用准备就绪""" self.logger.info("TUI应用已启动") # 更新状态显示 self.update_status_display() def on_unmount(self) -> None: """应用卸载时的清理""" if self.chat_client: self.chat_client.disconnect() self.logger.info("TUI应用已关闭") ``` ### 输入事件处理 ```python def on_input_submitted(self, event: Input.Submitted) -> None: """处理输入提交事件""" if event.input.id != "message_input": return user_input = event.value.strip() if not user_input: return # 清空输入框 self.message_input.value = "" # 显示用户输入 self.add_user_input(user_input) # 处理不同模式的输入 if self.login_mode: self.handle_login_input(user_input) elif self.register_mode: self.handle_register_input(user_input) else: self.handle_normal_input(user_input) def handle_normal_input(self, user_input: str): """处理正常模式的输入""" if user_input.startswith('/'): # 命令处理 self.handle_command(user_input) else: # 普通消息 self.send_chat_message(user_input) def handle_command(self, command_input: str): """处理命令输入""" if not self.command_handler: self.add_error_message("命令处理器未初始化") return try: success, message = self.command_handler.handle_command(command_input) if success: if message: self.add_system_message(message) else: self.add_error_message(message or "命令执行失败") except Exception as e: self.add_error_message(f"命令处理错误: {e}") ``` ### 键盘事件处理 ```python def on_key(self, event: events.Key) -> None: """处理键盘事件""" # Ctrl+C 退出应用 if event.key == "ctrl+c": self.exit() # Ctrl+L 清屏 elif event.key == "ctrl+l": self.clear_chat_log() # Ctrl+R 重连 elif event.key == "ctrl+r": self.reconnect_to_server() # F1 显示帮助 elif event.key == "f1": self.show_help() # ESC 取消当前模式 elif event.key == "escape": self.cancel_current_mode() def cancel_current_mode(self): """取消当前模式""" if self.login_mode: self.login_mode = False self.login_step = 0 self.temp_username = "" self.add_system_message("已取消登录") elif self.register_mode: self.register_mode = False self.login_step = 0 self.temp_username = "" self.add_system_message("已取消注册") # 更新输入提示 self.update_input_placeholder() ``` ## 💬 消息显示系统 ### 消息格式化 ```python def add_chat_message(self, message: ChatMessage): """添加聊天消息到显示区域""" # 格式化时间 timestamp = datetime.fromtimestamp(message.timestamp) time_str = timestamp.strftime(DISPLAY_TIME_FORMAT) # 格式化发送者 sender = message.sender_username if sender == AI_USERNAME: sender_style = "[bold blue]🤖 AI助手[/bold blue]" elif sender == self.current_username: sender_style = f"[bold green]{sender}[/bold green]" else: sender_style = f"[bold cyan]{sender}[/bold cyan]" # 格式化消息内容 content = self.format_message_content(message.content) # 构建完整消息 formatted_message = f"[dim]{time_str}[/dim] {sender_style}: {content}" # 添加到聊天记录 self.chat_log.write(formatted_message) # 自动滚动到底部 self.chat_log.scroll_end() def format_message_content(self, content: str) -> str: """格式化消息内容""" # 处理URL链接 import re url_pattern = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+' content = re.sub(url_pattern, r'[link=\g<0>]\g<0>[/link]', content) # 处理@用户提及 mention_pattern = r'@(\w+)' content = re.sub(mention_pattern, r'[bold yellow]@\1[/bold yellow]', content) # 处理表情符号(如果需要) # content = self.replace_emoticons(content) return content def add_system_message(self, message: str): """添加系统消息""" timestamp = datetime.now().strftime(DISPLAY_TIME_FORMAT) formatted_message = f"[dim]{timestamp}[/dim] [bold yellow]系统[/bold yellow]: {message}" self.chat_log.write(formatted_message) self.chat_log.scroll_end() def add_error_message(self, message: str): """添加错误消息""" timestamp = datetime.now().strftime(DISPLAY_TIME_FORMAT) formatted_message = f"[dim]{timestamp}[/dim] [bold red]错误[/bold red]: {message}" self.chat_log.write(formatted_message) self.chat_log.scroll_end() def add_user_input(self, user_input: str): """显示用户输入(用于登录等敏感信息)""" if self.login_mode and self.login_step == 1: # 密码输入不显示实际内容 display_input = "*" * len(user_input) else: display_input = user_input timestamp = datetime.now().strftime(DISPLAY_TIME_FORMAT) formatted_message = f"[dim]{timestamp}[/dim] [bold green]你[/bold green]: {display_input}" self.chat_log.write(formatted_message) self.chat_log.scroll_end() ``` ### 状态信息显示 ```python def update_status_display(self): """更新状态显示区域""" # 清空现有状态 self.status_list.clear() # 连接状态 if self.is_connected: connection_status = "[green]✅ 已连接[/green]" else: connection_status = "[red]❌ 未连接[/red]" self.status_list.append(ListItem(Label(f"连接状态: {connection_status}"))) # 用户状态 if self.is_logged_in and self.current_username: user_status = f"[blue]👤 {self.current_username}[/blue]" self.status_list.append(ListItem(Label(f"当前用户: {user_status}"))) # 聊天组状态 if self.current_chat_group: chat_status = f"[cyan]💬 {self.current_chat_group}[/cyan]" self.status_list.append(ListItem(Label(f"当前聊天组: {chat_status}"))) # 在线用户数(如果有) if hasattr(self, 'online_user_count'): self.status_list.append(ListItem(Label(f"在线用户: [yellow]{self.online_user_count}[/yellow]"))) # 添加分隔线 self.status_list.append(ListItem(Label("─" * 20))) # 快捷键提示 shortcuts = [ "F1: 帮助", "Ctrl+L: 清屏", "Ctrl+R: 重连", "Ctrl+C: 退出", "ESC: 取消" ] for shortcut in shortcuts: self.status_list.append(ListItem(Label(f"[dim]{shortcut}[/dim]"))) def update_input_placeholder(self): """更新输入框提示文本""" if self.login_mode: if self.login_step == 0: placeholder = "请输入用户名..." else: placeholder = "请输入密码..." elif self.register_mode: if self.login_step == 0: placeholder = "请输入新用户名..." else: placeholder = "请输入密码..." else: placeholder = "输入消息或命令..." self.message_input.placeholder = placeholder ``` ## 🔧 网络集成 ### 客户端连接管理 ```python def connect_to_server(self): """连接到服务器""" try: self.add_system_message(f"正在连接服务器 {self.host}:{self.port}...") # 创建网络客户端 self.chat_client = ChatClient(self.host, self.port) # 注册消息处理器 self.setup_message_handlers() # 连接服务器 if self.chat_client.connect(): self.is_connected = True self.add_system_message("✅ 服务器连接成功") # 创建命令处理器 self.command_handler = CommandHandler(self.chat_client) else: self.add_error_message("❌ 服务器连接失败") except Exception as e: self.add_error_message(f"连接错误: {e}") finally: self.update_status_display() def setup_message_handlers(self): """设置消息处理器""" if not self.chat_client: return # 注册各种消息类型的处理器 self.chat_client.register_message_handler( MessageType.LOGIN_RESPONSE.value, self.handle_login_response ) self.chat_client.register_message_handler( MessageType.CHAT_MESSAGE.value, self.handle_chat_message ) self.chat_client.register_message_handler( MessageType.ERROR_MESSAGE.value, self.handle_error_message ) # 设置默认处理器 self.chat_client.set_default_message_handler(self.handle_unknown_message) def handle_login_response(self, message: LoginResponse): """处理登录响应""" if message.success: self.is_logged_in = True self.current_username = message.username self.add_system_message(f"✅ 登录成功,欢迎 {message.username}!") # 退出登录模式 self.login_mode = False self.login_step = 0 else: self.add_error_message(f"❌ 登录失败: {message.message}") # 重置登录状态 self.login_mode = False self.login_step = 0 self.temp_username = "" self.update_status_display() self.update_input_placeholder() def handle_chat_message(self, message: ChatMessage): """处理聊天消息""" self.add_chat_message(message) def handle_error_message(self, message: ErrorMessage): """处理错误消息""" self.add_error_message(f"服务器错误: {message.error_message}") ``` ## 💡 学习要点 ### TUI开发技巧 1. **组件化设计**:将复杂界面拆分为独立组件 2. **状态管理**:清晰的应用状态跟踪和更新 3. **事件驱动**:基于事件的用户交互处理 4. **样式定制**:使用CSS样式提升界面美观度 ### Textual框架特性 1. **声明式UI**:类似现代Web框架的组件组合 2. **响应式布局**:自动适应终端大小变化 3. **Rich集成**:强大的文本格式化和样式支持 4. **异步支持**:原生支持异步操作 ### 用户体验设计 1. **即时反馈**:用户操作的即时视觉反馈 2. **错误处理**:友好的错误信息显示 3. **快捷键支持**:提高操作效率 4. **状态提示**:清晰的应用状态显示 ## 🤔 思考题 1. **如何优化TUI界面的响应性?** - 异步消息处理 - 界面更新优化 - 减少重绘次数 2. **如何提升TUI的用户体验?** - 键盘快捷键 - 智能补全 - 上下文帮助 3. **如何处理不同终端的兼容性?** - 颜色支持检测 - 字符编码处理 - 终端大小适配 --- **下一步**:学习命令系统 → [command-system.md](./command-system.md)