about summary refs log tree commit diff stats
path: root/ui
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2018-02-26 22:54:39 -0500
committerDrew DeVault <sir@cmpwn.com>2018-02-26 22:54:39 -0500
commit1418e1b9dc41d8f69bccb8de0fe0f1fb6835ce11 (patch)
tree4ae8b3373fdadb6dd3e7b8c8789cf938522b8f8a /ui
parent661e3ec2a4dd97d4a8a8eab4f281b088770a6af2 (diff)
downloadaerc-1418e1b9dc41d8f69bccb8de0fe0f1fb6835ce11.tar.gz
Split UI library and widgets
Diffstat (limited to 'ui')
-rw-r--r--ui/account.go.old97
-rw-r--r--ui/borders.go73
-rw-r--r--ui/context.go109
-rw-r--r--ui/drawable.go10
-rw-r--r--ui/exline.go127
-rw-r--r--ui/grid.go191
-rw-r--r--ui/interactive.go10
-rw-r--r--ui/tab.go115
-rw-r--r--ui/text.go71
-rw-r--r--ui/ui.go79
10 files changed, 0 insertions, 882 deletions
diff --git a/ui/account.go.old b/ui/account.go.old
deleted file mode 100644
index 393a47a..0000000
--- a/ui/account.go.old
+++ /dev/null
@@ -1,97 +0,0 @@
-package ui
-
-import (
-	"log"
-
-	tb "github.com/nsf/termbox-go"
-
-	"git.sr.ht/~sircmpwn/aerc2/config"
-	"git.sr.ht/~sircmpwn/aerc2/worker"
-	"git.sr.ht/~sircmpwn/aerc2/worker/types"
-)
-
-type AccountTab struct {
-	Config  *config.AccountConfig
-	Worker  *types.Worker
-	Parent  *UIState
-	logger  *log.Logger
-	counter int
-}
-
-func NewAccountTab(conf *config.AccountConfig,
-	logger *log.Logger) (*AccountTab, error) {
-
-	work, err := worker.NewWorker(conf.Source, logger)
-	if err != nil {
-		return nil, err
-	}
-	go work.Backend.Run()
-	acc := &AccountTab{
-		Config: conf,
-		Worker: work,
-		logger: logger,
-	}
-	acc.Worker.PostAction(&types.Configure{Config: conf}, nil)
-	acc.Worker.PostAction(&types.Connect{}, func(msg types.WorkerMessage) {
-		switch msg := msg.(type) {
-		case *types.Done:
-			acc.logger.Println("Connected.")
-			acc.Worker.PostAction(&types.ListDirectories{}, nil)
-		case *types.CertificateApprovalRequest:
-			// TODO: Ask the user
-			acc.logger.Println("Approving certificate")
-			acc.Worker.PostAction(&types.ApproveCertificate{
-				Message:  types.RespondTo(msg),
-				Approved: true,
-			}, nil)
-		default:
-			acc.logger.Println("Connection failed.")
-		}
-	})
-	return acc, nil
-}
-
-func (acc *AccountTab) Name() string {
-	return acc.Config.Name
-}
-
-func (acc *AccountTab) SetParent(parent *UIState) {
-	acc.Parent = parent
-}
-
-func (acc *AccountTab) Render(at Geometry) {
-	cell := tb.Cell{
-		Ch: ' ',
-		Fg: tb.ColorDefault,
-		Bg: tb.ColorDefault,
-	}
-	TFill(at, cell)
-	TPrintf(&at, cell, "%s %d\n", acc.Name(), acc.counter)
-	acc.counter++
-	if acc.counter%10000 == 0 {
-		acc.counter = 0
-	}
-	acc.Parent.InvalidateFrom(acc)
-}
-
-func (acc *AccountTab) GetChannel() chan types.WorkerMessage {
-	return acc.Worker.Messages
-}
-
-func (acc *AccountTab) HandleMessage(msg types.WorkerMessage) {
-	msg = acc.Worker.ProcessMessage(msg)
-	switch msg := msg.(type) {
-	case *types.Done:
-	case *types.CertificateApprovalRequest:
-	case *types.Unsupported:
-		// no-op
-	case *types.Error:
-		acc.logger.Printf("Error: %v\n", msg.Error)
-	case *types.Directory:
-		acc.logger.Printf("Directory: %s\n", msg.Name)
-	default:
-		acc.Worker.PostAction(&types.Unsupported{
-			Message: types.RespondTo(msg),
-		}, nil)
-	}
-}
diff --git a/ui/borders.go b/ui/borders.go
deleted file mode 100644
index 08071ad..0000000
--- a/ui/borders.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package ui
-
-import (
-	tb "github.com/nsf/termbox-go"
-)
-
-const (
-	BORDER_LEFT   = 1 << iota
-	BORDER_TOP    = 1 << iota
-	BORDER_RIGHT  = 1 << iota
-	BORDER_BOTTOM = 1 << iota
-)
-
-type Bordered struct {
-	borders      uint
-	content      Drawable
-	onInvalidate func(d Drawable)
-}
-
-func NewBordered(content Drawable, borders uint) *Bordered {
-	b := &Bordered{
-		borders: borders,
-		content: content,
-	}
-	content.OnInvalidate(b.contentInvalidated)
-	return b
-}
-
-func (bordered *Bordered) contentInvalidated(d Drawable) {
-	bordered.Invalidate()
-}
-
-func (bordered *Bordered) Invalidate() {
-	if bordered.onInvalidate != nil {
-		bordered.onInvalidate(bordered)
-	}
-}
-
-func (bordered *Bordered) OnInvalidate(onInvalidate func(d Drawable)) {
-	bordered.onInvalidate = onInvalidate
-}
-
-func (bordered *Bordered) Draw(ctx *Context) {
-	x := 0
-	y := 0
-	width := ctx.Width()
-	height := ctx.Height()
-	cell := tb.Cell{
-		Ch: ' ',
-		Fg: tb.ColorBlack,
-		Bg: tb.ColorWhite,
-	}
-	if bordered.borders&BORDER_LEFT != 0 {
-		ctx.Fill(0, 0, 1, ctx.Height(), cell)
-		x += 1
-		width -= 1
-	}
-	if bordered.borders&BORDER_TOP != 0 {
-		ctx.Fill(0, 0, ctx.Width(), 1, cell)
-		y += 1
-		height -= 1
-	}
-	if bordered.borders&BORDER_RIGHT != 0 {
-		ctx.Fill(ctx.Width()-1, 0, 1, ctx.Height(), cell)
-		width -= 1
-	}
-	if bordered.borders&BORDER_BOTTOM != 0 {
-		ctx.Fill(0, ctx.Height()-1, ctx.Width(), 1, cell)
-		height -= 1
-	}
-	subctx := ctx.Subcontext(x, y, width, height)
-	bordered.content.Draw(subctx)
-}
diff --git a/ui/context.go b/ui/context.go
deleted file mode 100644
index ca3f452..0000000
--- a/ui/context.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package ui
-
-import (
-	"fmt"
-
-	"github.com/mattn/go-runewidth"
-	tb "github.com/nsf/termbox-go"
-)
-
-// A context allows you to draw in a sub-region of the terminal
-type Context struct {
-	x      int
-	y      int
-	width  int
-	height int
-}
-
-func (ctx *Context) X() int {
-	return ctx.x
-}
-
-func (ctx *Context) Y() int {
-	return ctx.y
-}
-
-func (ctx *Context) Width() int {
-	return ctx.width
-}
-
-func (ctx *Context) Height() int {
-	return ctx.height
-}
-
-func NewContext(width, height int) *Context {
-	return &Context{0, 0, width, height}
-}
-
-func (ctx *Context) Subcontext(x, y, width, height int) *Context {
-	if x+width > ctx.width || y+height > ctx.height {
-		panic(fmt.Errorf("Attempted to create context larger than parent"))
-	}
-	return &Context{
-		x:      ctx.x + x,
-		y:      ctx.y + y,
-		width:  width,
-		height: height,
-	}
-}
-
-func (ctx *Context) SetCell(x, y int, ch rune, fg, bg tb.Attribute) {
-	if x >= ctx.width || y >= ctx.height {
-		panic(fmt.Errorf("Attempted to draw outside of context"))
-	}
-	tb.SetCell(ctx.x+x, ctx.y+y, ch, fg, bg)
-}
-
-func (ctx *Context) Printf(x, y int, ref tb.Cell,
-	format string, a ...interface{}) int {
-
-	if x >= ctx.width || y >= ctx.height {
-		panic(fmt.Errorf("Attempted to draw outside of context"))
-	}
-
-	str := fmt.Sprintf(format, a...)
-
-	x += ctx.x
-	y += ctx.y
-	old_x := x
-
-	newline := func() bool {
-		x = old_x
-		y++
-		return y < ctx.height
-	}
-	for _, ch := range str {
-		if str == " こんにちは " {
-			fmt.Printf("%c\n", ch)
-		}
-		switch ch {
-		case '\n':
-			if !newline() {
-				return runewidth.StringWidth(str)
-			}
-		case '\r':
-			x = old_x
-		default:
-			tb.SetCell(x, y, ch, ref.Fg, ref.Bg)
-			x += runewidth.RuneWidth(ch)
-			if x == old_x+ctx.width {
-				if !newline() {
-					return runewidth.StringWidth(str)
-				}
-			}
-		}
-	}
-
-	return runewidth.StringWidth(str)
-}
-
-func (ctx *Context) Fill(x, y, width, height int, ref tb.Cell) {
-	_x := x
-	_y := y
-	for ; y < _y+height && y < ctx.height; y++ {
-		for ; x < _x+width && x < ctx.width; x++ {
-			ctx.SetCell(x, y, ref.Ch, ref.Fg, ref.Bg)
-		}
-		x = _x
-	}
-}
diff --git a/ui/drawable.go b/ui/drawable.go
deleted file mode 100644
index ef09451..0000000
--- a/ui/drawable.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package ui
-
-type Drawable interface {
-	// Called when this renderable should draw itself
-	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/exline.go b/ui/exline.go
deleted file mode 100644
index a377cd7..0000000
--- a/ui/exline.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package ui
-
-import (
-	tb "github.com/nsf/termbox-go"
-)
-
-// TODO: history
-// TODO: tab completion
-// TODO: commit
-// TODO: cancel (via esc/ctrl+c)
-// TODO: scrolling
-
-type ExLine struct {
-	command *string
-	commit  func(cmd *string)
-	index   int
-	scroll  int
-
-	onInvalidate func(d Drawable)
-}
-
-func NewExLine() *ExLine {
-	cmd := ""
-	return &ExLine{command: &cmd}
-}
-
-func (ex *ExLine) OnInvalidate(onInvalidate func(d Drawable)) {
-	ex.onInvalidate = onInvalidate
-}
-
-func (ex *ExLine) Invalidate() {
-	if ex.onInvalidate != nil {
-		ex.onInvalidate(ex)
-	}
-}
-
-func (ex *ExLine) Draw(ctx *Context) {
-	cell := tb.Cell{
-		Fg: tb.ColorDefault,
-		Bg: tb.ColorDefault,
-		Ch: ' ',
-	}
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), cell)
-	ctx.Printf(0, 0, cell, ":%s", *ex.command)
-	tb.SetCursor(ctx.X()+ex.index-ex.scroll+1, ctx.Y())
-}
-
-func (ex *ExLine) insert(ch rune) {
-	newCmd := (*ex.command)[:ex.index] + string(ch) + (*ex.command)[ex.index:]
-	ex.command = &newCmd
-	ex.index++
-	ex.Invalidate()
-}
-
-func (ex *ExLine) deleteWord() {
-	// TODO: Break on any of / " '
-	if len(*ex.command) == 0 {
-		return
-	}
-	i := ex.index - 1
-	if (*ex.command)[i] == ' ' {
-		i--
-	}
-	for ; i >= 0; i-- {
-		if (*ex.command)[i] == ' ' {
-			break
-		}
-	}
-	newCmd := (*ex.command)[:i+1] + (*ex.command)[ex.index:]
-	ex.command = &newCmd
-	ex.index = i + 1
-	ex.Invalidate()
-}
-
-func (ex *ExLine) deleteChar() {
-	if len(*ex.command) > 0 && ex.index != len(*ex.command) {
-		newCmd := (*ex.command)[:ex.index] + (*ex.command)[ex.index+1:]
-		ex.command = &newCmd
-		ex.Invalidate()
-	}
-}
-
-func (ex *ExLine) backspace() {
-	if len(*ex.command) > 0 && ex.index != 0 {
-		newCmd := (*ex.command)[:ex.index-1] + (*ex.command)[ex.index:]
-		ex.command = &newCmd
-		ex.index--
-		ex.Invalidate()
-	}
-}
-
-func (ex *ExLine) Event(event tb.Event) bool {
-	switch event.Type {
-	case tb.EventKey:
-		switch event.Key {
-		case tb.KeySpace:
-			ex.insert(' ')
-		case tb.KeyBackspace, tb.KeyBackspace2:
-			ex.backspace()
-		case tb.KeyCtrlD, tb.KeyDelete:
-			ex.deleteChar()
-		case tb.KeyCtrlB, tb.KeyArrowLeft:
-			if ex.index > 0 {
-				ex.index--
-				ex.Invalidate()
-			}
-		case tb.KeyCtrlF, tb.KeyArrowRight:
-			if ex.index < len(*ex.command) {
-				ex.index++
-				ex.Invalidate()
-			}
-		case tb.KeyCtrlA, tb.KeyHome:
-			ex.index = 0
-			ex.Invalidate()
-		case tb.KeyCtrlE, tb.KeyEnd:
-			ex.index = len(*ex.command)
-			ex.Invalidate()
-		case tb.KeyCtrlW:
-			ex.deleteWord()
-		default:
-			if event.Ch != 0 {
-				ex.insert(event.Ch)
-			}
-		}
-	}
-	return true
-}
diff --git a/ui/grid.go b/ui/grid.go
deleted file mode 100644
index ede7d0c..0000000
--- a/ui/grid.go
+++ /dev/null
@@ -1,191 +0,0 @@
-package ui
-
-import (
-	"fmt"
-	"math"
-)
-
-type Grid struct {
-	rows         []GridSpec
-	rowLayout    []gridLayout
-	columns      []GridSpec
-	columnLayout []gridLayout
-	Cells        []*GridCell
-	onInvalidate func(d Drawable)
-	invalid      bool
-}
-
-const (
-	SIZE_EXACT  = iota
-	SIZE_WEIGHT = iota
-)
-
-// Specifies the layout of a single row or column
-type GridSpec struct {
-	// One of SIZE_EXACT or SIZE_WEIGHT
-	Strategy int
-	// If Strategy = SIZE_EXACT, this is the number of cells this row/col shall
-	// occupy. If SIZE_WEIGHT, the space left after all exact rows/cols are
-	// measured is distributed amonst the remainder weighted by this value.
-	Size int
-}
-
-// Used to cache layout of each row/column
-type gridLayout struct {
-	Offset int
-	Size   int
-}
-
-type GridCell struct {
-	Row     int
-	Column  int
-	RowSpan int
-	ColSpan int
-	Content Drawable
-	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) Rows(spec []GridSpec) *Grid {
-	grid.rows = spec
-	return grid
-}
-
-func (grid *Grid) Columns(spec []GridSpec) *Grid {
-	grid.columns = spec
-	return grid
-}
-
-func (grid *Grid) Draw(ctx *Context) {
-	invalid := grid.invalid
-	if invalid {
-		grid.reflow(ctx)
-	}
-	for _, cell := range grid.Cells {
-		if !cell.invalid && !invalid {
-			continue
-		}
-		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 _, col := range cols {
-			width += col.Size
-		}
-		for _, row := range rows {
-			height += row.Size
-		}
-		subctx := ctx.Subcontext(x, y, width, height)
-		cell.Content.Draw(subctx)
-	}
-}
-
-func (grid *Grid) reflow(ctx *Context) {
-	grid.rowLayout = nil
-	grid.columnLayout = nil
-	flow := func(specs *[]GridSpec, layouts *[]gridLayout, extent int) {
-		exact := 0
-		weight := 0
-		nweights := 0
-		for _, spec := range *specs {
-			if spec.Strategy == SIZE_EXACT {
-				exact += spec.Size
-			} else if spec.Strategy == SIZE_WEIGHT {
-				nweights += 1
-				weight += spec.Size
-			}
-		}
-		offset := 0
-		for _, spec := range *specs {
-			layout := gridLayout{Offset: offset}
-			if spec.Strategy == SIZE_EXACT {
-				layout.Size = spec.Size
-			} else if spec.Strategy == SIZE_WEIGHT {
-				size := float64(spec.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.Height())
-	flow(&grid.columns, &grid.columnLayout, ctx.Width())
-	grid.invalid = false
-}
-
-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(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()
-	return cell
-}
-
-func (grid *Grid) RemoveChild(cell *GridCell) {
-	for i, _cell := range grid.Cells {
-		if _cell == cell {
-			grid.Cells = append(grid.Cells[:i], grid.Cells[i+1:]...)
-			break
-		}
-	}
-	grid.invalidateLayout()
-}
-
-func (grid *Grid) cellInvalidated(drawable Drawable) {
-	var cell *GridCell
-	for _, cell = range grid.Cells {
-		if cell.Content == drawable {
-			break
-		}
-		cell = nil
-	}
-	if cell == nil {
-		panic(fmt.Errorf("Attempted to invalidate unknown cell"))
-	}
-	cell.invalid = true
-	if grid.onInvalidate != nil {
-		grid.onInvalidate(grid)
-	}
-}
diff --git a/ui/interactive.go b/ui/interactive.go
deleted file mode 100644
index 5dd5fef..0000000
--- a/ui/interactive.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package ui
-
-import (
-	tb "github.com/nsf/termbox-go"
-)
-
-type Interactive interface {
-	// Returns true if the event was handled by this component
-	Event(event tb.Event) bool
-}
diff --git a/ui/tab.go b/ui/tab.go
deleted file mode 100644
index e6a8aa5..0000000
--- a/ui/tab.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package ui
-
-import (
-	tb "github.com/nsf/termbox-go"
-)
-
-type Tabs struct {
-	Tabs       []*Tab
-	TabStrip   *TabStrip
-	TabContent *TabContent
-	Selected   int
-
-	onInvalidateStrip   func(d Drawable)
-	onInvalidateContent func(d Drawable)
-}
-
-type Tab struct {
-	Content Drawable
-	Name    string
-	invalid bool
-}
-
-type TabStrip Tabs
-type TabContent Tabs
-
-func NewTabs() *Tabs {
-	tabs := &Tabs{}
-	tabs.TabStrip = (*TabStrip)(tabs)
-	tabs.TabContent = (*TabContent)(tabs)
-	return tabs
-}
-
-func (tabs *Tabs) Add(content Drawable, name string) {
-	tabs.Tabs = append(tabs.Tabs, &Tab{
-		Content: content,
-		Name:    name,
-	})
-	tabs.TabStrip.Invalidate()
-	content.OnInvalidate(tabs.invalidateChild)
-}
-
-func (tabs *Tabs) invalidateChild(d Drawable) {
-	for i, tab := range tabs.Tabs {
-		if tab.Content == d {
-			if i == tabs.Selected {
-				tabs.TabContent.Invalidate()
-			}
-			return
-		}
-	}
-}
-
-func (tabs *Tabs) Remove(content Drawable) {
-	for i, tab := range tabs.Tabs {
-		if tab.Content == content {
-			tabs.Tabs = append(tabs.Tabs[:i], tabs.Tabs[i+1:]...)
-			break
-		}
-	}
-	tabs.TabStrip.Invalidate()
-}
-
-func (tabs *Tabs) Select(index int) {
-	if tabs.Selected != index {
-		tabs.Selected = index
-		tabs.TabStrip.Invalidate()
-		tabs.TabContent.Invalidate()
-	}
-}
-
-// TODO: Color repository
-func (strip *TabStrip) Draw(ctx *Context) {
-	x := 0
-	for i, tab := range strip.Tabs {
-		cell := tb.Cell{
-			Fg: tb.ColorBlack,
-			Bg: tb.ColorWhite,
-		}
-		if strip.Selected == i {
-			cell.Fg = tb.ColorDefault
-			cell.Bg = tb.ColorDefault
-		}
-		x += ctx.Printf(x, 0, cell, " %s ", tab.Name)
-	}
-	cell := tb.Cell{
-		Fg: tb.ColorBlack,
-		Bg: tb.ColorWhite,
-	}
-	ctx.Fill(x, 0, ctx.Width()-x, 1, cell)
-}
-
-func (strip *TabStrip) Invalidate() {
-	if strip.onInvalidateStrip != nil {
-		strip.onInvalidateStrip(strip)
-	}
-}
-
-func (strip *TabStrip) OnInvalidate(onInvalidate func(d Drawable)) {
-	strip.onInvalidateStrip = onInvalidate
-}
-
-func (content *TabContent) Draw(ctx *Context) {
-	tab := content.Tabs[content.Selected]
-	tab.Content.Draw(ctx)
-}
-
-func (content *TabContent) Invalidate() {
-	if content.onInvalidateContent != nil {
-		content.onInvalidateContent(content)
-	}
-}
-
-func (content *TabContent) OnInvalidate(onInvalidate func(d Drawable)) {
-	content.onInvalidateContent = onInvalidate
-}
diff --git a/ui/text.go b/ui/text.go
deleted file mode 100644
index 6164837..0000000
--- a/ui/text.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package ui
-
-import (
-	"github.com/mattn/go-runewidth"
-	tb "github.com/nsf/termbox-go"
-)
-
-const (
-	TEXT_LEFT   = iota
-	TEXT_CENTER = iota
-	TEXT_RIGHT  = iota
-)
-
-type Text struct {
-	text         string
-	strategy     uint
-	fg           tb.Attribute
-	bg           tb.Attribute
-	onInvalidate func(d Drawable)
-}
-
-func NewText(text string) *Text {
-	return &Text{text: text}
-}
-
-func (t *Text) Text(text string) *Text {
-	t.text = text
-	t.Invalidate()
-	return t
-}
-
-func (t *Text) Strategy(strategy uint) *Text {
-	t.strategy = strategy
-	t.Invalidate()
-	return t
-}
-
-func (t *Text) Color(fg tb.Attribute, bg tb.Attribute) *Text {
-	t.fg = fg
-	t.bg = bg
-	t.Invalidate()
-	return t
-}
-
-func (t *Text) Draw(ctx *Context) {
-	size := runewidth.StringWidth(t.text)
-	cell := tb.Cell{
-		Ch: ' ',
-		Fg: t.fg,
-		Bg: t.bg,
-	}
-	x := 0
-	if t.strategy == TEXT_CENTER {
-		x = (ctx.Width() - size) / 2
-	}
-	if t.strategy == TEXT_RIGHT {
-		x = ctx.Width() - size
-	}
-	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), cell)
-	ctx.Printf(x, 0, cell, "%s", t.text)
-}
-
-func (t *Text) OnInvalidate(onInvalidate func(d Drawable)) {
-	t.onInvalidate = onInvalidate
-}
-
-func (t *Text) Invalidate() {
-	if t.onInvalidate != nil {
-		t.onInvalidate(t)
-	}
-}
diff --git a/ui/ui.go b/ui/ui.go
deleted file mode 100644
index 9ea037c..0000000
--- a/ui/ui.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package ui
-
-import (
-	tb "github.com/nsf/termbox-go"
-
-	"git.sr.ht/~sircmpwn/aerc2/config"
-)
-
-type UI struct {
-	Exit    bool
-	Content Drawable
-	ctx     *Context
-
-	interactive []Interactive
-
-	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() {
-		for !state.Exit {
-			state.tbEvents <- tb.PollEvent()
-		}
-	})()
-	go (func() { state.invalidations <- nil })()
-	content.OnInvalidate(func(_ Drawable) {
-		go (func() { state.invalidations <- nil })()
-	})
-	return &state, nil
-}
-
-func (state *UI) Close() {
-	tb.Close()
-}
-
-func (state *UI) Tick() bool {
-	select {
-	case event := <-state.tbEvents:
-		switch event.Type {
-		case tb.EventKey:
-			if event.Key == tb.KeyEsc {
-				state.Exit = true
-			}
-		case tb.EventResize:
-			tb.Clear(tb.ColorDefault, tb.ColorDefault)
-			state.ctx = NewContext(event.Width, event.Height)
-			state.Content.Invalidate()
-		}
-		if state.interactive != nil {
-			for _, i := range state.interactive {
-				i.Event(event)
-			}
-		}
-	case <-state.invalidations:
-		state.Content.Draw(state.ctx)
-		tb.Flush()
-	default:
-		return false
-	}
-	return true
-}
-
-func (state *UI) AddInteractive(i Interactive) {
-	state.interactive = append(state.interactive, i)
-}