aboutsummaryrefslogtreecommitdiffstats
path: root/gui/widget
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2024-01-17 15:38:33 -0500
committerSam Anthony <sam@samanthony.xyz>2024-01-17 15:38:33 -0500
commit29e2be59cc3d0f31241884087624ece7d35115e4 (patch)
tree07486ce8cebf9d7f9901f015c6ccd7803b8baa59 /gui/widget
parent6a2e268df7e008579d1b6a0f2ef47597fcbe4862 (diff)
downloadvolute-29e2be59cc3d0f31241884087624ece7d35115e4.zip
add user input widget
Diffstat (limited to 'gui/widget')
-rw-r--r--gui/widget/concurrent_face.go51
-rw-r--r--gui/widget/text.go74
-rw-r--r--gui/widget/widget.go65
3 files changed, 190 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..cb66a0a
--- /dev/null
+++ b/gui/widget/text.go
@@ -0,0 +1,74 @@
+package widget
+
+import (
+ "log"
+ "sync"
+
+ "image"
+ "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 float64 = 15
+ DPI float64 = 72
+ BG_COLOR = image.White
+ TEXT_COLOR = image.Black
+)
+
+var face *concurrentFace
+
+func init() {
+ fnt, err := opentype.Parse(FONT)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fce, err := opentype.NewFace(fnt, &opentype.FaceOptions{
+ Size: FONT_SIZE,
+ DPI: DPI,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ face = &concurrentFace{sync.Mutex{}, fce}
+}
+
+func drawText(text []byte, dst draw.Image, r image.Rectangle) {
+ drawer := font.Drawer{
+ Src: TEXT_COLOR,
+ Face: face,
+ Dot: fixed.P(0, 0),
+ }
+
+ bounds := textBounds(text, drawer)
+
+ // background
+ draw.Draw(dst, r, BG_COLOR, image.ZP, draw.Src)
+
+ // text image
+ textImg := image.NewRGBA(bounds)
+ draw.Draw(textImg, bounds, BG_COLOR, image.ZP, draw.Src)
+ drawer.Dst = textImg
+ drawer.DrawBytes(text)
+
+ // draw text image over background
+ left := image.Pt(bounds.Min.X, (bounds.Min.Y+bounds.Max.Y)/2)
+ target := image.Pt(r.Min.X, (r.Min.Y+r.Max.Y)/2)
+ delta := target.Sub(left)
+ 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..913809b
--- /dev/null
+++ b/gui/widget/widget.go
@@ -0,0 +1,65 @@
+package widget
+
+import (
+ "cmp"
+ "fmt"
+
+ "image"
+ "image/draw"
+
+ "volute/gui"
+ "volute/gui/win"
+)
+
+func Input(env gui.Env, r image.Rectangle, val chan<- float64) {
+ redraw := func(text []byte) func(draw.Image) image.Rectangle {
+ return func(drw draw.Image) image.Rectangle {
+ drawText(text, drw, r)
+ return r
+ }
+ }
+ text := []byte{'0'}
+ focus := false
+
+ env.Draw() <- redraw(text)
+
+ for event := range env.Events() {
+ switch event := event.(type) {
+ case win.WiFocus:
+ if event.Focused {
+ env.Draw() <- redraw(text)
+ }
+ case win.MoDown:
+ if event.Point.In(r) {
+ focus = true
+ }
+ case win.KbType:
+ if !focus ||
+ (!isDigit(event.Rune) && event.Rune != '.') ||
+ (event.Rune == '.' && contains(text, '.')) {
+ continue
+ }
+ text = fmt.Appendf(text, "%c", event.Rune)
+ env.Draw() <- redraw(text)
+ case win.KbDown:
+ if event.Key == win.KeyBackspace && len(text) > 0 {
+ text = text[:len(text)-1]
+ env.Draw() <- redraw(text)
+ }
+ }
+ }
+ 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
+}