aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2018-02-17 16:35:36 -0500
committerDrew DeVault <sir@cmpwn.com>2018-02-17 16:35:36 -0500
commit60b351b78c930110716b0c9db2227e13704f826d (patch)
tree3d5d6f69fe416cfd4032c7d672168c965999ec4d
parent1892d73161a006182d7ef467e2bfc03c11587cb6 (diff)
downloadaerc-60b351b78c930110716b0c9db2227e13704f826d.tar.gz
Polish up grid and add new rendering loop
-rw-r--r--cmd/aerc/main.go50
-rw-r--r--ui/account.go.old (renamed from ui/account.go)0
-rw-r--r--ui/context.go3
-rw-r--r--ui/drawable.go2
-rw-r--r--ui/grid.go70
-rw-r--r--ui/helpers.go41
-rw-r--r--ui/types.go71
-rw-r--r--ui/ui.go91
8 files changed, 126 insertions, 202 deletions
diff --git a/cmd/aerc/main.go b/cmd/aerc/main.go
index 42199789..1d11c5d1 100644
--- a/cmd/aerc/main.go
+++ b/cmd/aerc/main.go
@@ -1,7 +1,6 @@
package main
import (
- "fmt"
"io"
"io/ioutil"
"log"
@@ -9,11 +8,30 @@ import (
"time"
"github.com/mattn/go-isatty"
+ tb "github.com/nsf/termbox-go"
"git.sr.ht/~sircmpwn/aerc2/config"
"git.sr.ht/~sircmpwn/aerc2/ui"
)
+type fill rune
+
+func (f fill) Draw(ctx *ui.Context) {
+ for x := 0; x < ctx.Width(); x += 1 {
+ for y := 0; y < ctx.Height(); y += 1 {
+ ctx.SetCell(x, y, rune(f), tb.ColorDefault, tb.ColorDefault)
+ }
+ }
+}
+
+func (f fill) OnInvalidate(callback func(d ui.Drawable)) {
+ // no-op
+}
+
+func (f fill) Invalidate() {
+ // no-op
+}
+
func main() {
var logOut io.Writer
var logger *log.Logger
@@ -29,20 +47,30 @@ func main() {
if err != nil {
panic(err)
}
- _ui, err := ui.Initialize(conf)
+
+ grid := ui.NewGrid()
+ grid.Rows = []ui.DimSpec{
+ ui.DimSpec{ui.SIZE_EXACT, 4},
+ ui.DimSpec{ui.SIZE_WEIGHT, 1},
+ ui.DimSpec{ui.SIZE_WEIGHT, 1},
+ ui.DimSpec{ui.SIZE_EXACT, 1},
+ }
+ grid.Columns = []ui.DimSpec{
+ ui.DimSpec{ui.SIZE_WEIGHT, 3},
+ ui.DimSpec{ui.SIZE_WEIGHT, 2},
+ }
+ grid.AddChild(fill('★')).At(0, 0).Span(1, 2)
+ grid.AddChild(fill('☆')).At(1, 0).Span(1, 2)
+ grid.AddChild(fill('.')).At(2, 0).Span(1, 2)
+ grid.AddChild(fill('•')).At(2, 1).Span(1, 1)
+ grid.AddChild(fill('+')).At(3, 0).Span(1, 2)
+
+ _ui, err := ui.Initialize(conf, grid)
if err != nil {
panic(err)
}
defer _ui.Close()
- for _, account := range conf.Accounts {
- logger.Printf("Initializing account %s\n", account.Name)
- tab, err := ui.NewAccountTab(&account, log.New(
- logOut, fmt.Sprintf("[%s] ", account.Name), log.LstdFlags))
- if err != nil {
- panic(err)
- }
- _ui.AddTab(tab)
- }
+
for !_ui.Exit {
if !_ui.Tick() {
time.Sleep(100 * time.Millisecond)
diff --git a/ui/account.go b/ui/account.go.old
index 393a47af..393a47af 100644
--- a/ui/account.go
+++ b/ui/account.go.old
diff --git a/ui/context.go b/ui/context.go
index 9f2e2fe5..e7d9ebe0 100644
--- a/ui/context.go
+++ b/ui/context.go
@@ -22,8 +22,7 @@ func (ctx *Context) Height() int {
return ctx.height
}
-func NewContext() *Context {
- width, height := termbox.Size()
+func NewContext(width, height int) *Context {
return &Context{0, 0, width, height}
}
diff --git a/ui/drawable.go b/ui/drawable.go
index a61c0203..ef094519 100644
--- a/ui/drawable.go
+++ b/ui/drawable.go
@@ -5,4 +5,6 @@ type Drawable interface {
Draw(ctx *Context)
// Specifies a function to call when this cell needs to be redrawn
OnInvalidate(callback func(d Drawable))
+ // Invalidates the drawable
+ Invalidate()
}
diff --git a/ui/grid.go b/ui/grid.go
index 2183a557..2091fc5c 100644
--- a/ui/grid.go
+++ b/ui/grid.go
@@ -1,6 +1,9 @@
package ui
-import "fmt"
+import (
+ "fmt"
+ "math"
+)
type Grid struct {
Rows []DimSpec
@@ -42,6 +45,22 @@ type GridCell struct {
invalid bool
}
+func NewGrid() *Grid {
+ return &Grid{invalid: true}
+}
+
+func (cell *GridCell) At(row, col int) *GridCell {
+ cell.Row = row
+ cell.Column = col
+ return cell
+}
+
+func (cell *GridCell) Span(rows, cols int) *GridCell {
+ cell.RowSpan = rows
+ cell.ColSpan = cols
+ return cell
+}
+
func (grid *Grid) Draw(ctx *Context) {
invalid := grid.invalid
if invalid {
@@ -51,17 +70,17 @@ func (grid *Grid) Draw(ctx *Context) {
if !cell.invalid && !invalid {
continue
}
- rows := grid.rowLayout[cell.Row:cell.RowSpan]
- cols := grid.columnLayout[cell.Column:cell.ColSpan]
+ rows := grid.rowLayout[cell.Row : cell.Row+cell.RowSpan]
+ cols := grid.columnLayout[cell.Column : cell.Column+cell.ColSpan]
x := cols[0].Offset
y := rows[0].Offset
width := 0
height := 0
- for _, row := range rows {
- width += row.Size
- }
for _, col := range cols {
- height += col.Size
+ width += col.Size
+ }
+ for _, row := range rows {
+ height += row.Size
}
subctx := ctx.Subcontext(x, y, width, height)
cell.Content.Draw(subctx)
@@ -74,10 +93,12 @@ func (grid *Grid) reflow(ctx *Context) {
flow := func(specs *[]DimSpec, layouts *[]dimLayout, extent int) {
exact := 0
weight := 0
+ nweights := 0
for _, dim := range *specs {
if dim.Strategy == SIZE_EXACT {
exact += dim.Size
} else if dim.Strategy == SIZE_WEIGHT {
+ nweights += 1
weight += dim.Size
}
}
@@ -87,30 +108,49 @@ func (grid *Grid) reflow(ctx *Context) {
if dim.Strategy == SIZE_EXACT {
layout.Size = dim.Size
} else if dim.Strategy == SIZE_WEIGHT {
- size := float64(dim.Size) / float64(weight) * float64(extent)
- layout.Size = int(size)
+ size := float64(dim.Size) / float64(weight)
+ size *= float64(extent - exact)
+ layout.Size = int(math.Floor(size))
}
+ offset += layout.Size
*layouts = append(*layouts, layout)
}
}
- flow(&grid.Rows, &grid.rowLayout, ctx.Width())
- flow(&grid.Columns, &grid.columnLayout, ctx.Height())
+ flow(&grid.Rows, &grid.rowLayout, ctx.Height())
+ flow(&grid.Columns, &grid.columnLayout, ctx.Width())
grid.invalid = false
}
-func (grid *Grid) InvalidateLayout() {
+func (grid *Grid) invalidateLayout() {
grid.invalid = true
+ if grid.onInvalidate != nil {
+ grid.onInvalidate(grid)
+ }
+}
+
+func (grid *Grid) Invalidate() {
+ grid.invalidateLayout()
+ for _, cell := range grid.Cells {
+ cell.Content.Invalidate()
+ }
}
func (grid *Grid) OnInvalidate(onInvalidate func(d Drawable)) {
grid.onInvalidate = onInvalidate
}
-func (grid *Grid) AddChild(cell *GridCell) {
+func (grid *Grid) AddChild(content Drawable) *GridCell {
+ cell := &GridCell{
+ RowSpan: 1,
+ ColSpan: 1,
+ Content: content,
+ invalid: true,
+ }
grid.Cells = append(grid.Cells, cell)
cell.Content.OnInvalidate(grid.cellInvalidated)
cell.invalid = true
- grid.InvalidateLayout()
+ grid.invalidateLayout()
+ return cell
}
func (grid *Grid) RemoveChild(cell *GridCell) {
@@ -120,7 +160,7 @@ func (grid *Grid) RemoveChild(cell *GridCell) {
break
}
}
- grid.InvalidateLayout()
+ grid.invalidateLayout()
}
func (grid *Grid) cellInvalidated(drawable Drawable) {
diff --git a/ui/helpers.go b/ui/helpers.go
deleted file mode 100644
index f2b2adff..00000000
--- a/ui/helpers.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package ui
-
-import (
- "fmt"
-
- tb "github.com/nsf/termbox-go"
-)
-
-func TPrintf(geo *Geometry, ref tb.Cell, format string, a ...interface{}) {
- str := fmt.Sprintf(format, a...)
- _geo := *geo
- newline := func() {
- // TODO: Abort when out of room?
- geo.Col = _geo.Col
- geo.Row++
- }
- for _, ch := range str {
- switch ch {
- case '\n':
- newline()
- case '\r':
- geo.Col = _geo.Col
- default:
- tb.SetCell(geo.Col, geo.Row, ch, ref.Fg, ref.Bg)
- geo.Col++
- if geo.Col == _geo.Col+geo.Width {
- newline()
- }
- }
- }
-}
-
-func TFill(geo Geometry, ref tb.Cell) {
- _geo := geo
- for ; geo.Row < geo.Height; geo.Row++ {
- for ; geo.Col < geo.Width; geo.Col++ {
- tb.SetCell(geo.Col, geo.Row, ref.Ch, ref.Fg, ref.Bg)
- }
- geo.Col = _geo.Col
- }
-}
diff --git a/ui/types.go b/ui/types.go
deleted file mode 100644
index 5437642c..00000000
--- a/ui/types.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package ui
-
-import (
- tb "github.com/nsf/termbox-go"
-
- "git.sr.ht/~sircmpwn/aerc2/config"
- "git.sr.ht/~sircmpwn/aerc2/worker/types"
-)
-
-const (
- Valid = 0
- InvalidateTabList = 1 << iota
- InvalidateTabView
- InvalidateStatusBar
-)
-
-const (
- InvalidateAll = InvalidateTabList |
- InvalidateTabView |
- InvalidateStatusBar
-)
-
-type Geometry struct {
- Row int
- Col int
- Width int
- Height int
-}
-
-type AercTab interface {
- Name() string
- Render(at Geometry)
- SetParent(parent *UIState)
-}
-
-type WorkerListener interface {
- GetChannel() chan types.WorkerMessage
- HandleMessage(msg types.WorkerMessage)
-}
-
-type wrappedMessage struct {
- msg types.WorkerMessage
- listener WorkerListener
-}
-
-type UIState struct {
- Config *config.AercConfig
- Exit bool
- InvalidPanes uint
-
- Panes struct {
- TabList Geometry
- TabView Geometry
- Sidebar Geometry
- StatusBar Geometry
- }
-
- Tabs []AercTab
- SelectedTab int
-
- Prompt struct {
- Prompt *string
- Text *string
- Index int
- Scroll int
- }
-
- tbEvents chan tb.Event
- // Aggregate channel for all worker messages
- workerEvents chan wrappedMessage
-}
diff --git a/ui/ui.go b/ui/ui.go
index db316966..d1d2ca3d 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -6,17 +6,27 @@ import (
"git.sr.ht/~sircmpwn/aerc2/config"
)
-func Initialize(conf *config.AercConfig) (*UIState, error) {
- state := UIState{
- Config: conf,
- InvalidPanes: InvalidateAll,
+type UI struct {
+ Exit bool
+ Content Drawable
+ ctx *Context
- tbEvents: make(chan tb.Event, 10),
- workerEvents: make(chan wrappedMessage),
- }
+ tbEvents chan tb.Event
+ invalidations chan interface{}
+}
+
+func Initialize(conf *config.AercConfig, content Drawable) (*UI, error) {
if err := tb.Init(); err != nil {
return nil, err
}
+ width, height := tb.Size()
+ state := UI{
+ Content: content,
+ ctx: NewContext(width, height),
+
+ tbEvents: make(chan tb.Event, 10),
+ invalidations: make(chan interface{}),
+ }
tb.SetInputMode(tb.InputEsc | tb.InputMouse)
tb.SetOutputMode(tb.Output256)
go (func() {
@@ -24,50 +34,18 @@ func Initialize(conf *config.AercConfig) (*UIState, error) {
state.tbEvents <- tb.PollEvent()
}
})()
+ go (func() { state.invalidations <- nil })()
+ content.OnInvalidate(func(_ Drawable) {
+ go (func() { state.invalidations <- nil })()
+ })
return &state, nil
}
-func (state *UIState) Close() {
+func (state *UI) Close() {
tb.Close()
}
-func (state *UIState) AddTab(tab AercTab) {
- tab.SetParent(state)
- state.Tabs = append(state.Tabs, tab)
- if listener, ok := tab.(WorkerListener); ok {
- go (func() {
- for msg := range listener.GetChannel() {
- state.workerEvents <- wrappedMessage{
- msg: msg,
- listener: listener,
- }
- }
- })()
- }
-}
-
-func (state *UIState) Invalidate(what uint) {
- state.InvalidPanes |= what
-}
-
-func (state *UIState) InvalidateFrom(tab AercTab) {
- if state.Tabs[state.SelectedTab] == tab {
- state.Invalidate(InvalidateTabView)
- }
-}
-
-func (state *UIState) calcGeometries() {
- width, height := tb.Size()
- // TODO: more
- state.Panes.TabView = Geometry{
- Row: 0,
- Col: 0,
- Width: width,
- Height: height,
- }
-}
-
-func (state *UIState) Tick() bool {
+func (state *UI) Tick() bool {
select {
case event := <-state.tbEvents:
switch event.Type {
@@ -76,26 +54,15 @@ func (state *UIState) Tick() bool {
state.Exit = true
}
case tb.EventResize:
- state.Invalidate(InvalidateAll)
- }
- case msg := <-state.workerEvents:
- msg.listener.HandleMessage(msg.msg)
- default:
- // no-op
- break
- }
- if state.InvalidPanes != 0 {
- invalid := state.InvalidPanes
- state.InvalidPanes = 0
- if invalid&InvalidateAll == InvalidateAll {
tb.Clear(tb.ColorDefault, tb.ColorDefault)
- state.calcGeometries()
- }
- if invalid&InvalidateTabView != 0 {
- tab := state.Tabs[state.SelectedTab]
- tab.Render(state.Panes.TabView)
+ state.ctx = NewContext(event.Width, event.Height)
+ state.Content.Invalidate()
}
+ case <-state.invalidations:
+ state.Content.Draw(state.ctx)
tb.Flush()
+ default:
+ return false
}
return true
}