From 8087907b4b942c7d8c9296c3fd0e1f08d5d88694 Mon Sep 17 00:00:00 2001 From: Kyren223 Date: Thu, 5 Dec 2024 14:11:28 +0200 Subject: Refactored how the core UI looks --- internal/client/ui/core/chat/chat.go | 37 +++-- internal/client/ui/core/core.go | 55 ++++--- .../client/ui/core/frequencylist/frequencylist.go | 164 ++++++++++++++++++++ internal/client/ui/core/network/network.go | 170 --------------------- internal/client/ui/viminput/linedecorations.go | 4 +- internal/client/ui/viminput/viminput.go | 38 ++++- 6 files changed, 258 insertions(+), 210 deletions(-) create mode 100644 internal/client/ui/core/frequencylist/frequencylist.go delete mode 100644 internal/client/ui/core/network/network.go (limited to 'internal/client') diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go index b3597a8..04eedb7 100644 --- a/internal/client/ui/core/chat/chat.go +++ b/internal/client/ui/core/chat/chat.go @@ -1,9 +1,6 @@ package chat import ( - "log" - - "github.com/charmbracelet/bubbles/textarea" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -12,31 +9,34 @@ import ( ) type Model struct { - vi viminput.Model + vi viminput.Model + focus bool } func New() Model { vi := viminput.New(30, 3) vi.Placeholder = "Send a message..." vi.PlaceholderStyle = lipgloss.NewStyle().Foreground(colors.Gray) - vi.LineDecoration = func(lnum int, line string, cursorLnum int) string { - return "┃ " + vi.LineDecoration = func(lnum int, m viminput.Model) string { + lineNumberDecor := viminput.LineNumberDecoration(lipgloss.NewStyle()) + lineNumber := lineNumberDecor(lnum, m) + return lineNumber + " ┃ " } - // vi.ShowLineNumbers = false + + vi.SetLines(viminput.Line("test"), viminput.Line("best")) + // ta.Cursor.SetChar() // vi.CharLimit = 280 // vi.Focus() - log.Println("Placeholder:", vi.Placeholder) - return Model{ - vi: vi, + vi: vi, } } func (m Model) Init() tea.Cmd { - return tea.Batch(textarea.Blink, nil) + return nil } func (m Model) View() string { @@ -44,5 +44,18 @@ func (m Model) View() string { } func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { - return m, nil + if !m.focus { + return m, nil + } + var cmd tea.Cmd + m.vi, cmd = m.vi.Update(msg) + return m, cmd +} + +func (m *Model) Focus() { + m.focus = true +} + +func (m *Model) Blur() { + m.focus = false } diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index b4390e0..dd29106 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -13,8 +13,9 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/kyren223/eko/internal/client/gateway" "github.com/kyren223/eko/internal/client/ui" + "github.com/kyren223/eko/internal/client/ui/core/chat" "github.com/kyren223/eko/internal/client/ui/core/frequencycreation" - "github.com/kyren223/eko/internal/client/ui/core/network" + "github.com/kyren223/eko/internal/client/ui/core/frequencylist" "github.com/kyren223/eko/internal/client/ui/core/networkcreation" "github.com/kyren223/eko/internal/client/ui/core/networklist" "github.com/kyren223/eko/internal/client/ui/core/state" @@ -35,7 +36,8 @@ var ( const ( FocusNetworkList = iota - FocusNetwork + FocusFrequencyList + FocusChat FocusMax ) @@ -52,7 +54,8 @@ type Model struct { networkCreationPopup *networkcreation.Model frequencyCreationPopup *frequencycreation.Model networkList networklist.Model - network network.Model + frequencyList frequencylist.Model + chat chat.Model focus int } @@ -68,7 +71,8 @@ func New(privKey ed25519.PrivateKey, name string) Model { networkCreationPopup: nil, frequencyCreationPopup: nil, networkList: networklist.New(), - network: network.New(), + frequencyList: frequencylist.New(), + chat: chat.New(), focus: FocusNetworkList, } m.move(0) // Update focus @@ -86,8 +90,9 @@ func (m Model) View() string { } networkList := m.networkList.View() - network := m.network.View() - result := lipgloss.JoinHorizontal(lipgloss.Top, networkList, network) + frequencyList := m.frequencyList.View() + chat := m.chat.View() + result := lipgloss.JoinHorizontal(lipgloss.Top, networkList, frequencyList, chat) result = lipgloss.Place( ui.Width, ui.Height, @@ -161,6 +166,9 @@ func (m *Model) updateNotConnected(msg tea.Msg) tea.Cmd { func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { switch msg := msg.(type) { + case ui.QuitMsg: + gateway.Disconnect() + case gateway.ConnectionLost: m.connected = false m.timeout = initialTimeout @@ -205,9 +213,6 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { network.Frequencies = frequencies } - case ui.QuitMsg: - gateway.Disconnect() - case tea.KeyMsg: key := msg.Type switch key { @@ -218,7 +223,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { popup := networkcreation.New() m.networkCreationPopup = &popup } - case FocusNetwork: + case FocusFrequencyList: if m.frequencyCreationPopup == nil { index := m.networkList.Index() network := state.State.Networks[index] @@ -260,12 +265,15 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { return cmd } - if msg.String() == "L" && m.focus == FocusNetworkList { - m.move(1) - } else if msg.String() == "H" && m.focus == FocusNetwork { - m.move(-1) + left := msg.String() == "H" + right := msg.String() == "L" + direction := 0 + if left { + direction = -1 + } else if right { + direction = 1 } - + m.move(direction) } } @@ -278,9 +286,11 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { switch m.focus { case FocusNetworkList: m.networkList, cmd = m.networkList.Update(msg) - m.network.Set(m.networkList.Index()) - case FocusNetwork: - m.network, cmd = m.network.Update(msg) + m.frequencyList.Set(m.networkList.Index()) + case FocusFrequencyList: + m.frequencyList, cmd = m.frequencyList.Update(msg) + case FocusChat: + m.chat, cmd = m.chat.Update(msg) } return cmd } @@ -299,12 +309,15 @@ func (m *Model) move(direction int) { m.focus = max(0, min(FocusMax-1, focus)) m.networkList.Blur() - m.network.Blur() + m.frequencyList.Blur() + m.chat.Blur() switch m.focus { case FocusNetworkList: m.networkList.Focus() - case FocusNetwork: - m.network.Focus() + case FocusFrequencyList: + m.frequencyList.Focus() + case FocusChat: + m.chat.Focus() default: assert.Never("missing switch statement field in move", "focus", m.focus) } diff --git a/internal/client/ui/core/frequencylist/frequencylist.go b/internal/client/ui/core/frequencylist/frequencylist.go new file mode 100644 index 0000000..3b7f800 --- /dev/null +++ b/internal/client/ui/core/frequencylist/frequencylist.go @@ -0,0 +1,164 @@ +package frequencylist + +import ( + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/kyren223/eko/internal/client/gateway" + "github.com/kyren223/eko/internal/client/ui" + "github.com/kyren223/eko/internal/client/ui/colors" + "github.com/kyren223/eko/internal/client/ui/core/state" + "github.com/kyren223/eko/internal/packet" +) + +var ( + width = 24 + sepStyle = lipgloss.NewStyle().Width(0). + Border(lipgloss.ThickBorder(), false, true, false, false) + nameStyle = lipgloss.NewStyle(). + Margin(0, 0, 1).Padding(1).Width(width).Align(lipgloss.Center). + Border(lipgloss.ThickBorder(), false, false, true) + margin = 3 + frequencyStyle = lipgloss.NewStyle(). + Margin(0, margin).Padding(0, 1).Width(width - (margin * 2)). + Align(lipgloss.Left) + symbol = "# " +) + +type Model struct { + history []func(m *Model) + networkIndex int + index int + focus bool +} + +func New() Model { + return Model{ + focus: false, + index: 0, + } +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) View() string { + if m.Network() == nil { + return "" + } + + var builder strings.Builder + + bg := lipgloss.Color(m.Network().BgHexColor) + fg := lipgloss.Color(m.Network().FgHexColor) + nameStyle := nameStyle.Background(bg).Foreground(fg) + if m.focus { + nameStyle = nameStyle.BorderForeground(colors.Focus) + } + builder.WriteString(nameStyle.Render(m.Network().Name)) + + builder.WriteString("\n") + for i, frequency := range m.Network().Frequencies { + frequencyStyle := frequencyStyle.Foreground(lipgloss.Color(frequency.HexColor)) + if m.index == i { + frequencyStyle = frequencyStyle.Background(colors.BackgroundHighlight) + } + builder.WriteString(frequencyStyle.Render(symbol + frequency.Name)) + builder.WriteString("\n") + } + + sidebar := builder.String() + sep := sepStyle.Height(ui.Height) + if m.focus { + sep = sep.BorderForeground(colors.Focus) + } + result := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, sep.String()) + + return result +} + +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + switch msg := msg.(type) { + case *packet.SwapFrequencies: + // Pop first by shifting to the left + copy(m.history, m.history[1:]) + m.history = m.history[:len(m.history)-1] + case *packet.Error: + if msg.PktType == packet.PacketSwapFrequencies { + // Server failed, revert! + undo := m.history[len(m.history)-1] + m.history = m.history[:len(m.history)-1] + undo(&m) + } + + case tea.KeyMsg: + key := msg.String() + switch key { + case "K": + if 0 < m.index { + return m.Swap(-1) + } + case "J": + if m.index < m.FrequenciesLength()-1 { + return m.Swap(1) + } + case "k": + m.index = max(0, m.index-1) + case "j": + m.index = min(m.FrequenciesLength()-1, m.index+1) + + case "ctrl+d": + frequencyId := m.Network().Frequencies[m.index].ID + return m, gateway.Send(&packet.DeleteFrequency{ + Frequency: frequencyId, + }) + + } + } + + return m, nil +} + +func (m *Model) Focus() { + m.focus = true +} + +func (m *Model) Blur() { + m.focus = false +} + +func (m Model) Swap(dir int) (Model, tea.Cmd) { + cmd := gateway.Send(&packet.SwapFrequencies{ + Network: m.Network().ID, + Pos1: m.index, + Pos2: m.index + dir, + }) + tmp := m.Network().Frequencies[m.index] + m.Network().Frequencies[m.index] = m.Network().Frequencies[m.index+dir] + m.Network().Frequencies[m.index+dir] = tmp + m.index += dir + m.history = append(m.history, func(m *Model) { + m.index -= dir + tmp := m.Network().Frequencies[m.index] + m.Network().Frequencies[m.index] = m.Network().Frequencies[m.index+dir] + m.Network().Frequencies[m.index+dir] = tmp + }) + return m, cmd +} + +func (m *Model) Set(index int) { + m.networkIndex = index +} + +func (m Model) FrequenciesLength() int { + return len(m.Network().Frequencies) +} + +func (m Model) Network() *packet.FullNetwork { + if m.networkIndex < 0 || m.networkIndex > len(state.State.Networks)-1 { + return nil + } + return &state.State.Networks[m.networkIndex] +} diff --git a/internal/client/ui/core/network/network.go b/internal/client/ui/core/network/network.go deleted file mode 100644 index 3c24350..0000000 --- a/internal/client/ui/core/network/network.go +++ /dev/null @@ -1,170 +0,0 @@ -package network - -import ( - "strings" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/kyren223/eko/internal/client/gateway" - "github.com/kyren223/eko/internal/client/ui" - "github.com/kyren223/eko/internal/client/ui/colors" - "github.com/kyren223/eko/internal/client/ui/core/chat" - "github.com/kyren223/eko/internal/client/ui/core/state" - "github.com/kyren223/eko/internal/packet" -) - -var ( - width = 24 - sepStyle = lipgloss.NewStyle().Width(0). - Border(lipgloss.ThickBorder(), false, true, false, false) - nameStyle = lipgloss.NewStyle(). - Margin(0, 0, 1).Padding(1).Width(width).Align(lipgloss.Center). - Border(lipgloss.ThickBorder(), false, false, true) - margin = 3 - frequencyStyle = lipgloss.NewStyle(). - Margin(0, margin).Padding(0, 1).Width(width - (margin * 2)). - Align(lipgloss.Left) - symbol = "# " -) - -type Model struct { - history []func(m *Model) - networkIndex int - index int - focus bool - chat chat.Model -} - -func New() Model { - chat := chat.New() - - return Model{ - focus: false, - index: 0, - chat: chat, - } -} - -func (m Model) Init() tea.Cmd { - return nil -} - -func (m Model) View() string { - if m.Network() == nil { - return "" - } - - var builder strings.Builder - - bg := lipgloss.Color(m.Network().BgHexColor) - fg := lipgloss.Color(m.Network().FgHexColor) - nameStyle := nameStyle.Background(bg).Foreground(fg) - if m.focus { - nameStyle = nameStyle.BorderForeground(colors.Focus) - } - builder.WriteString(nameStyle.Render(m.Network().Name)) - - builder.WriteString("\n") - for i, frequency := range m.Network().Frequencies { - frequencyStyle := frequencyStyle.Foreground(lipgloss.Color(frequency.HexColor)) - if m.index == i { - frequencyStyle = frequencyStyle.Background(colors.BackgroundHighlight) - } - builder.WriteString(frequencyStyle.Render(symbol + frequency.Name)) - builder.WriteString("\n") - } - - sidebar := builder.String() - sep := sepStyle.Height(ui.Height) - if m.focus { - sep = sep.BorderForeground(colors.Focus) - } - sidebar = lipgloss.JoinHorizontal(lipgloss.Top, sidebar, sep.String()) - - result := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, m.chat.View()) - - return result -} - -func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { - switch msg := msg.(type) { - case *packet.SwapFrequencies: - // Pop first by shifting to the left - copy(m.history, m.history[1:]) - m.history = m.history[:len(m.history)-1] - case *packet.Error: - if msg.PktType == packet.PacketSwapFrequencies { - // Server failed, revert! - undo := m.history[len(m.history)-1] - m.history = m.history[:len(m.history)-1] - undo(&m) - } - - case tea.KeyMsg: - key := msg.String() - switch key { - case "K": - if 0 < m.index { - return m.Swap(-1) - } - case "J": - if m.index < m.FrequenciesLength()-1 { - return m.Swap(1) - } - case "k": - m.index = max(0, m.index-1) - case "j": - m.index = min(m.FrequenciesLength()-1, m.index+1) - - case "ctrl+d": - frequencyId := m.Network().Frequencies[m.index].ID - return m, gateway.Send(&packet.DeleteFrequency{ - Frequency: frequencyId, - }) - - } - } - return m, nil -} - -func (m *Model) Focus() { - m.focus = true -} - -func (m *Model) Blur() { - m.focus = false -} - -func (m Model) Swap(dir int) (Model, tea.Cmd) { - cmd := gateway.Send(&packet.SwapFrequencies{ - Network: m.Network().ID, - Pos1: m.index, - Pos2: m.index + dir, - }) - tmp := m.Network().Frequencies[m.index] - m.Network().Frequencies[m.index] = m.Network().Frequencies[m.index+dir] - m.Network().Frequencies[m.index+dir] = tmp - m.index += dir - m.history = append(m.history, func(m *Model) { - m.index -= dir - tmp := m.Network().Frequencies[m.index] - m.Network().Frequencies[m.index] = m.Network().Frequencies[m.index+dir] - m.Network().Frequencies[m.index+dir] = tmp - }) - return m, cmd -} - -func (m *Model) Set(index int) { - m.networkIndex = index -} - -func (m Model) FrequenciesLength() int { - return len(m.Network().Frequencies) -} - -func (m Model) Network() *packet.FullNetwork { - if m.networkIndex < 0 || m.networkIndex > len(state.State.Networks)-1 { - return nil - } - return &state.State.Networks[m.networkIndex] -} diff --git a/internal/client/ui/viminput/linedecorations.go b/internal/client/ui/viminput/linedecorations.go index 82e51f1..631c0ce 100644 --- a/internal/client/ui/viminput/linedecorations.go +++ b/internal/client/ui/viminput/linedecorations.go @@ -7,11 +7,11 @@ import ( ) func LineNumberDecoration(style lipgloss.Style) LineDecoration { - return func(lnum int, line string, cursorLnum int) string { + return func(lnum int, m Model) string { return style.Render(strconv.FormatInt(int64(lnum), 10)) } } -func EmptyLineDecoration(lnum int, line string, cursorLnum int) string { +func EmptyLineDecoration(lnum int, m Model) string { return "" } diff --git a/internal/client/ui/viminput/viminput.go b/internal/client/ui/viminput/viminput.go index e2b65a8..94c5413 100644 --- a/internal/client/ui/viminput/viminput.go +++ b/internal/client/ui/viminput/viminput.go @@ -1,13 +1,15 @@ package viminput import ( + "strings" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) type ( Keymap struct{} - LineDecoration = func(lnum int, line string, cursorLnum int) string + LineDecoration = func(lnum int, m Model) string ) type Line []byte @@ -21,7 +23,9 @@ type Model struct { Placeholder string LineDecoration LineDecoration - lines []Line + lines []Line + CursorLine int + CursorColumn int width int height int @@ -46,11 +50,23 @@ func (m Model) Init() tea.Cmd { } func (m Model) View() string { - placeholder := m.PlaceholderStyle.Render(m.Placeholder) - lineDecoration := m.LineDecoration(1, "", 1) + var lines []Line + if len(m.lines) == 0 { + placeholder := m.PlaceholderStyle.Render(m.Placeholder) + lines = append(lines, Line(placeholder)) + } else { + lines = m.lines + } - result := lipgloss.JoinHorizontal(lipgloss.Top, lineDecoration, placeholder) + var builder strings.Builder + for i, line := range lines { + lineDecoration := m.LineDecoration(i, m) + builder.WriteString(lineDecoration) + builder.Write(line) + builder.WriteByte('\n') + } + result := builder.String() result = lipgloss.NewStyle().Width(m.width).Height(m.height).Render(result) return result @@ -87,3 +103,15 @@ func (m *Model) Focus() { func (m *Model) Blur() { m.focus = false } + +func (m *Model) SetLines(lines ...Line) { + m.lines = lines +} + +func (m *Model) SetLine(lnum int, line Line) { + m.lines[lnum] = line +} + +func (m *Model) Line(lnum int) Line { + return m.lines[lnum] +} -- cgit v1.3.1