From 5b2e3a0ca0b549c569ff6c01549c2dc425b0ba40 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sat, 17 Feb 2018 19:42:29 -0500 Subject: [PATCH] Implement tab container --- cmd/aerc/main.go | 15 +++++-- ui/context.go | 31 ++++++++----- ui/tab.go | 115 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 ui/tab.go diff --git a/cmd/aerc/main.go b/cmd/aerc/main.go index 1d11c5d..7a273b8 100644 --- a/cmd/aerc/main.go +++ b/cmd/aerc/main.go @@ -48,9 +48,13 @@ func main() { panic(err) } + tabs := ui.NewTabs() + tabs.Add(fill('★'), "白い星") + tabs.Add(fill('☆'), "empty stars") + grid := ui.NewGrid() grid.Rows = []ui.DimSpec{ - ui.DimSpec{ui.SIZE_EXACT, 4}, + ui.DimSpec{ui.SIZE_EXACT, 1}, ui.DimSpec{ui.SIZE_WEIGHT, 1}, ui.DimSpec{ui.SIZE_WEIGHT, 1}, ui.DimSpec{ui.SIZE_EXACT, 1}, @@ -59,8 +63,8 @@ func main() { 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(tabs.TabStrip).At(0, 0).Span(1, 2) + grid.AddChild(tabs.TabContent).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) @@ -71,6 +75,11 @@ func main() { } defer _ui.Close() + go (func() { + time.Sleep(1 * time.Second) + tabs.Select(1) + })() + for !_ui.Exit { if !_ui.Tick() { time.Sleep(100 * time.Millisecond) diff --git a/ui/context.go b/ui/context.go index e7d9ebe..ae9e561 100644 --- a/ui/context.go +++ b/ui/context.go @@ -3,7 +3,8 @@ package ui import ( "fmt" - "github.com/nsf/termbox-go" + "github.com/mattn/go-runewidth" + tb "github.com/nsf/termbox-go" ) // A context allows you to draw in a sub-region of the terminal @@ -38,15 +39,15 @@ func (ctx *Context) Subcontext(x, y, width, height int) *Context { } } -func (ctx *Context) SetCell(x, y int, ch rune, fg, bg termbox.Attribute) { +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")) } - termbox.SetCell(ctx.x+x, ctx.y+y, ch, fg, bg) + tb.SetCell(ctx.x+x, ctx.y+y, ch, fg, bg) } -func (ctx *Context) Printf(x, y int, ref termbox.Cell, - format string, a ...interface{}) { +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")) @@ -64,29 +65,35 @@ func (ctx *Context) Printf(x, y int, ref termbox.Cell, return y < ctx.height } for _, ch := range str { + if str == " こんにちは " { + fmt.Printf("%c\n", ch) + } switch ch { case '\n': if !newline() { - return + return runewidth.StringWidth(str) } case '\r': x = old_x default: - termbox.SetCell(x, y, ch, ref.Fg, ref.Bg) - x++ + tb.SetCell(x, y, ch, ref.Fg, ref.Bg) + x += runewidth.RuneWidth(ch) if x == old_x+ctx.width { if !newline() { - return + return runewidth.StringWidth(str) } } } } + + return runewidth.StringWidth(str) } -func (ctx *Context) Fill(x, y, width, height int, ref termbox.Cell) { +func (ctx *Context) Fill(x, y, width, height int, ref tb.Cell) { _x := x - for ; y < height && y < ctx.height; y++ { - for ; x < width && x < ctx.width; 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/tab.go b/ui/tab.go new file mode 100644 index 0000000..e6a8aa5 --- /dev/null +++ b/ui/tab.go @@ -0,0 +1,115 @@ +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 +}