6c1fa6c3a8faa754723a788d4fdb244b99b06d9a

Author
Christian Rocha <christian@rocha.is>
Committer
Christian Rocha <christian@rocha.is>
Date

Message

Generally "smooth" out style and layout

Diff

This diff is truncated to protect this page.

  1diff --git a/tui/bubble.go b/tui/bubble.go
  2index eca06e297cc1fc372b478a7c709f5b0ea6eba4e2..8c937fa27a88d354a42bf101517b6ce8d9337522 100644
  3--- a/tui/bubble.go
  4+++ b/tui/bubble.go
  5@@ -2,7 +2,6 @@ package tui
  6 
  7 import (
  8 	"fmt"
  9-	"io"
 10 	"smoothie/git"
 11 	"smoothie/tui/bubbles/commits"
 12 	"smoothie/tui/bubbles/repo"
 13@@ -91,7 +90,7 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 14 		switch msg.String() {
 15 		case "q", "ctrl+c":
 16 			return b, tea.Quit
 17-		case "tab":
 18+		case "tab", "shift+tab":
 19 			b.activeBox = (b.activeBox + 1) % 2
 20 		case "h", "left":
 21 			if b.activeBox > 0 {
 22@@ -118,6 +117,7 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 23 				cmds = append(cmds, cmd)
 24 			}
 25 		}
 26+		// XXX: maybe propagate size changes to child bubbles (particularly height)
 27 	case selection.SelectedMsg:
 28 		b.activeBox = 1
 29 		rb := b.repoMenu[msg.Index].bubble
 30@@ -138,48 +138,88 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 31 	return b, tea.Batch(cmds...)
 32 }
 33 
 34-func (b *Bubble) viewForBox(i int, width int, height int) string {
 35-	var ls lipgloss.Style
 36-	if i == b.activeBox {
 37-		ls = activeBoxStyle.Copy()
 38-	} else {
 39-		ls = inactiveBoxStyle.Copy()
 40-	}
 41-	ls.Width(width)
 42-	if height > 0 {
 43-		ls.Height(height).MarginBottom(3)
 44+func (b *Bubble) viewForBox(i int) string {
 45+	box := b.boxes[i]
 46+	isActive := i == b.activeBox
 47+	var s lipgloss.Style
 48+	var menuHeightFix int // TODO: figure out why we need this
 49+	switch box.(type) {
 50+	case *selection.Bubble:
 51+		menuHeightFix = 1
 52+		if isActive {
 53+			s = menuActiveStyle
 54+			break
 55+		}
 56+		s = menuStyle
 57+	case *repo.Bubble:
 58+		if isActive {
 59+			s = contentBoxActiveStyle
 60+		} else {
 61+			s = contentBoxStyle
 62+		}
 63+		const repoWidthFix = 1 // TODO: figure out why we need this
 64+		w := b.width -
 65+			lipgloss.Width(b.viewForBox(0)) -
 66+			appBoxStyle.GetHorizontalFrameSize() -
 67+			s.GetHorizontalFrameSize() + repoWidthFix
 68+		s = s.Copy().Width(w)
 69+	default:
 70+		panic(fmt.Sprintf("unknown box type %T", box))
 71 	}
 72-	return ls.Render(b.boxes[i].View())
 73+	h := b.height -
 74+		lipgloss.Height(b.headerView()) -
 75+		lipgloss.Height(b.footerView()) -
 76+		s.GetVerticalFrameSize() -
 77+		appBoxStyle.GetVerticalFrameSize() +
 78+		menuHeightFix
 79+	return s.Copy().Height(h).Render(box.View())
 80 }
 81 
 82-func (b Bubble) footerView(w io.Writer) {
 83+func (b Bubble) headerView() string {
 84+	w := b.width - appBoxStyle.GetHorizontalFrameSize()
 85+	return headerStyle.Copy().Width(w).Render(b.config.Name)
 86+}
 87+
 88+func (b Bubble) footerView() string {
 89+	w := &strings.Builder{}
 90 	h := []helpEntry{
 91 		{"tab", "section"},
 92 		{"↑/↓", "navigate"},
 93 		{"q", "quit"},
 94 	}
 95+	if _, ok := b.boxes[b.activeBox].(*repo.Bubble); ok {
 96+		h = append(h[:2], helpEntry{"f/b", "pgup/pgdown"}, h[2])
 97+	}
 98 	for i, v := range h {
 99 		fmt.Fprint(w, v)
100 		if i != len(h)-1 {
101 			fmt.Fprint(w, helpDivider)
102 		}
103 	}
104+	return footerStyle.Render(w.String())
105diff --git a/tui/bubbles/repo/bubble.go b/tui/bubbles/repo/bubble.go
106index 8c30c60680e66d7d348a53d81589e46267917b0a..001a9f48b2bbe4ecf2e0d3f2967e1d68f63f5996 100644
107--- a/tui/bubbles/repo/bubble.go
108+++ b/tui/bubbles/repo/bubble.go
109@@ -8,8 +8,11 @@ import (
110 	"github.com/charmbracelet/bubbles/viewport"
111 	tea "github.com/charmbracelet/bubbletea"
112 	"github.com/charmbracelet/glamour"
113+	"github.com/charmbracelet/lipgloss"
114 )
115 
116+const glamourMaxWidth = 120
117+
118 type ErrMsg struct {
119 	Error error
120 }
121@@ -28,21 +31,18 @@ type Bubble struct {
122 }
123 
124 func NewBubble(rs *git.RepoSource, name string, width, wm, height, hm int, tmp interface{}) *Bubble {
125-	return &Bubble{
126+	b := &Bubble{
127 		templateObject: tmp,
128 		repoSource:     rs,
129 		name:           name,
130-		height:         height,
131-		width:          width,
132 		heightMargin:   hm,
133 		widthMargin:    wm,
134 		readmeViewport: &ViewportBubble{
135-			Viewport: &viewport.Model{
136-				Width:  width - wm,
137-				Height: height - hm,
138-			},
139+			Viewport: &viewport.Model{},
140 		},
141 	}
142+	b.SetSize(width, height)
143+	return b
144 }
145 
146 func (b *Bubble) Init() tea.Cmd {
147@@ -53,8 +53,10 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
148 	var cmds []tea.Cmd
149 	switch msg := msg.(type) {
150 	case tea.WindowSizeMsg:
151-		b.readmeViewport.Viewport.Width = msg.Width - b.widthMargin
152-		b.readmeViewport.Viewport.Height = msg.Height - b.heightMargin
153+		b.SetSize(msg.Width, msg.Height)
154+		// XXX: if we find that longer readmes take more than a few
155+		// milliseconds to render we may need to move Glamour rendering into a
156+		// command.
157 		md, err := b.glamourize(b.readme)
158 		if err != nil {
159 			return b, nil
160@@ -63,12 +65,17 @@ func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
161 	}
162 	rv, cmd := b.readmeViewport.Update(msg)
163 	b.readmeViewport = rv.(*ViewportBubble)
164-	if cmd != nil {
165-		cmds = append(cmds, cmd)
166-	}
167+	cmds = append(cmds, cmd)
168 	return b, tea.Batch(cmds...)
169 }
170 
171+func (b *Bubble) SetSize(w, h int) {
172+	b.width = w
173+	b.height = h
174+	b.readmeViewport.Viewport.Width = w - b.widthMargin
175+	b.readmeViewport.Viewport.Height = h - b.heightMargin
176+}
177+
178 func (b *Bubble) GotoTop() {
179 	b.readmeViewport.Viewport.GotoTop()
180 }
181@@ -116,9 +123,14 @@ func (b *Bubble) templatize(mdt string) (string, error) {
182 }
183 
184 func (b *Bubble) glamourize(md string) (string, error) {
185+	// TODO: read gaps in appropriate style to remove the magic number below.
186+	w := b.width - b.widthMargin - 2
187+	if w > glamourMaxWidth {
188+		w = glamourMaxWidth
189+	}
190 	tr, err := glamour.NewTermRenderer(
191 		glamour.WithStandardStyle("dark"),
192-		glamour.WithWordWrap(b.width-b.widthMargin),
193+		glamour.WithWordWrap(w),
194 	)
195 
196 	if err != nil {
197@@ -128,5 +140,13 @@ func (b *Bubble) glamourize(md string) (string, error) {
198 	if err != nil {
199 		return "", err
200 	}
201+	// Enforce a maximum width for cases when glamour lines run long.
202+	//
203+	// TODO: use Reflow's unconditional wrapping to force-wrap long lines. This
204+	// should utlimately happen as a Glamour option.
205+	//
206+	// See:
207+	// https://github.com/muesli/reflow#unconditional-wrapping
208+	mdt = lipgloss.NewStyle().MaxWidth(w).Render(mdt)
209diff --git a/tui/bubbles/selection/bubble.go b/tui/bubbles/selection/bubble.go
210index 69d7c2b981e08d5597bf4ca4d37013bafa283c4b..75736b268d2f9adbb2687a54b180f07553bfad9c 100644
211--- a/tui/bubbles/selection/bubble.go
212+++ b/tui/bubbles/selection/bubble.go
213@@ -18,14 +18,16 @@ type ActiveMsg struct {
214 type Bubble struct {
215 	NormalStyle   lipgloss.Style
216 	SelectedStyle lipgloss.Style
217+	Cursor        string
218 	Items         []string
219 	SelectedItem  int
220 }
221 
222-func NewBubble(items []string) *Bubble {
223+func NewBubble(items []string, normalStyle, selectedStyle lipgloss.Style, cursor string) *Bubble {
224 	return &Bubble{
225 		NormalStyle:   normalStyle,
226 		SelectedStyle: selectedStyle,
227+		Cursor:        cursor,
228 		Items:         items,
229 	}
230 }
231@@ -34,13 +36,17 @@ func (b *Bubble) Init() tea.Cmd {
232 	return nil
233 }
234 
235-func (b *Bubble) View() string {
236+func (b Bubble) View() string {
237 	s := ""
238 	for i, item := range b.Items {
239 		if i == b.SelectedItem {
240-			s += b.SelectedStyle.Render(item) + "\n"
241+			s += b.Cursor
242+			s += b.SelectedStyle.Render(item)
243 		} else {
244-			s += b.NormalStyle.Render(item) + "\n"
245+			s += b.NormalStyle.Render(item)
246+		}
247+		if i < len(b.Items)-1 {
248+			s += "\n"
249 		}
250 	}
251 	return s
252diff --git a/tui/bubbles/selection/style.go b/tui/bubbles/selection/style.go
253deleted file mode 100644
254index 120fc75a70134528e5f05b04b9c339d641b1115b..0000000000000000000000000000000000000000
255--- a/tui/bubbles/selection/style.go
256+++ /dev/null
257@@ -1,11 +0,0 @@
258-package selection
259-
260-import (
261-	"github.com/charmbracelet/lipgloss"
262-)
263-
264-var normalStyle = lipgloss.NewStyle().
265-	Foreground(lipgloss.Color("#707070"))
266-
267-var selectedStyle = lipgloss.NewStyle().
268-	Foreground(lipgloss.Color("#FFFFFF"))
269diff --git a/tui/commands.go b/tui/commands.go
270index 68f54fd6b2765436a897c948087f23babff971ec..a02fc8a1474951108d0fd1fd62f3056385d50a46 100644
271--- a/tui/commands.go
272+++ b/tui/commands.go
273@@ -2,7 +2,6 @@ package tui
274 
275 import (
276 	"fmt"
277-	"smoothie/tui/bubbles/commits"
278 	"smoothie/tui/bubbles/repo"
279 	"smoothie/tui/bubbles/selection"
280 
281@@ -49,7 +48,10 @@ func (b *Bubble) setupCmd() tea.Msg {
282 		if me.Repo == "config" {
283 			tmplConfig = b.config
284 		}
285-		rb := repo.NewBubble(b.repoSource, me.Repo, b.width, boxLeftWidth+12, b.height, 12, tmplConfig)
286+		width := b.width
287+		boxLeftWidth := menuStyle.GetWidth() + menuStyle.GetHorizontalFrameSize()
288+		const heightMargin = 12 // TODO: figure out why this needs to be 12
289+		rb := repo.NewBubble(b.repoSource, me.Repo, width, boxLeftWidth, b.height, heightMargin, tmplConfig)
290 		initCmd := rb.Init()
291 		msg := initCmd()
292 		switch msg := msg.(type) {
293@@ -60,13 +62,15 @@ func (b *Bubble) setupCmd() tea.Msg {
294 		b.repoMenu = append(b.repoMenu, me)
295 		rs = append(rs, me.Name)
296 	}
297-	b.repoSelect = selection.NewBubble(rs)
298+	b.repoSelect = selection.NewBubble(rs, menuItemStyle, selectedMenuItemStyle, menuCursor.String())
299 	b.boxes[0] = b.repoSelect
300-	b.commitsLog = commits.NewBubble(
301-		b.height-verticalPadding-2,
302-		boxRightWidth-horizontalPadding-2,
303-		b.repoSource.GetCommits(200),
304-	)
305+	/*
306+		b.commitsLog = commits.NewBubble(
307+			b.height-verticalPadding-2,
308+			boxRightWidth-horizontalPadding-2,
309+			b.repoSource.GetCommits(200),
310+		)
311+	*/
312 	ir := -1
313 	if b.initialRepo != "" {
314 		for i, me := range b.repoMenu {
315diff --git a/tui/help.go b/tui/help.go
316deleted file mode 100644
317index 19b290aff51536cefc194b6eac3ef9b525824121..0000000000000000000000000000000000000000
318--- a/tui/help.go
319+++ /dev/null
320@@ -1,12 +0,0 @@
321-package tui
322-
323-import "fmt"
324-
325-type helpEntry struct {
326-	key string
327-	val string
328-}
329-
330-func (h helpEntry) String() string {
331-	return fmt.Sprintf("%s %s", helpKeyStyle.Render(h.key), helpValueStyle.Render(h.val))
332-}
333diff --git a/tui/style.go b/tui/style.go
334index d4ff52ae7cf06964c9dac96991f87fad27f4968f..998de114e3e4ae269a8fa4d310161fd5e43d0e9e 100644
335--- a/tui/style.go
336+++ b/tui/style.go
337@@ -4,42 +4,48 @@ import (
338 	"github.com/charmbracelet/lipgloss"
339 )
340 
341-const boxLeftWidth = 25
342-const boxRightWidth = 85
343-const headerHeight = 1
344-const footerHeight = 2
345-const appPadding = 1
346-const boxPadding = 1
347-const viewportHeightConstant = 7 // TODO figure out why this needs to be 7
348-const horizontalPadding = appPadding * 2
349-const verticalPadding = headerHeight + footerHeight + (appPadding * 2)
350-
351-var appBoxStyle = lipgloss.NewStyle().
352-	PaddingLeft(appPadding).
353-	PaddingRight(appPadding)
354-
355-var inactiveBoxStyle = lipgloss.NewStyle().
356-	Foreground(lipgloss.Color("#606060")).
357+var activeBorderColor = lipgloss.Color("243")
358+var inactiveBorderColor = lipgloss.Color("236")
359+
360+var hiddenBorder = lipgloss.Border{
361+	TopLeft:     " ",
362+	Top:         " ",
363+	TopRight:    " ",
364+	BottomLeft:  " ",
365+	Bottom:      " ",
366+	BottomRight: " ",
367+}
368+
369+var appBoxStyle = lipgloss.NewStyle()
370+
371+var menuStyle = lipgloss.NewStyle().
372+	BorderStyle(lipgloss.RoundedBorder()).
373+	BorderForeground(inactiveBorderColor).
374+	Padding(1, 2).
375+	MarginRight(1).
376+	Width(24)
377+
378+var menuActiveStyle = menuStyle.Copy().
379+	BorderStyle(lipgloss.RoundedBorder()).
380+	BorderForeground(activeBorderColor)
381+
382+var contentBoxStyle = lipgloss.NewStyle().
383 	BorderStyle(lipgloss.RoundedBorder()).
384-	BorderForeground(lipgloss.Color("#303030")).
385-	Padding(boxPadding)
386+	BorderForeground(inactiveBorderColor).
387+	PaddingRight(1).
388+	MarginBottom(1)
389 
390-var activeBoxStyle = lipgloss.NewStyle().
391-	Foreground(lipgloss.Color("#FFFFFF")).
392+var contentBoxActiveStyle = contentBoxStyle.Copy().
393 	BorderStyle(lipgloss.RoundedBorder()).
394-	BorderForeground(lipgloss.Color("#714C7B")).
395-	Padding(boxPadding)
396+	BorderForeground(activeBorderColor)
397 
398 var headerStyle = lipgloss.NewStyle().
399-	Foreground(lipgloss.Color("#714C7B")).
400+	Foreground(lipgloss.Color("61")).
401 	Align(lipgloss.Right).
402 	Bold(true)
403 
404-var normalStyle = lipgloss.NewStyle().
405-	Foreground(lipgloss.Color("#FFFFFF"))
406-
407-var errorStyle = lipgloss.NewStyle().
408-	Foreground(lipgloss.Color("#FF00000"))
409+var footerStyle = lipgloss.NewStyle().
410+	MarginTop(1)
411 
412 var helpKeyStyle = lipgloss.NewStyle().
413 	Foreground(lipgloss.Color("241"))
414@@ -47,6 +53,21 @@ var helpKeyStyle = lipgloss.NewStyle().
415 var helpValueStyle = lipgloss.NewStyle().
416 	Foreground(lipgloss.Color("239"))
417 
418+var menuItemStyle = lipgloss.NewStyle().
419+	Foreground(lipgloss.Color("252")).
420+	PaddingLeft(2)
421+
422+var selectedMenuItemStyle = lipgloss.NewStyle().
423+	Foreground(lipgloss.Color("207")).
424+	PaddingLeft(1)
425+
426+var menuCursor = lipgloss.NewStyle().
427+	Foreground(lipgloss.Color("213")).
428+	SetString(">")
429+
430+var errorStyle = lipgloss.NewStyle().
431+	Foreground(lipgloss.Color("#FF00000"))
432+
433 var helpDivider = lipgloss.NewStyle().
434 	Foreground(lipgloss.Color("237")).
435 	SetString(" • ")