From 1418e1b9dc41d8f69bccb8de0fe0f1fb6835ce11 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Mon, 26 Feb 2018 22:54:39 -0500 Subject: Split UI library and widgets --- ui/account.go.old | 97 --------------------------- ui/borders.go | 73 --------------------- ui/context.go | 109 ------------------------------- ui/drawable.go | 10 --- ui/exline.go | 127 ------------------------------------ ui/grid.go | 191 ------------------------------------------------------ ui/interactive.go | 10 --- ui/tab.go | 115 -------------------------------- ui/text.go | 71 -------------------- ui/ui.go | 79 ---------------------- 10 files changed, 882 deletions(-) delete mode 100644 ui/account.go.old delete mode 100644 ui/borders.go delete mode 100644 ui/context.go delete mode 100644 ui/drawable.go delete mode 100644 ui/exline.go delete mode 100644 ui/grid.go delete mode 100644 ui/interactive.go delete mode 100644 ui/tab.go delete mode 100644 ui/text.go delete mode 100644 ui/ui.go (limited to 'ui') 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) -} -- cgit v1.2.3