diff options
Diffstat (limited to 'gui/widget')
| -rw-r--r-- | gui/widget/concurrent_face.go | 51 | ||||
| -rw-r--r-- | gui/widget/focus.go | 70 | ||||
| -rw-r--r-- | gui/widget/text.go | 78 | ||||
| -rw-r--r-- | gui/widget/widget.go | 170 |
4 files changed, 369 insertions, 0 deletions
diff --git a/gui/widget/concurrent_face.go b/gui/widget/concurrent_face.go new file mode 100644 index 0000000..98db572 --- /dev/null +++ b/gui/widget/concurrent_face.go @@ -0,0 +1,51 @@ +package widget + +import ( + "image" + "sync" + + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" +) + +type concurrentFace struct { + mu sync.Mutex + face font.Face +} + +func (cf *concurrentFace) Close() error { + cf.mu.Lock() + defer cf.mu.Unlock() + return cf.face.Close() +} + +func (cf *concurrentFace) Glyph(dot fixed.Point26_6, r rune) ( + dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { + cf.mu.Lock() + defer cf.mu.Unlock() + return cf.face.Glyph(dot, r) +} + +func (cf *concurrentFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { + cf.mu.Lock() + defer cf.mu.Unlock() + return cf.face.GlyphBounds(r) +} + +func (cf *concurrentFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { + cf.mu.Lock() + defer cf.mu.Unlock() + return cf.face.GlyphAdvance(r) +} + +func (cf *concurrentFace) Kern(r0, r1 rune) fixed.Int26_6 { + cf.mu.Lock() + defer cf.mu.Unlock() + return cf.face.Kern(r0, r1) +} + +func (cf *concurrentFace) Metrics() font.Metrics { + cf.mu.Lock() + defer cf.mu.Unlock() + return cf.face.Metrics() +} diff --git a/gui/widget/focus.go b/gui/widget/focus.go new file mode 100644 index 0000000..e2d1074 --- /dev/null +++ b/gui/widget/focus.go @@ -0,0 +1,70 @@ +package widget + +import "image" + +// Focus keeps track of the currently selected widget. +// A widget receives true when it gains focus and false when it loses focus. +type Focus struct { + Widgets [][]chan bool + p image.Point // coordinates of currently focused widget +} + +func NewFocus(rows []int) Focus { + f := Focus{ + make([][]chan bool, len(rows)), + image.Point{}, + } + for i := range f.Widgets { + f.Widgets[i] = make([]chan bool, rows[i]) + for j := range f.Widgets[i] { + f.Widgets[i][j] = make(chan bool) + } + } + return f +} + +func (f *Focus) Close() { + for i := range f.Widgets { + for j := range f.Widgets[i] { + close(f.Widgets[i][j]) + } + } +} + +func (f *Focus) Focus(focus bool) { + f.Widgets[f.p.Y][f.p.X] <- focus +} + +func (f *Focus) Left() { + f.Focus(false) + if f.p.X <= 0 { + f.p.X = len(f.Widgets[f.p.Y]) - 1 + } else { + f.p.X-- + } + f.Focus(true) +} + +func (f *Focus) Right() { + f.Focus(false) + f.p.X = (f.p.X + 1) % len(f.Widgets[f.p.Y]) + f.Focus(true) +} + +func (f *Focus) Up() { + f.Focus(false) + if f.p.Y <= 0 { + f.p.Y = len(f.Widgets) - 1 + } else { + f.p.Y-- + } + f.p.X = min(f.p.X, len(f.Widgets[f.p.Y])-1) + f.Focus(true) +} + +func (f *Focus) Down() { + f.Focus(false) + f.p.Y = (f.p.Y + 1) % len(f.Widgets) + f.p.X = min(f.p.X, len(f.Widgets[f.p.Y])-1) + f.Focus(true) +} diff --git a/gui/widget/text.go b/gui/widget/text.go new file mode 100644 index 0000000..1b40096 --- /dev/null +++ b/gui/widget/text.go @@ -0,0 +1,78 @@ +package widget + +import ( + "log" + "sync" + + "image" + "image/color" + "image/draw" + + "golang.org/x/image/font" + "golang.org/x/image/font/gofont/goregular" + "golang.org/x/image/font/opentype" + "golang.org/x/image/math/fixed" +) + +var ( + FONT = goregular.TTF + FONT_SIZE = 15 + DPI = 72 + PAD = 3 +) + +var face *concurrentFace + +func init() { + fnt, err := opentype.Parse(FONT) + if err != nil { + log.Fatal(err) + } + fce, err := opentype.NewFace(fnt, &opentype.FaceOptions{ + Size: float64(FONT_SIZE), + DPI: float64(DPI), + }) + if err != nil { + log.Fatal(err) + } + face = &concurrentFace{sync.Mutex{}, fce} +} + +func TextSize(text string) image.Point { + bounds := textBounds([]byte(text), font.Drawer{Face: face}) + return image.Point{bounds.Max.X - bounds.Min.X + 2*PAD, bounds.Max.Y - bounds.Min.Y + 2*PAD} +} + +func drawText(text []byte, dst draw.Image, r image.Rectangle, fg, bg color.Color) { + drawer := font.Drawer{ + Src: &image.Uniform{fg}, + Face: face, + Dot: fixed.P(0, 0), + } + + // background + draw.Draw(dst, r, &image.Uniform{bg}, image.ZP, draw.Src) + + // text image + bounds := textBounds(text, drawer) + textImg := image.NewRGBA(bounds) + draw.Draw(textImg, bounds, &image.Uniform{bg}, image.ZP, draw.Src) + drawer.Dst = textImg + drawer.DrawBytes(text) + + // draw text image over background + leftCentre := image.Pt(bounds.Min.X, (bounds.Min.Y+bounds.Max.Y)/2) + target := image.Pt(r.Max.X-bounds.Max.X-PAD, (r.Min.Y+r.Max.Y)/2) + delta := target.Sub(leftCentre) + draw.Draw(dst, bounds.Add(delta).Intersect(r), drawer.Dst, bounds.Min, draw.Src) +} + +func textBounds(text []byte, drawer font.Drawer) image.Rectangle { + b, _ := drawer.BoundBytes(text) + return image.Rect( + b.Min.X.Floor(), + b.Min.Y.Floor(), + b.Max.X.Ceil(), + b.Max.Y.Ceil(), + ) +} diff --git a/gui/widget/widget.go b/gui/widget/widget.go new file mode 100644 index 0000000..ae09160 --- /dev/null +++ b/gui/widget/widget.go @@ -0,0 +1,170 @@ +package widget + +import ( + "cmp" + "fmt" + "sync" + + xdraw "golang.org/x/image/draw" + "image" + "image/color" + "image/draw" + + "volute/gui" + "volute/gui/win" +) + +var ( + FOCUS_COLOR = color.RGBA{179, 217, 255, 255} + GREEN = color.RGBA{51, 102, 0, 255} + BLACK = color.Gray{0} + WHITE = color.Gray{255} +) + +func Label(text string, r image.Rectangle, env gui.Env, wg *sync.WaitGroup) { + defer wg.Done() + defer close(env.Draw()) + + redraw := func(drw draw.Image) image.Rectangle { + drawText([]byte(text), drw, r, BLACK, WHITE) + return r + } + + env.Draw() <- redraw + for event := range env.Events() { + switch event := event.(type) { + case win.WiFocus: + if event.Focused { + env.Draw() <- redraw + } + } + } +} + +func Input(val chan<- uint, r image.Rectangle, focusChan <-chan bool, env gui.Env, wg *sync.WaitGroup) { + defer wg.Done() + defer close(env.Draw()) + defer close(val) + + redraw := func(text []byte, focus bool) func(draw.Image) image.Rectangle { + return func(drw draw.Image) image.Rectangle { + if focus { + drawText(text, drw, r, GREEN, FOCUS_COLOR) + } else { + drawText(text, drw, r, GREEN, WHITE) + } + return r + } + } + text := []byte{'0'} + focus := false + + env.Draw() <- redraw(text, focus) +Loop: + for { + select { + case focus = <-focusChan: + env.Draw() <- redraw(text, focus) + case event, ok := <-env.Events(): + if !ok { // channel closed + break Loop + } + switch event := event.(type) { + case win.WiFocus: + if event.Focused { + env.Draw() <- redraw(text, focus) + } + case win.KbType: + if focus && isDigit(event.Rune) { + text = fmt.Appendf(text, "%c", event.Rune) + env.Draw() <- redraw(text, focus) + val <- atoi(text) + } + case win.KbDown: + if focus && event.Key == win.KeyBackspace && len(text) > 0 { + text = text[:len(text)-1] + env.Draw() <- redraw(text, focus) + val <- atoi(text) + } + } + } + } +} + +func Output(val <-chan float64, r image.Rectangle, env gui.Env, wg *sync.WaitGroup) { + defer wg.Done() + defer close(env.Draw()) + + redraw := func(v float64) func(draw.Image) image.Rectangle { + return func(drw draw.Image) image.Rectangle { + drawText([]byte(fmt.Sprintf("%.3f", v)), drw, r, BLACK, WHITE) + return r + } + } + var v float64 = 0.0 + + env.Draw() <- redraw(v) +Loop: + for { + select { + case v = <-val: + env.Draw() <- redraw(v) + case event, ok := <-env.Events(): + if !ok { // channel closed + break Loop + } + if event, ok := event.(win.WiFocus); ok && event.Focused { + env.Draw() <- redraw(v) + } + } + } +} + +func Image(imChan <-chan image.Image, r image.Rectangle, env gui.Env, wg *sync.WaitGroup) { + defer wg.Done() + defer close(env.Draw()) + + interp := xdraw.ApproxBiLinear + redraw := func(im image.Image) func(draw.Image) image.Rectangle { + return func(drw draw.Image) image.Rectangle { + interp.Scale(drw, r, im, im.Bounds(), draw.Src, nil) + return r + } + } + var im image.Image = image.NewGray(r) + + for { + select { + case im = <-imChan: + env.Draw() <- redraw(im) + case event, ok := <-env.Events(): + if !ok { + return + } + if event, ok := event.(win.WiFocus); ok && event.Focused { + env.Draw() <- redraw(im) + } + } + } +} + +func isDigit(r rune) bool { + return '0' <= r && r <= '9' +} + +func contains[T cmp.Ordered](slc []T, v T) bool { + for i := range slc { + if slc[i] == v { + return true + } + } + return false +} + +func atoi(s []byte) uint { + var n uint = 0 + for _, d := range s { + n = n*10 + uint(d-'0') + } + return n +} |