From 29e2be59cc3d0f31241884087624ece7d35115e4 Mon Sep 17 00:00:00 2001 From: Sam Anthony Date: Wed, 17 Jan 2024 15:38:33 -0500 Subject: add user input widget --- gui/widget/concurrent_face.go | 51 +++++++++++++++++++++++++++++ gui/widget/text.go | 74 +++++++++++++++++++++++++++++++++++++++++++ gui/widget/widget.go | 65 +++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 gui/widget/concurrent_face.go create mode 100644 gui/widget/text.go create mode 100644 gui/widget/widget.go (limited to 'gui') 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 +} -- cgit v1.2.3