summary refs log tree commit diff stats
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 4219978..1d11c5d 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 393a47a..393a47a 100644
--- a/ui/account.go
+++ b/ui/account.go.old
diff --git a/ui/context.go b/ui/context.go
index 9f2e2fe..e7d9ebe 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 a61c020..ef09451 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 2183a55..2091fc5 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 f2b2adf..0000000
--- 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 5437642..0000000
--- 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 db31696..d1d2ca3 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
 }