aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/ui/context.go23
-rw-r--r--lib/ui/popover.go62
-rw-r--r--lib/ui/ui.go26
3 files changed, 102 insertions, 9 deletions
diff --git a/lib/ui/context.go b/lib/ui/context.go
index d450fd87..6bdf76ae 100644
--- a/lib/ui/context.go
+++ b/lib/ui/context.go
@@ -10,9 +10,10 @@ import (
// A context allows you to draw in a sub-region of the terminal
type Context struct {
- screen tcell.Screen
- viewport *views.ViewPort
- x, y int
+ screen tcell.Screen
+ viewport *views.ViewPort
+ x, y int
+ onPopover func(*Popover)
}
func (ctx *Context) X() int {
@@ -35,9 +36,9 @@ func (ctx *Context) Height() int {
return height
}
-func NewContext(width, height int, screen tcell.Screen) *Context {
+func NewContext(width, height int, screen tcell.Screen, p func(*Popover)) *Context {
vp := views.NewViewPort(screen, 0, 0, width, height)
- return &Context{screen, vp, 0, 0}
+ return &Context{screen, vp, 0, 0, p}
}
func (ctx *Context) Subcontext(x, y, width, height int) *Context {
@@ -49,7 +50,7 @@ func (ctx *Context) Subcontext(x, y, width, height int) *Context {
panic(fmt.Errorf("Attempted to create context larger than parent"))
}
vp := views.NewViewPort(ctx.viewport, x, y, width, height)
- return &Context{ctx.screen, vp, ctx.x + x, ctx.y + y}
+ return &Context{ctx.screen, vp, ctx.x + x, ctx.y + y, ctx.onPopover}
}
func (ctx *Context) SetCell(x, y int, ch rune, style tcell.Style) {
@@ -113,3 +114,13 @@ func (ctx *Context) SetCursor(x, y int) {
func (ctx *Context) HideCursor() {
ctx.screen.HideCursor()
}
+
+func (ctx *Context) Popover(x, y, width, height int, d Drawable) {
+ ctx.onPopover(&Popover{
+ x: ctx.x + x,
+ y: ctx.y + y,
+ width: width,
+ height: height,
+ content: d,
+ })
+}
diff --git a/lib/ui/popover.go b/lib/ui/popover.go
new file mode 100644
index 00000000..a76f2225
--- /dev/null
+++ b/lib/ui/popover.go
@@ -0,0 +1,62 @@
+package ui
+
+import "github.com/gdamore/tcell"
+
+type Popover struct {
+ x, y, width, height int
+ content Drawable
+}
+
+func (p *Popover) Draw(ctx *Context) {
+ var subcontext *Context
+
+ // trim desired width to fit
+ width := p.width
+ if p.x+p.width > ctx.Width() {
+ width = ctx.Width() - p.x
+ }
+
+ if p.y+p.height+1 < ctx.Height() {
+ // draw below
+ subcontext = ctx.Subcontext(p.x, p.y+1, width, p.height)
+ } else if p.y-p.height >= 0 {
+ // draw above
+ subcontext = ctx.Subcontext(p.x, p.y-p.height, width, p.height)
+ } else {
+ // can't fit entirely above or below, so find the largest available
+ // vertical space and shrink to fit
+ if p.y > ctx.Height()-p.y {
+ // there is more space above than below
+ height := p.y
+ subcontext = ctx.Subcontext(p.x, 0, width, height)
+ } else {
+ // there is more space below than above
+ height := ctx.Height() - p.y
+ subcontext = ctx.Subcontext(p.x, p.y+1, width, height-1)
+ }
+ }
+ p.content.Draw(subcontext)
+}
+
+func (p *Popover) Event(e tcell.Event) bool {
+ if di, ok := p.content.(DrawableInteractive); ok {
+ return di.Event(e)
+ }
+ return false
+}
+
+func (p *Popover) Focus(f bool) {
+ if di, ok := p.content.(DrawableInteractive); ok {
+ di.Focus(f)
+ }
+}
+
+func (p *Popover) Invalidate() {
+ p.content.Invalidate()
+}
+
+func (p *Popover) OnInvalidate(f func(Drawable)) {
+ p.content.OnInvalidate(func(_ Drawable) {
+ f(p)
+ })
+}
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index 01d12dc4..16b176d5 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -11,6 +11,7 @@ type UI struct {
exit atomic.Value // bool
ctx *Context
screen tcell.Screen
+ popover *Popover
tcEvents chan tcell.Event
invalid int32 // access via atomic
@@ -34,11 +35,11 @@ func Initialize(content DrawableInteractiveBeeper) (*UI, error) {
state := UI{
Content: content,
- ctx: NewContext(width, height, screen),
screen: screen,
tcEvents: make(chan tcell.Event, 10),
}
+ state.ctx = NewContext(width, height, screen, state.onPopover)
state.exit.Store(false)
go func() {
@@ -57,6 +58,10 @@ func Initialize(content DrawableInteractiveBeeper) (*UI, error) {
return &state, nil
}
+func (state *UI) onPopover(p *Popover) {
+ state.popover = p
+}
+
func (state *UI) ShouldExit() bool {
return state.exit.Load().(bool)
}
@@ -78,17 +83,32 @@ func (state *UI) Tick() bool {
case *tcell.EventResize:
state.screen.Clear()
width, height := event.Size()
- state.ctx = NewContext(width, height, state.screen)
+ state.ctx = NewContext(width, height, state.screen, state.onPopover)
state.Content.Invalidate()
}
- state.Content.Event(event)
+ // if we have a popover, and it can handle the event, it does so
+ if state.popover == nil || !state.popover.Event(event) {
+ // otherwise, we send the event to the main content
+ state.Content.Event(event)
+ }
more = true
default:
}
wasInvalid := atomic.SwapInt32(&state.invalid, 0)
if wasInvalid != 0 {
+ if state.popover != nil {
+ // if the previous frame had a popover, rerender the entire display
+ state.Content.Invalidate()
+ atomic.StoreInt32(&state.invalid, 0)
+ }
+ // reset popover for the next Draw
+ state.popover = nil
state.Content.Draw(state.ctx)
+ if state.popover != nil {
+ // if the Draw resulted in a popover, draw it
+ state.popover.Draw(state.ctx)
+ }
state.screen.Show()
more = true
}