From b35508e623dbc06fe05c7dd05fcca6c169a4e148 Mon Sep 17 00:00:00 2001 From: titor Date: Mon, 6 Apr 2026 05:50:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20textarea=E5=B8=83=E5=B1=80=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20-=20=E5=85=A8=E5=AE=BD/=E8=87=AA=E9=80=82=E5=BA=94?= =?UTF-8?q?=E9=AB=98=E5=BA=A6(=E6=9C=80=E5=A4=9A7=E8=A1=8C)/=E6=B7=B1?= =?UTF-8?q?=E8=89=B2=E8=83=8C=E6=99=AF/=E7=AA=97=E5=8F=A3=E5=B0=BA?= =?UTF-8?q?=E5=AF=B8=E5=93=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 1 + internal/tui/app.go | 92 +++++++++++++++++++++++++++++++++------------ 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/changelog.md b/changelog.md index be29119..702c250 100644 --- a/changelog.md +++ b/changelog.md @@ -63,6 +63,7 @@ **改进内容**: - ✅ 模块7: 多行输入 - textarea组件替换textinput +- ✅ 模块7补充: 布局和样式优化 - 全宽/自适应高度/深色背景 - ⏳ 模块8: 弹出框组件 - 通用modal - ⏳ 模块9: 斜杠命令菜单 - / 命令选择器 - ⏳ 模块10: 翻译结果滚动 - viewport diff --git a/internal/tui/app.go b/internal/tui/app.go index 2d8f6d8..65ced12 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -2,6 +2,7 @@ package tui import ( "context" + "strings" "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbletea" @@ -19,6 +20,8 @@ type model struct { targetLang string langIndex int loading bool + width int + height int } type translateMsg struct { @@ -32,10 +35,6 @@ var ( Bold(true) dividerStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#00D9FF")) - inputStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FAFAFA")). - Background(lipgloss.Color("#1A1A2E")). - Width(50) resultStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#98FB98")). Background(lipgloss.Color("#0D1B2A")) @@ -46,13 +45,10 @@ var ( Foreground(lipgloss.Color("#888888")) statusBarStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#FFFFFF")). - Background(lipgloss.Color("#1F2937")). - Width(60) + Background(lipgloss.Color("#1F2937")) langStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#FBBF24")). Bold(true) - keyStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#60A5FA")) loadingStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#60A5FA")) ) @@ -69,9 +65,12 @@ func NewApp(cfg *config.Config, t *translator.Translator) *tea.Program { ta.Placeholder = "输入要翻译的文本..." ta.Focus() ta.Prompt = "" - ta.SetWidth(50) - ta.SetHeight(5) ta.ShowLineNumbers = false + ta.SetHeight(3) + + ta.FocusedStyle.Base = lipgloss.NewStyle(). + Background(lipgloss.Color("#1A1A2E")). + Foreground(lipgloss.Color("#FAFAFA")) return tea.NewProgram(model{ config: cfg, @@ -89,6 +88,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + m.updateTextAreaWidth() + case translateMsg: m.loading = false if msg.err != nil { @@ -98,6 +102,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.result = msg.result m.errMsg = "" } + m.updateTextAreaHeight() return m, nil case tea.KeyMsg: @@ -114,10 +119,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.errMsg = "" return m, m.doTranslate(text, m.targetLang) - case tea.KeyCtrlJ: - m.textArea, cmd = m.textArea.Update(msg) - return m, cmd - case tea.KeyCtrlC: return m, tea.Quit @@ -138,9 +139,35 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.textArea, cmd = m.textArea.Update(msg) + m.updateTextAreaHeight() return m, cmd } +func (m model) updateTextAreaWidth() { + if m.width > 0 { + margin := 4 + width := m.width - margin + if width < 20 { + width = 60 + } + m.textArea.SetWidth(width) + } +} + +func (m model) updateTextAreaHeight() { + lines := strings.Count(m.textArea.Value(), "\n") + 1 + if lines < 1 { + lines = 1 + } + if lines > 7 { + lines = 7 + } + if lines < 3 { + lines = 3 + } + m.textArea.SetHeight(lines) +} + func (m model) doTranslate(text, toLang string) tea.Cmd { return func() tea.Msg { result, err := m.translator.Translate( @@ -159,34 +186,49 @@ func (m model) doTranslate(text, toLang string) tea.Cmd { } func (m model) View() string { + margin := " " resultBox := m.renderResult() return "\n" + - " " + headerStyle.Render("YOYO翻译") + "\n" + - " " + dividerStyle.Render("─────────────────────") + "\n\n" + - m.textArea.View() + "\n\n" + - resultBox + + margin + headerStyle.Render("YOYO翻译") + "\n" + + margin + dividerStyle.Render(getDivider(m.width-2)) + "\n\n" + + margin + m.textArea.View() + "\n\n" + + margin + resultBox + + margin + dividerStyle.Render(getDivider(m.width-2)) + "\n" + m.renderStatusBar() } +func getDivider(width int) string { + if width < 10 { + width = 40 + } + result := "" + for i := 0; i < width-4; i++ { + result += "─" + } + return result +} + func (m model) renderResult() string { if m.loading { - return " " + loadingStyle.Render("正在翻译...") + "\n\n" + return loadingStyle.Render("正在翻译...") + "\n\n" } if m.errMsg != "" { - return " " + errorStyle.Render("错误: "+m.errMsg) + "\n\n" + return errorStyle.Render("错误: "+m.errMsg) + "\n\n" } if m.result == "" { - return " " + helpStyle.Render("翻译结果将显示在这里...") + "\n\n" + return helpStyle.Render("翻译结果将显示在这里...") + "\n\n" } - return " " + resultStyle.Render(m.result) + "\n\n" + return resultStyle.Render(m.result) + "\n\n" } func (m model) renderStatusBar() string { - divider := dividerStyle.Render("─") + width := m.width - 4 + if width < 30 { + width = 60 + } langInfo := langStyle.Render("目标: " + m.targetLang) hint := helpStyle.Render("按 / 显示命令") - return " " + divider + "\n" + - " " + statusBarStyle.Render(" "+langInfo+" ") + " " + hint + return " " + statusBarStyle.Render(" "+langInfo+" ") + " " + hint }