v0.1.0 initial commit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/terminal"
|
||||
"github.com/lixenwraith/terminal/tui"
|
||||
)
|
||||
|
||||
// Panel is a modal overlay with a scrollable body. It owns the frame, the
|
||||
// scroll state and the scrollbar; the caller renders rows into the region
|
||||
// Render returns, starting at Scroll.
|
||||
type Panel struct {
|
||||
Title string
|
||||
Hint string
|
||||
Status string // line inside the bottom of the frame, e.g. a full filename
|
||||
W, H int // 0 = 80% of the screen
|
||||
Cursor bool // track a selected row rather than scrolling freely
|
||||
|
||||
Sel int
|
||||
Scroll int
|
||||
Rows int // content rows, set by the caller before Render
|
||||
View int // visible rows, set by Render
|
||||
}
|
||||
|
||||
// Move advances the selection, or the scroll offset when Cursor is unset.
|
||||
func (p *Panel) Move(d int) {
|
||||
if p.Cursor {
|
||||
p.Sel += d
|
||||
} else {
|
||||
p.Scroll += d
|
||||
}
|
||||
p.clamp()
|
||||
}
|
||||
|
||||
// Page moves by n screens.
|
||||
func (p *Panel) Page(n int) { p.Move(n * max(1, p.View)) }
|
||||
|
||||
// Half moves by n half screens.
|
||||
func (p *Panel) Half(n int) { p.Move(n * max(1, p.View/2)) }
|
||||
|
||||
// First jumps to the top.
|
||||
func (p *Panel) First() { p.Sel, p.Scroll = 0, 0 }
|
||||
|
||||
// Last jumps to the bottom.
|
||||
func (p *Panel) Last() {
|
||||
p.Sel, p.Scroll = p.Rows-1, p.Rows
|
||||
p.clamp()
|
||||
}
|
||||
|
||||
// Reset returns the panel to the top, for new content.
|
||||
func (p *Panel) Reset() { p.Sel, p.Scroll = 0, 0 }
|
||||
|
||||
func (p *Panel) clamp() {
|
||||
if p.Rows <= 0 {
|
||||
p.Sel, p.Scroll = 0, 0
|
||||
return
|
||||
}
|
||||
p.Sel = tui.ClampCursor(p.Sel, p.Rows)
|
||||
if p.Cursor {
|
||||
p.Scroll = tui.AdjustScroll(p.Sel, p.Scroll, p.View, p.Rows)
|
||||
}
|
||||
p.Scroll = tui.ClampScroll(p.Scroll, p.View, p.Rows)
|
||||
}
|
||||
|
||||
// Render draws the frame and returns the body region, scrollbar excluded.
|
||||
// A zero-width result means the screen is too small to show the panel.
|
||||
func (p *Panel) Render(root tui.Region, th *Theme) tui.Region {
|
||||
w, h := p.W, p.H
|
||||
if w <= 0 {
|
||||
w = root.W * 80 / 100
|
||||
}
|
||||
if h <= 0 {
|
||||
h = root.H * 80 / 100
|
||||
}
|
||||
w, h = min(w, root.W-4), min(h, root.H-2)
|
||||
if w < 24 || h < 6 {
|
||||
return tui.Region{}
|
||||
}
|
||||
|
||||
t, hint := p.Title, p.Hint
|
||||
if t != "" {
|
||||
t = " " + t + " "
|
||||
}
|
||||
if hint != "" {
|
||||
hint = " " + hint + " "
|
||||
}
|
||||
|
||||
content := tui.Center(root, w, h).Modal(tui.ModalOpts{
|
||||
Title: t, Hint: hint, Border: tui.LineDouble,
|
||||
BorderFg: th.Accent, TitleFg: th.HeaderFg, HintFg: th.HintFg, Bg: th.FocusBg,
|
||||
})
|
||||
|
||||
body := content
|
||||
if p.Status != "" && content.H > 3 {
|
||||
body = content.Sub(0, 0, content.W, content.H-2)
|
||||
content.HLine(content.H-2, tui.LineSingle, th.Border)
|
||||
content.Text(0, content.H-1, tui.Truncate(p.Status, content.W),
|
||||
th.HintFg, th.FocusBg, terminal.AttrDim)
|
||||
}
|
||||
|
||||
p.View = body.H
|
||||
p.clamp()
|
||||
if p.Rows > body.H && body.W > 2 {
|
||||
body.Sub(body.W-1, 0, 1, body.H).ScrollBar(0, p.Scroll, body.H, p.Rows, th.Border)
|
||||
body = body.Sub(0, 0, body.W-1, body.H)
|
||||
}
|
||||
return body
|
||||
}
|
||||
Reference in New Issue
Block a user