aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/imageviewer/browser.go181
-rw-r--r--examples/imageviewer/button.go61
-rw-r--r--examples/imageviewer/main.go66
-rw-r--r--examples/imageviewer/splits.go112
-rw-r--r--examples/imageviewer/theme.go19
-rw-r--r--examples/imageviewer/util.go104
-rw-r--r--examples/imageviewer/viewer.go65
7 files changed, 608 insertions, 0 deletions
diff --git a/examples/imageviewer/browser.go b/examples/imageviewer/browser.go
new file mode 100644
index 0000000..087e596
--- /dev/null
+++ b/examples/imageviewer/browser.go
@@ -0,0 +1,181 @@
+package main
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "os"
+ "path/filepath"
+
+ "github.com/faiface/gui"
+ "golang.org/x/image/math/fixed"
+)
+
+func Browser(env gui.Env, theme *Theme, dir string, cd <-chan string, view chan<- string) {
+ reload := func(dir string) (names []string, lineHeight int, namesImage *image.RGBA) {
+ names = nil
+
+ filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+ if path == dir {
+ return nil
+ }
+ rel, err := filepath.Rel(dir, path)
+ if err != nil {
+ return nil
+ }
+ if info.IsDir() {
+ names = append(names, rel+string(filepath.Separator))
+ return filepath.SkipDir
+ }
+ names = append(names, rel)
+ return nil
+ })
+
+ var images []image.Image
+ for _, name := range names {
+ images = append(images, DrawText(name, theme.Face, theme.Text))
+ }
+
+ const inset = 4
+
+ var width int
+ for _, img := range images {
+ if img.Bounds().Dx() > width {
+ width = img.Bounds().Inset(-inset).Dx()
+ }
+ }
+
+ metrics := theme.Face.Metrics()
+ lineHeight = (metrics.Height + 2*fixed.I(inset)).Ceil()
+ height := lineHeight * len(names)
+
+ namesImage = image.NewRGBA(image.Rect(0, 0, width+2*inset, height+2*inset))
+ for i := range images {
+ r := image.Rect(
+ 0, lineHeight*i,
+ width, lineHeight*(i+1),
+ )
+ DrawLeftCentered(namesImage, r.Inset(inset), images[i], draw.Over)
+ }
+
+ return names, lineHeight, namesImage
+ }
+
+ redraw := func(r image.Rectangle, selected int, position image.Point, lineHeight int, namesImage image.Image) func(draw.Image) image.Rectangle {
+ return func(drw draw.Image) image.Rectangle {
+ draw.Draw(drw, r, &image.Uniform{theme.Background}, image.ZP, draw.Src)
+ draw.Draw(drw, r, namesImage, position, draw.Over)
+ if selected >= 0 {
+ highlightR := image.Rect(
+ namesImage.Bounds().Min.X,
+ namesImage.Bounds().Min.Y+lineHeight*selected,
+ namesImage.Bounds().Max.X,
+ namesImage.Bounds().Min.Y+lineHeight*(selected+1),
+ )
+ highlightR = highlightR.Sub(position).Add(r.Min)
+ draw.DrawMask(
+ drw, highlightR.Intersect(r),
+ &image.Uniform{theme.Highlight}, image.ZP,
+ &image.Uniform{color.Alpha{64}}, image.ZP,
+ draw.Over,
+ )
+ }
+ return r
+ }
+ }
+
+ names, lineHeight, namesImage := reload(dir)
+
+ var (
+ r image.Rectangle
+ position = image.ZP
+ selected = -1
+ )
+
+ for {
+ select {
+ case path := <-cd:
+ if filepath.IsAbs(path) {
+ dir = path
+ } else {
+ dir = filepath.Join(dir, path)
+ }
+ names, lineHeight, namesImage = reload(dir)
+ position = image.ZP
+ selected = -1
+ env.Draw() <- redraw(r, selected, position, lineHeight, namesImage)
+
+ case e, ok := <-env.Events():
+ if !ok {
+ close(env.Draw())
+ return
+ }
+
+ var (
+ minX, minY, maxX, maxY int
+ x, y int
+ )
+
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &minX, &minY, &maxX, &maxY):
+ r = image.Rect(minX, minY, maxX, maxY)
+ env.Draw() <- redraw(r, selected, position, lineHeight, namesImage)
+
+ case e.Matches("mo/down/%d/%d", &x, &y):
+ if !image.Pt(x, y).In(r) {
+ continue
+ }
+ click := image.Pt(x, y).Sub(r.Min).Add(position)
+ i := click.Y / lineHeight
+ if i < 0 || i >= len(names) {
+ continue
+ }
+ if selected == i {
+ func() {
+ path := filepath.Join(dir, names[selected])
+ f, err := os.Open(path)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ info, err := f.Stat()
+ if err != nil {
+ return
+ }
+ if info.IsDir() {
+ dir = path
+ names, lineHeight, namesImage = reload(dir)
+ position = image.ZP
+ selected = -1
+ env.Draw() <- redraw(r, selected, position, lineHeight, namesImage)
+ } else {
+ view <- path
+ }
+ }()
+ } else {
+ selected = i
+ env.Draw() <- redraw(r, selected, position, lineHeight, namesImage)
+ }
+
+ case e.Matches("mo/scroll/%d/%d", &y, &x):
+ newP := position.Sub(image.Pt(int(x*16), int(y*16)))
+ if newP.X > namesImage.Bounds().Max.X-r.Dx() {
+ newP.X = namesImage.Bounds().Max.X - r.Dx()
+ }
+ if newP.Y > namesImage.Bounds().Max.Y-r.Dy() {
+ newP.Y = namesImage.Bounds().Max.Y - r.Dy()
+ }
+ if newP.X < namesImage.Bounds().Min.X {
+ newP.X = namesImage.Bounds().Min.X
+ }
+ if newP.Y < namesImage.Bounds().Min.Y {
+ newP.Y = namesImage.Bounds().Min.Y
+ }
+ if newP != position {
+ position = newP
+ env.Draw() <- redraw(r, selected, position, lineHeight, namesImage)
+ }
+ }
+ }
+ }
+}
diff --git a/examples/imageviewer/button.go b/examples/imageviewer/button.go
new file mode 100644
index 0000000..0078774
--- /dev/null
+++ b/examples/imageviewer/button.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+
+ "github.com/faiface/gui"
+)
+
+func Button(env gui.Env, theme *Theme, text string, action func()) {
+ textImg := DrawText(text, theme.Face, theme.Text)
+
+ redraw := func(r image.Rectangle, over, pressed bool) func(draw.Image) image.Rectangle {
+ return func(drw draw.Image) image.Rectangle {
+ var clr color.Color
+ if pressed {
+ clr = theme.ButtonDown
+ } else if over {
+ clr = theme.ButtonOver
+ } else {
+ clr = theme.ButtonUp
+ }
+ draw.Draw(drw, r, &image.Uniform{clr}, image.ZP, draw.Src)
+ DrawCentered(drw, r, textImg, draw.Over)
+ return r
+ }
+ }
+
+ var (
+ r image.Rectangle
+ over bool
+ pressed bool
+ )
+
+ for e := range env.Events() {
+ var x, y, minX, minY, maxX, maxY int
+
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &minX, &minY, &maxX, &maxY):
+ r = image.Rect(minX, minY, maxX, maxY)
+ env.Draw() <- redraw(r, over, pressed)
+
+ case e.Matches("mo/down/%d/%d/left", &x, &y):
+ newPressed := image.Pt(x, y).In(r)
+ if newPressed != pressed {
+ pressed = newPressed
+ env.Draw() <- redraw(r, over, pressed)
+ }
+
+ case e.Matches("mo/up/%d/%d/left", &x, &y):
+ if pressed {
+ action()
+ pressed = false
+ env.Draw() <- redraw(r, over, pressed)
+ }
+ }
+ }
+
+ close(env.Draw())
+}
diff --git a/examples/imageviewer/main.go b/examples/imageviewer/main.go
new file mode 100644
index 0000000..056bf1f
--- /dev/null
+++ b/examples/imageviewer/main.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "os/user"
+
+ "github.com/faiface/gui"
+ "github.com/faiface/gui/win"
+ "github.com/faiface/mainthread"
+ "github.com/golang/image/font/gofont/goregular"
+ "golang.org/x/image/colornames"
+)
+
+func run() {
+ face, err := TTFToFace(goregular.TTF, 18)
+ if err != nil {
+ panic(err)
+ }
+
+ theme := &Theme{
+ Face: face,
+ Background: colornames.White,
+ Empty: colornames.Darkgrey,
+ Text: colornames.Black,
+ Highlight: colornames.Blueviolet,
+ ButtonUp: colornames.Lightgrey,
+ ButtonDown: colornames.Grey,
+ }
+
+ w, err := win.New(win.Title("Image Viewer"), win.Size(900, 600), win.Resizable())
+ if err != nil {
+ panic(err)
+ }
+
+ mux, env := gui.NewMux(w)
+
+ cd := make(chan string)
+ view := make(chan string)
+
+ go Browser(FixedBottom(FixedLeft(mux.MakeEnv(), 300), 30), theme, ".", cd, view)
+ go Viewer(FixedRight(mux.MakeEnv(), 300), theme, view)
+
+ go Button(EvenHorizontal(FixedTop(FixedLeft(mux.MakeEnv(), 300), 30), 0, 1, 3), theme, "Dir Up", func() {
+ cd <- ".."
+ })
+ go Button(EvenHorizontal(FixedTop(FixedLeft(mux.MakeEnv(), 300), 30), 1, 2, 3), theme, "Refresh", func() {
+ cd <- "."
+ })
+ go Button(EvenHorizontal(FixedTop(FixedLeft(mux.MakeEnv(), 300), 30), 2, 3, 3), theme, "Home", func() {
+ user, err := user.Current()
+ if err != nil {
+ return
+ }
+ cd <- user.HomeDir
+ })
+
+ for e := range env.Events() {
+ switch {
+ case e.Matches("wi/close"):
+ close(env.Draw())
+ }
+ }
+}
+
+func main() {
+ mainthread.Run(run)
+}
diff --git a/examples/imageviewer/splits.go b/examples/imageviewer/splits.go
new file mode 100644
index 0000000..041a770
--- /dev/null
+++ b/examples/imageviewer/splits.go
@@ -0,0 +1,112 @@
+package main
+
+import (
+ "image"
+ "image/draw"
+
+ "github.com/faiface/gui"
+)
+
+type envPair struct {
+ events <-chan gui.Event
+ draw chan<- func(draw.Image) image.Rectangle
+}
+
+func (ep *envPair) Events() <-chan gui.Event { return ep.events }
+func (ep *envPair) Draw() chan<- func(draw.Image) image.Rectangle { return ep.draw }
+
+func FixedLeft(env gui.Env, maxX int) gui.Env {
+ out, in := gui.MakeEventsChan()
+
+ go func() {
+ for e := range env.Events() {
+ var minX, minY, dummy, maxY int
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &minX, &minY, &dummy, &maxY):
+ in <- gui.Eventf("resize/%d/%d/%d/%d", minX, minY, maxX, maxY)
+ default:
+ in <- e
+ }
+ }
+ close(in)
+ }()
+
+ return &envPair{out, env.Draw()}
+}
+
+func FixedRight(env gui.Env, minX int) gui.Env {
+ out, in := gui.MakeEventsChan()
+
+ go func() {
+ for e := range env.Events() {
+ var dummy, minY, maxX, maxY int
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &dummy, &minY, &maxX, &maxY):
+ in <- gui.Eventf("resize/%d/%d/%d/%d", minX, minY, maxX, maxY)
+ default:
+ in <- e
+ }
+ }
+ close(in)
+ }()
+
+ return &envPair{out, env.Draw()}
+}
+
+func FixedTop(env gui.Env, maxY int) gui.Env {
+ out, in := gui.MakeEventsChan()
+
+ go func() {
+ for e := range env.Events() {
+ var minX, minY, maxX, dummy int
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &minX, &minY, &maxX, &dummy):
+ in <- gui.Eventf("resize/%d/%d/%d/%d", minX, minY, maxX, maxY)
+ default:
+ in <- e
+ }
+ }
+ close(in)
+ }()
+
+ return &envPair{out, env.Draw()}
+}
+
+func FixedBottom(env gui.Env, minY int) gui.Env {
+ out, in := gui.MakeEventsChan()
+
+ go func() {
+ for e := range env.Events() {
+ var minX, dummy, maxX, maxY int
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &minX, &dummy, &maxX, &maxY):
+ in <- gui.Eventf("resize/%d/%d/%d/%d", minX, minY, maxX, maxY)
+ default:
+ in <- e
+ }
+ }
+ close(in)
+ }()
+
+ return &envPair{out, env.Draw()}
+}
+
+func EvenHorizontal(env gui.Env, minI, maxI, n int) gui.Env {
+ out, in := gui.MakeEventsChan()
+
+ go func() {
+ for e := range env.Events() {
+ var minX, minY, maxX, maxY int
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &minX, &minY, &maxX, &maxY):
+ minX, maxX := minX+(maxX-minX)*minI/n, minX+(maxX-minX)*maxI/n
+ in <- gui.Eventf("resize/%d/%d/%d/%d", minX, minY, maxX, maxY)
+ default:
+ in <- e
+ }
+ }
+ close(in)
+ }()
+
+ return &envPair{out, env.Draw()}
+}
diff --git a/examples/imageviewer/theme.go b/examples/imageviewer/theme.go
new file mode 100644
index 0000000..e37c0c0
--- /dev/null
+++ b/examples/imageviewer/theme.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "image/color"
+
+ "golang.org/x/image/font"
+)
+
+type Theme struct {
+ Face font.Face
+
+ Background color.Color
+ Empty color.Color
+ Text color.Color
+ Highlight color.Color
+ ButtonUp color.Color
+ ButtonOver color.Color
+ ButtonDown color.Color
+}
diff --git a/examples/imageviewer/util.go b/examples/imageviewer/util.go
new file mode 100644
index 0000000..94f25fb
--- /dev/null
+++ b/examples/imageviewer/util.go
@@ -0,0 +1,104 @@
+package main
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+ "sync"
+
+ "github.com/golang/freetype/truetype"
+
+ "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()
+}
+
+func TTFToFace(ttf []byte, size float64) (font.Face, error) {
+ font, err := truetype.Parse(ttf)
+ if err != nil {
+ return nil, err
+ }
+ return &concurrentFace{face: truetype.NewFace(font, &truetype.Options{
+ Size: size,
+ })}, nil
+}
+
+func DrawText(text string, face font.Face, clr color.Color) image.Image {
+ drawer := &font.Drawer{
+ Src: &image.Uniform{clr},
+ Face: face,
+ Dot: fixed.P(0, 0),
+ }
+ b26_6, _ := drawer.BoundString(text)
+ bounds := image.Rect(
+ b26_6.Min.X.Floor(),
+ b26_6.Min.Y.Floor(),
+ b26_6.Max.X.Ceil(),
+ b26_6.Max.Y.Ceil(),
+ )
+ drawer.Dst = image.NewRGBA(bounds)
+ drawer.DrawString(text)
+ return drawer.Dst
+}
+
+func DrawCentered(dst draw.Image, r image.Rectangle, src image.Image, op draw.Op) {
+ if src == nil {
+ return
+ }
+ bounds := src.Bounds()
+ center := bounds.Min.Add(bounds.Max).Div(2)
+ target := r.Min.Add(r.Max).Div(2)
+ delta := target.Sub(center)
+ draw.Draw(dst, bounds.Add(delta).Intersect(r), src, bounds.Min, op)
+}
+
+func DrawLeftCentered(dst draw.Image, r image.Rectangle, src image.Image, op draw.Op) {
+ if src == nil {
+ return
+ }
+ bounds := src.Bounds()
+ leftCenter := 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(leftCenter)
+ draw.Draw(dst, bounds.Add(delta).Intersect(r), src, bounds.Min, op)
+}
diff --git a/examples/imageviewer/viewer.go b/examples/imageviewer/viewer.go
new file mode 100644
index 0000000..e783d78
--- /dev/null
+++ b/examples/imageviewer/viewer.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+ "image"
+ "image/draw"
+ "os"
+
+ "github.com/faiface/gui"
+
+ _ "image/gif"
+ _ "image/jpeg"
+ _ "image/png"
+
+ _ "golang.org/x/image/bmp"
+)
+
+func Viewer(env gui.Env, theme *Theme, view <-chan string) {
+ redraw := func(r image.Rectangle, img image.Image) func(draw.Image) image.Rectangle {
+ return func(drw draw.Image) image.Rectangle {
+ draw.Draw(drw, r, &image.Uniform{theme.Empty}, image.ZP, draw.Src)
+ DrawCentered(drw, r, img, draw.Over)
+ return r
+ }
+ }
+
+ invalid := DrawText("Invalid image", theme.Face, theme.Text)
+
+ var (
+ r image.Rectangle
+ img image.Image
+ )
+
+ for {
+ select {
+ case path := <-view:
+ func() {
+ f, err := os.Open(path)
+ if err != nil {
+ img = invalid
+ return
+ }
+ defer f.Close()
+ img, _, err = image.Decode(f)
+ if err != nil {
+ img = invalid
+ return
+ }
+ }()
+ env.Draw() <- redraw(r, img)
+
+ case e, ok := <-env.Events():
+ if !ok {
+ close(env.Draw())
+ return
+ }
+
+ var minX, minY, maxX, maxY int
+ switch {
+ case e.Matches("resize/%d/%d/%d/%d", &minX, &minY, &maxX, &maxY):
+ r = image.Rect(minX, minY, maxX, maxY)
+ env.Draw() <- redraw(r, img)
+ }
+ }
+ }
+}