aboutsummaryrefslogtreecommitdiffstats
path: root/gui/widget
diff options
context:
space:
mode:
Diffstat (limited to 'gui/widget')
-rw-r--r--gui/widget/concurrent_face.go51
-rw-r--r--gui/widget/text.go78
-rw-r--r--gui/widget/widget.go130
3 files changed, 259 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/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..cbc837e
--- /dev/null
+++ b/gui/widget/widget.go
@@ -0,0 +1,130 @@
+package widget
+
+import (
+ "cmp"
+ "fmt"
+
+ "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) {
+ 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
+ }
+ }
+ }
+ close(env.Draw())
+}
+
+func Input(val chan<- uint, r image.Rectangle, focusChan <-chan bool, env gui.Env) {
+ 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)
+
+ for {
+ select {
+ case focus = <-focusChan:
+ env.Draw() <- redraw(text, focus)
+ case event := <-env.Events():
+ 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)
+ }
+ }
+ }
+ }
+ close(env.Draw())
+}
+
+func Output(val <-chan uint, r image.Rectangle, env gui.Env) {
+ redraw := func(n uint) func(draw.Image) image.Rectangle {
+ return func(drw draw.Image) image.Rectangle {
+ drawText([]byte(fmt.Sprint(n)), drw, r, BLACK, WHITE)
+ return r
+ }
+ }
+
+ var n uint = 0
+ env.Draw() <- redraw(n)
+
+Loop:
+ for {
+ select {
+ case n = <-val:
+ env.Draw() <- redraw(n)
+ case event, ok := <-env.Events():
+ if !ok { // channel closed
+ break Loop
+ }
+ if event, ok := event.(win.WiFocus); ok && event.Focused {
+ env.Draw() <- redraw(n)
+ }
+ }
+ }
+ close(env.Draw())
+}
+
+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
+}