Detailed changes
@@ -18,7 +18,8 @@ import (
"github.com/charmbracelet/crush/internal/db"
"github.com/charmbracelet/crush/internal/event"
"github.com/charmbracelet/crush/internal/tui"
- "github.com/charmbracelet/crush/internal/ui"
+ "github.com/charmbracelet/crush/internal/ui/common"
+ ui "github.com/charmbracelet/crush/internal/ui/model"
"github.com/charmbracelet/crush/internal/version"
"github.com/charmbracelet/fang"
"github.com/charmbracelet/lipgloss/v2"
@@ -83,7 +84,8 @@ crush -y
// Set up the TUI.
// ui := tui.New(app)
- ui := ui.New(app)
+ com := common.DefaultCommon(app.Config())
+ ui := ui.New(com, app)
program := tea.NewProgram(
ui,
tea.WithContext(cmd.Context()),
@@ -1,17 +1,20 @@
-package ui
+package common
-import "github.com/charmbracelet/crush/internal/config"
+import (
+ "github.com/charmbracelet/crush/internal/config"
+ "github.com/charmbracelet/crush/internal/ui/styles"
+)
// Common defines common UI options and configurations.
type Common struct {
Config *config.Config
- Styles Styles
+ Styles styles.Styles
}
// DefaultCommon returns the default common UI configurations.
func DefaultCommon(cfg *config.Config) *Common {
return &Common{
Config: cfg,
- Styles: DefaultStyles(),
+ Styles: styles.DefaultStyles(),
}
}
@@ -1,19 +1,20 @@
-package ui
+package dialog
import (
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
+ "github.com/charmbracelet/crush/internal/ui/component"
"github.com/charmbracelet/lipgloss/v2"
)
-// DialogOverlayKeyMap defines key bindings for dialogs.
-type DialogOverlayKeyMap struct {
+// OverlayKeyMap defines key bindings for dialogs.
+type OverlayKeyMap struct {
Close key.Binding
}
-// DefaultDialogOverlayKeyMap returns the default key bindings for dialogs.
-func DefaultDialogOverlayKeyMap() DialogOverlayKeyMap {
- return DialogOverlayKeyMap{
+// DefaultOverlayKeyMap returns the default key bindings for dialogs.
+func DefaultOverlayKeyMap() OverlayKeyMap {
+ return OverlayKeyMap{
Close: key.NewBinding(
key.WithKeys("esc", "alt+esc"),
),
@@ -22,21 +23,21 @@ func DefaultDialogOverlayKeyMap() DialogOverlayKeyMap {
// Dialog is a component that can be displayed on top of the UI.
type Dialog interface {
- Model[Dialog]
+ component.Model[Dialog]
ID() string
}
// Overlay manages multiple dialogs as an overlay.
type Overlay struct {
dialogs []Dialog
- keyMap DialogOverlayKeyMap
+ keyMap OverlayKeyMap
}
-// NewDialogOverlay creates a new [Overlay] instance.
-func NewDialogOverlay(dialogs ...Dialog) *Overlay {
+// NewOverlay creates a new [Overlay] instance.
+func NewOverlay(dialogs ...Dialog) *Overlay {
return &Overlay{
dialogs: dialogs,
- keyMap: DefaultDialogOverlayKeyMap(),
+ keyMap: DefaultOverlayKeyMap(),
}
}
@@ -1,8 +1,9 @@
-package ui
+package dialog
import (
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
+ "github.com/charmbracelet/crush/internal/ui/common"
"github.com/charmbracelet/lipgloss/v2"
)
@@ -46,16 +47,16 @@ func DefaultQuitKeyMap() QuitDialogKeyMap {
}
}
-// QuitDialog represents a confirmation dialog for quitting the application.
-type QuitDialog struct {
- com *Common
+// Quit represents a confirmation dialog for quitting the application.
+type Quit struct {
+ com *common.Common
keyMap QuitDialogKeyMap
selectedNo bool // true if "No" button is selected
}
-// NewQuitDialog creates a new quit confirmation dialog.
-func NewQuitDialog(com *Common) *QuitDialog {
- q := &QuitDialog{
+// NewQuit creates a new quit confirmation dialog.
+func NewQuit(com *common.Common) *Quit {
+ q := &Quit{
com: com,
keyMap: DefaultQuitKeyMap(),
}
@@ -63,12 +64,12 @@ func NewQuitDialog(com *Common) *QuitDialog {
}
// ID implements [Model].
-func (*QuitDialog) ID() string {
+func (*Quit) ID() string {
return "quit"
}
// Update implements [Model].
-func (q *QuitDialog) Update(msg tea.Msg) (Dialog, tea.Cmd) {
+func (q *Quit) Update(msg tea.Msg) (Dialog, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch {
@@ -91,7 +92,7 @@ func (q *QuitDialog) Update(msg tea.Msg) (Dialog, tea.Cmd) {
}
// View implements [Model].
-func (q *QuitDialog) View() string {
+func (q *Quit) View() string {
const question = "Are you sure you want to quit?"
baseStyle := q.com.Styles.Base
yesStyle := q.com.Styles.ButtonSelected
@@ -125,7 +126,7 @@ func (q *QuitDialog) View() string {
}
// ShortHelp implements [help.KeyMap].
-func (q *QuitDialog) ShortHelp() []key.Binding {
+func (q *Quit) ShortHelp() []key.Binding {
return []key.Binding{
q.keyMap.LeftRight,
q.keyMap.EnterSpace,
@@ -133,7 +134,7 @@ func (q *QuitDialog) ShortHelp() []key.Binding {
}
// FullHelp implements [help.KeyMap].
-func (q *QuitDialog) FullHelp() [][]key.Binding {
+func (q *Quit) FullHelp() [][]key.Binding {
return [][]key.Binding{
{q.keyMap.LeftRight, q.keyMap.EnterSpace, q.keyMap.Yes, q.keyMap.No},
{q.keyMap.Tab, q.keyMap.Close},
@@ -1,9 +0,0 @@
-package ui
-
-import tea "github.com/charmbracelet/bubbletea/v2"
-
-// Model represents a common interface for UI components.
-type Model[T any] interface {
- Update(msg tea.Msg) (T, tea.Cmd)
- View() string
-}
@@ -1,4 +1,4 @@
-package ui
+package model
import "github.com/charmbracelet/bubbles/v2/key"
@@ -1,4 +1,4 @@
-package ui
+package model
import (
"image"
@@ -6,6 +6,8 @@ import (
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/crush/internal/app"
+ "github.com/charmbracelet/crush/internal/ui/common"
+ "github.com/charmbracelet/crush/internal/ui/dialog"
"github.com/charmbracelet/lipgloss/v2"
uv "github.com/charmbracelet/ultraviolet"
)
@@ -18,22 +20,21 @@ const (
type UI struct {
app *app.App
- com *Common
+ com *common.Common
width, height int
state uiState
keyMap KeyMap
- styles Styles
- dialog *Overlay
+ dialog *dialog.Overlay
}
-func New(com *Common, app *app.App) *UI {
+func New(com *common.Common, app *app.App) *UI {
return &UI{
app: app,
com: com,
- dialog: NewDialogOverlay(),
+ dialog: dialog.NewOverlay(),
keyMap: DefaultKeyMap(),
}
}
@@ -53,7 +54,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case uiStateMain:
switch {
case key.Matches(msg, m.keyMap.Quit):
- quitDialog := NewQuitDialog(m.com)
+ quitDialog := dialog.NewQuit(m.com)
if !m.dialog.ContainsDialog(quitDialog.ID()) {
m.dialog.AddDialog(quitDialog)
return m, nil
@@ -73,6 +74,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m *UI) View() tea.View {
var v tea.View
+ v.AltScreen = true
// The screen area we're working with
area := image.Rect(0, 0, m.width, m.height)
@@ -88,6 +90,29 @@ func (m *UI) View() tea.View {
)
}
+ mainRect, sideRect := uv.SplitHorizontal(area, uv.Fixed(area.Dx()-40))
+ mainRect, footRect := uv.SplitVertical(mainRect, uv.Fixed(area.Dy()-7))
+
+ layers = append(layers, lipgloss.NewLayer(
+ lipgloss.NewStyle().Width(mainRect.Dx()).
+ Height(mainRect.Dy()).
+ Border(lipgloss.NormalBorder()).
+ Render(" Main View "),
+ ).X(mainRect.Min.X).Y(mainRect.Min.Y),
+ lipgloss.NewLayer(
+ lipgloss.NewStyle().Width(sideRect.Dx()).
+ Height(sideRect.Dy()).
+ Border(lipgloss.NormalBorder()).
+ Render(" Side View "),
+ ).X(sideRect.Min.X).Y(sideRect.Min.Y),
+ lipgloss.NewLayer(
+ lipgloss.NewStyle().Width(footRect.Dx()).
+ Height(footRect.Dy()).
+ Border(lipgloss.NormalBorder()).
+ Render(" Footer View "),
+ ).X(footRect.Min.X).Y(footRect.Min.Y),
+ )
+
v.Layer = lipgloss.NewCanvas(layers...)
return v
@@ -1,4 +1,4 @@
-package ui
+package styles
import (
"github.com/charmbracelet/bubbles/v2/filepicker"