Polish up grid and add new rendering loop
This commit is contained in:
parent
1892d73161
commit
60b351b78c
8 changed files with 126 additions and 202 deletions
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -9,11 +8,30 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
|
tb "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
"git.sr.ht/~sircmpwn/aerc2/config"
|
"git.sr.ht/~sircmpwn/aerc2/config"
|
||||||
"git.sr.ht/~sircmpwn/aerc2/ui"
|
"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() {
|
func main() {
|
||||||
var logOut io.Writer
|
var logOut io.Writer
|
||||||
var logger *log.Logger
|
var logger *log.Logger
|
||||||
|
@ -29,20 +47,30 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer _ui.Close()
|
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 {
|
for !_ui.Exit {
|
||||||
if !_ui.Tick() {
|
if !_ui.Tick() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
|
@ -22,8 +22,7 @@ func (ctx *Context) Height() int {
|
||||||
return ctx.height
|
return ctx.height
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContext() *Context {
|
func NewContext(width, height int) *Context {
|
||||||
width, height := termbox.Size()
|
|
||||||
return &Context{0, 0, width, height}
|
return &Context{0, 0, width, height}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,6 @@ type Drawable interface {
|
||||||
Draw(ctx *Context)
|
Draw(ctx *Context)
|
||||||
// Specifies a function to call when this cell needs to be redrawn
|
// Specifies a function to call when this cell needs to be redrawn
|
||||||
OnInvalidate(callback func(d Drawable))
|
OnInvalidate(callback func(d Drawable))
|
||||||
|
// Invalidates the drawable
|
||||||
|
Invalidate()
|
||||||
}
|
}
|
||||||
|
|
70
ui/grid.go
70
ui/grid.go
|
@ -1,6 +1,9 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
type Grid struct {
|
type Grid struct {
|
||||||
Rows []DimSpec
|
Rows []DimSpec
|
||||||
|
@ -42,6 +45,22 @@ type GridCell struct {
|
||||||
invalid bool
|
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) {
|
func (grid *Grid) Draw(ctx *Context) {
|
||||||
invalid := grid.invalid
|
invalid := grid.invalid
|
||||||
if invalid {
|
if invalid {
|
||||||
|
@ -51,17 +70,17 @@ func (grid *Grid) Draw(ctx *Context) {
|
||||||
if !cell.invalid && !invalid {
|
if !cell.invalid && !invalid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rows := grid.rowLayout[cell.Row:cell.RowSpan]
|
rows := grid.rowLayout[cell.Row : cell.Row+cell.RowSpan]
|
||||||
cols := grid.columnLayout[cell.Column:cell.ColSpan]
|
cols := grid.columnLayout[cell.Column : cell.Column+cell.ColSpan]
|
||||||
x := cols[0].Offset
|
x := cols[0].Offset
|
||||||
y := rows[0].Offset
|
y := rows[0].Offset
|
||||||
width := 0
|
width := 0
|
||||||
height := 0
|
height := 0
|
||||||
for _, row := range rows {
|
|
||||||
width += row.Size
|
|
||||||
}
|
|
||||||
for _, col := range cols {
|
for _, col := range cols {
|
||||||
height += col.Size
|
width += col.Size
|
||||||
|
}
|
||||||
|
for _, row := range rows {
|
||||||
|
height += row.Size
|
||||||
}
|
}
|
||||||
subctx := ctx.Subcontext(x, y, width, height)
|
subctx := ctx.Subcontext(x, y, width, height)
|
||||||
cell.Content.Draw(subctx)
|
cell.Content.Draw(subctx)
|
||||||
|
@ -74,10 +93,12 @@ func (grid *Grid) reflow(ctx *Context) {
|
||||||
flow := func(specs *[]DimSpec, layouts *[]dimLayout, extent int) {
|
flow := func(specs *[]DimSpec, layouts *[]dimLayout, extent int) {
|
||||||
exact := 0
|
exact := 0
|
||||||
weight := 0
|
weight := 0
|
||||||
|
nweights := 0
|
||||||
for _, dim := range *specs {
|
for _, dim := range *specs {
|
||||||
if dim.Strategy == SIZE_EXACT {
|
if dim.Strategy == SIZE_EXACT {
|
||||||
exact += dim.Size
|
exact += dim.Size
|
||||||
} else if dim.Strategy == SIZE_WEIGHT {
|
} else if dim.Strategy == SIZE_WEIGHT {
|
||||||
|
nweights += 1
|
||||||
weight += dim.Size
|
weight += dim.Size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,30 +108,49 @@ func (grid *Grid) reflow(ctx *Context) {
|
||||||
if dim.Strategy == SIZE_EXACT {
|
if dim.Strategy == SIZE_EXACT {
|
||||||
layout.Size = dim.Size
|
layout.Size = dim.Size
|
||||||
} else if dim.Strategy == SIZE_WEIGHT {
|
} else if dim.Strategy == SIZE_WEIGHT {
|
||||||
size := float64(dim.Size) / float64(weight) * float64(extent)
|
size := float64(dim.Size) / float64(weight)
|
||||||
layout.Size = int(size)
|
size *= float64(extent - exact)
|
||||||
|
layout.Size = int(math.Floor(size))
|
||||||
}
|
}
|
||||||
|
offset += layout.Size
|
||||||
*layouts = append(*layouts, layout)
|
*layouts = append(*layouts, layout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flow(&grid.Rows, &grid.rowLayout, ctx.Width())
|
flow(&grid.Rows, &grid.rowLayout, ctx.Height())
|
||||||
flow(&grid.Columns, &grid.columnLayout, ctx.Height())
|
flow(&grid.Columns, &grid.columnLayout, ctx.Width())
|
||||||
grid.invalid = false
|
grid.invalid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (grid *Grid) InvalidateLayout() {
|
func (grid *Grid) invalidateLayout() {
|
||||||
grid.invalid = true
|
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)) {
|
func (grid *Grid) OnInvalidate(onInvalidate func(d Drawable)) {
|
||||||
grid.onInvalidate = onInvalidate
|
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)
|
grid.Cells = append(grid.Cells, cell)
|
||||||
cell.Content.OnInvalidate(grid.cellInvalidated)
|
cell.Content.OnInvalidate(grid.cellInvalidated)
|
||||||
cell.invalid = true
|
cell.invalid = true
|
||||||
grid.InvalidateLayout()
|
grid.invalidateLayout()
|
||||||
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
func (grid *Grid) RemoveChild(cell *GridCell) {
|
func (grid *Grid) RemoveChild(cell *GridCell) {
|
||||||
|
@ -120,7 +160,7 @@ func (grid *Grid) RemoveChild(cell *GridCell) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
grid.InvalidateLayout()
|
grid.invalidateLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (grid *Grid) cellInvalidated(drawable Drawable) {
|
func (grid *Grid) cellInvalidated(drawable Drawable) {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
71
ui/types.go
71
ui/types.go
|
@ -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
|
|
||||||
}
|
|
91
ui/ui.go
91
ui/ui.go
|
@ -6,17 +6,27 @@ import (
|
||||||
"git.sr.ht/~sircmpwn/aerc2/config"
|
"git.sr.ht/~sircmpwn/aerc2/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Initialize(conf *config.AercConfig) (*UIState, error) {
|
type UI struct {
|
||||||
state := UIState{
|
Exit bool
|
||||||
Config: conf,
|
Content Drawable
|
||||||
InvalidPanes: InvalidateAll,
|
ctx *Context
|
||||||
|
|
||||||
tbEvents: make(chan tb.Event, 10),
|
tbEvents chan tb.Event
|
||||||
workerEvents: make(chan wrappedMessage),
|
invalidations chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Initialize(conf *config.AercConfig, content Drawable) (*UI, error) {
|
||||||
if err := tb.Init(); err != nil {
|
if err := tb.Init(); err != nil {
|
||||||
return nil, err
|
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.SetInputMode(tb.InputEsc | tb.InputMouse)
|
||||||
tb.SetOutputMode(tb.Output256)
|
tb.SetOutputMode(tb.Output256)
|
||||||
go (func() {
|
go (func() {
|
||||||
|
@ -24,50 +34,18 @@ func Initialize(conf *config.AercConfig) (*UIState, error) {
|
||||||
state.tbEvents <- tb.PollEvent()
|
state.tbEvents <- tb.PollEvent()
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
go (func() { state.invalidations <- nil })()
|
||||||
|
content.OnInvalidate(func(_ Drawable) {
|
||||||
|
go (func() { state.invalidations <- nil })()
|
||||||
|
})
|
||||||
return &state, nil
|
return &state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *UIState) Close() {
|
func (state *UI) Close() {
|
||||||
tb.Close()
|
tb.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *UIState) AddTab(tab AercTab) {
|
func (state *UI) Tick() bool {
|
||||||
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 {
|
|
||||||
select {
|
select {
|
||||||
case event := <-state.tbEvents:
|
case event := <-state.tbEvents:
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
|
@ -76,26 +54,15 @@ func (state *UIState) Tick() bool {
|
||||||
state.Exit = true
|
state.Exit = true
|
||||||
}
|
}
|
||||||
case tb.EventResize:
|
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)
|
tb.Clear(tb.ColorDefault, tb.ColorDefault)
|
||||||
state.calcGeometries()
|
state.ctx = NewContext(event.Width, event.Height)
|
||||||
}
|
state.Content.Invalidate()
|
||||||
if invalid&InvalidateTabView != 0 {
|
|
||||||
tab := state.Tabs[state.SelectedTab]
|
|
||||||
tab.Render(state.Panes.TabView)
|
|
||||||
}
|
}
|
||||||
|
case <-state.invalidations:
|
||||||
|
state.Content.Draw(state.ctx)
|
||||||
tb.Flush()
|
tb.Flush()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue