diff options
| -rw-r--r-- | event.go | 65 | ||||
| -rw-r--r-- | examples/imageviewer/browser.go | 22 | ||||
| -rw-r--r-- | examples/imageviewer/button.go | 17 | ||||
| -rw-r--r-- | examples/imageviewer/main.go | 4 | ||||
| -rw-r--r-- | examples/imageviewer/splits.go | 55 | ||||
| -rw-r--r-- | examples/imageviewer/viewer.go | 7 | ||||
| -rw-r--r-- | examples/paint/main.go | 30 | ||||
| -rw-r--r-- | examples/pexeso/main.go | 11 | ||||
| -rw-r--r-- | mux.go | 10 | ||||
| -rw-r--r-- | win/events.go | 89 | ||||
| -rw-r--r-- | win/win.go | 97 |
11 files changed, 212 insertions, 195 deletions
@@ -1,59 +1,26 @@ package gui -import "fmt" +import ( + "fmt" + "image" +) -// Event is a string encoding of an event. This may sound dangerous at first, but -// it enables nice pattern matching. +// Event is something that can happen in an environment. // -// Here are some examples of events (wi=window, mo=mouse, kb=keyboard): -// -// wi/close -// mo/down/421/890 -// kb/type/98 -// resize/0/0/920/655 -// -// As you can see, the common way is to form the event string like a file path, -// from the most general information to the most specific. This allows pattern matching -// the prefix of the event, while ignoring the rest. -// -// Here's how to pattern match on an event: -// -// switch { -// case event.Matches("wi/close"): -// // window closed -// case event.Matches("mo/move/%d/%d", &x, &y): -// // mouse moved to (x, y) -// // mouse released on (x, y) -// case event.Matches("kb/type/%d", &r): -// // rune r typed on the keyboard (encoded as a number in the event string) -// case event.Matches("resize/%d/%d/%d/%d", &x0, &y0, &x1, &y1): -// // environment resized to (x0, y0, x1, y1) -// } -// -// And here's how to pattern match on the prefix of an event: -// -// switch { -// case event.Matches("mo/"): -// // this matches any mouse event -// case event.Matches("kb/"): -// // this matches any keyboard event -// } -type Event string +// This package defines only one kind of event: Resize. Other packages implementing environments +// may implement more kinds of events. For example, the win package implements all kinds of +// events for mouse and keyboard. +type Event interface { + String() string +} -// Eventf forms a new event. It works the same as fmt.Sprintf, except the return type is Event. -// -// For example: -// Eventf("mo/down/%d/%d", x, y) -func Eventf(format string, a ...interface{}) Event { - return Event(fmt.Sprintf(format, a...)) +// Resize is an event that happens when the environment changes the size of its drawing area. +type Resize struct { + image.Rectangle } -// Matches works the same as fmt.Sscanf, but returns a bool telling whether the match -// was successful. This makes it usable in a switch statement. See the doc for the Event -// type for an example. -func (e Event) Matches(format string, a ...interface{}) bool { - _, err := fmt.Sscanf(string(e), format, a...) - return err == nil +func (r Resize) String() string { + return fmt.Sprintf("resize/%d/%d/%d/%d", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y) } // MakeEventsChan implements a channel of events with an unlimited capacity. It does so diff --git a/examples/imageviewer/browser.go b/examples/imageviewer/browser.go index 5a79c3b..cdac56f 100644 --- a/examples/imageviewer/browser.go +++ b/examples/imageviewer/browser.go @@ -8,6 +8,7 @@ import ( "path/filepath" "github.com/faiface/gui" + "github.com/faiface/gui/win" "golang.org/x/image/math/fixed" ) @@ -111,21 +112,16 @@ func Browser(env gui.Env, theme *Theme, dir string, cd <-chan string, view chan< return } - var ( - x0, y0, x1, y1 int - x, y int - ) - - switch { - case e.Matches("resize/%d/%d/%d/%d", &x0, &y0, &x1, &y1): - r = image.Rect(x0, y0, x1, y1) + switch e := e.(type) { + case gui.Resize: + r = e.Rectangle env.Draw() <- redraw(r, selected, position, lineHeight, namesImage) - case e.Matches("mo/down/%d/%d", &x, &y): - if !image.Pt(x, y).In(r) { + case win.MoDown: + if !e.Point.In(r) { continue } - click := image.Pt(x, y).Sub(r.Min).Add(position) + click := e.Point.Sub(r.Min).Add(position) i := click.Y / lineHeight if i < 0 || i >= len(names) { continue @@ -157,8 +153,8 @@ func Browser(env gui.Env, theme *Theme, dir string, cd <-chan string, view chan< env.Draw() <- redraw(r, selected, position, lineHeight, namesImage) } - case e.Matches("mo/scroll/%d/%d", &x, &y): - newP := position.Sub(image.Pt(int(x*16), int(y*16))) + case win.MoScroll: + newP := position.Sub(e.Point.Mul(16)) if newP.X > namesImage.Bounds().Max.X-r.Dx() { newP.X = namesImage.Bounds().Max.X - r.Dx() } diff --git a/examples/imageviewer/button.go b/examples/imageviewer/button.go index f2a619b..0693e06 100644 --- a/examples/imageviewer/button.go +++ b/examples/imageviewer/button.go @@ -6,6 +6,7 @@ import ( "image/draw" "github.com/faiface/gui" + "github.com/faiface/gui/win" ) func Button(env gui.Env, theme *Theme, text string, action func()) { @@ -34,23 +35,21 @@ func Button(env gui.Env, theme *Theme, text string, action func()) { ) for e := range env.Events() { - var x, y, x0, y0, x1, y1 int - - switch { - case e.Matches("resize/%d/%d/%d/%d", &x0, &y0, &x1, &y1): - r = image.Rect(x0, y0, x1, y1) + switch e := e.(type) { + case gui.Resize: + r = e.Rectangle env.Draw() <- redraw(r, over, pressed) - case e.Matches("mo/down/%d/%d/left", &x, &y): - newPressed := image.Pt(x, y).In(r) + case win.MoDown: + newPressed := e.Point.In(r) if newPressed != pressed { pressed = newPressed env.Draw() <- redraw(r, over, pressed) } - case e.Matches("mo/up/%d/%d/left", &x, &y): + case win.MoUp: if pressed { - if image.Pt(x, y).In(r) { + if e.Point.In(r) { action() } pressed = false diff --git a/examples/imageviewer/main.go b/examples/imageviewer/main.go index eed3bb0..67f97fb 100644 --- a/examples/imageviewer/main.go +++ b/examples/imageviewer/main.go @@ -54,8 +54,8 @@ func run() { }) for e := range env.Events() { - switch { - case e.Matches("wi/close"): + switch e.(type) { + case win.WiClose: close(env.Draw()) } } diff --git a/examples/imageviewer/splits.go b/examples/imageviewer/splits.go index 871a3a5..f9daf2e 100644 --- a/examples/imageviewer/splits.go +++ b/examples/imageviewer/splits.go @@ -15,16 +15,15 @@ type envPair struct { 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, x1 int) gui.Env { +func FixedLeft(env gui.Env, maxX int) gui.Env { out, in := gui.MakeEventsChan() go func() { for e := range env.Events() { - var x0, y0, dummy, y1 int - switch { - case e.Matches("resize/%d/%d/%d/%d", &x0, &y0, &dummy, &y1): - in <- gui.Eventf("resize/%d/%d/%d/%d", x0, y0, x1, y1) - default: + if resize, ok := e.(gui.Resize); ok { + resize.Max.X = maxX + in <- resize + } else { in <- e } } @@ -34,16 +33,15 @@ func FixedLeft(env gui.Env, x1 int) gui.Env { return &envPair{out, env.Draw()} } -func FixedRight(env gui.Env, x0 int) gui.Env { +func FixedRight(env gui.Env, minX int) gui.Env { out, in := gui.MakeEventsChan() go func() { for e := range env.Events() { - var dummy, y0, x1, y1 int - switch { - case e.Matches("resize/%d/%d/%d/%d", &dummy, &y0, &x1, &y1): - in <- gui.Eventf("resize/%d/%d/%d/%d", x0, y0, x1, y1) - default: + if resize, ok := e.(gui.Resize); ok { + resize.Min.X = minX + in <- resize + } else { in <- e } } @@ -53,16 +51,15 @@ func FixedRight(env gui.Env, x0 int) gui.Env { return &envPair{out, env.Draw()} } -func FixedTop(env gui.Env, y1 int) gui.Env { +func FixedTop(env gui.Env, maxY int) gui.Env { out, in := gui.MakeEventsChan() go func() { for e := range env.Events() { - var x0, y0, x1, dummy int - switch { - case e.Matches("resize/%d/%d/%d/%d", &x0, &y0, &x1, &dummy): - in <- gui.Eventf("resize/%d/%d/%d/%d", x0, y0, x1, y1) - default: + if resize, ok := e.(gui.Resize); ok { + resize.Max.Y = maxY + in <- resize + } else { in <- e } } @@ -72,16 +69,15 @@ func FixedTop(env gui.Env, y1 int) gui.Env { return &envPair{out, env.Draw()} } -func FixedBottom(env gui.Env, y0 int) gui.Env { +func FixedBottom(env gui.Env, minY int) gui.Env { out, in := gui.MakeEventsChan() go func() { for e := range env.Events() { - var x0, dummy, x1, y1 int - switch { - case e.Matches("resize/%d/%d/%d/%d", &x0, &dummy, &x1, &y1): - in <- gui.Eventf("resize/%d/%d/%d/%d", x0, y0, x1, y1) - default: + if resize, ok := e.(gui.Resize); ok { + resize.Min.Y = minY + in <- resize + } else { in <- e } } @@ -96,12 +92,11 @@ func EvenHorizontal(env gui.Env, minI, maxI, n int) gui.Env { go func() { for e := range env.Events() { - var x0, y0, x1, y1 int - switch { - case e.Matches("resize/%d/%d/%d/%d", &x0, &y0, &x1, &y1): - x0, x1 := x0+(x1-x0)*minI/n, x0+(x1-x0)*maxI/n - in <- gui.Eventf("resize/%d/%d/%d/%d", x0, y0, x1, y1) - default: + if resize, ok := e.(gui.Resize); ok { + x0, x1 := resize.Min.X, resize.Max.X + resize.Min.X, resize.Max.X = x0+(x1-x0)*minI/n, x0+(x1-x0)*maxI/n + in <- resize + } else { in <- e } } diff --git a/examples/imageviewer/viewer.go b/examples/imageviewer/viewer.go index b70594e..6f68e15 100644 --- a/examples/imageviewer/viewer.go +++ b/examples/imageviewer/viewer.go @@ -53,11 +53,8 @@ func Viewer(env gui.Env, theme *Theme, view <-chan string) { close(env.Draw()) return } - - var x0, y0, x1, y1 int - switch { - case e.Matches("resize/%d/%d/%d/%d", &x0, &y0, &x1, &y1): - r = image.Rect(x0, y0, x1, y1) + if resize, ok := e.(gui.Resize); ok { + r = resize.Rectangle env.Draw() <- redraw(r, img) } } diff --git a/examples/paint/main.go b/examples/paint/main.go index cd11882..fec06c3 100644 --- a/examples/paint/main.go +++ b/examples/paint/main.go @@ -18,10 +18,9 @@ func ColorPicker(env gui.Env, pick chan<- color.Color, r image.Rectangle, clr co } for event := range env.Events() { - var x, y int - switch { - case event.Matches("mo/down/%d/%d", &x, &y): - if image.Pt(x, y).In(r) { + switch event := event.(type) { + case win.MoDown: + if event.Point.In(r) { pick <- clr } } @@ -43,7 +42,7 @@ func Canvas(env gui.Env, pick <-chan color.Color, r image.Rectangle) { var ( clr = color.Color(color.Black) pressed = false - px, py = 0, 0 + prev image.Point ) for { @@ -56,21 +55,20 @@ func Canvas(env gui.Env, pick <-chan color.Color, r image.Rectangle) { return } - var x, y int - switch { - case event.Matches("mo/down/%d/%d", &x, &y): - if image.Pt(x, y).In(r) { + switch event := event.(type) { + case win.MoDown: + if event.Point.In(r) { pressed = true - px, py = x, y + prev = event.Point } - case event.Matches("mo/up/%d/%d", &x, &y): + case win.MoUp: pressed = false - case event.Matches("mo/move/%d/%d", &x, &y): + case win.MoMove: if pressed { - x0, y0, x1, y1 := px, py, x, y - px, py = x, y + x0, y0, x1, y1 := prev.X, prev.Y, event.X, event.Y + prev = event.Point env.Draw() <- func(drw draw.Image) image.Rectangle { dc.SetColor(clr) @@ -118,8 +116,8 @@ func run() { go Canvas(mux.MakeEnv(), pick, image.Rect(0, 0, 750, 600)) for event := range env.Events() { - switch { - case event.Matches("wi/close"): + switch event.(type) { + case win.WiClose: close(env.Draw()) } } diff --git a/examples/pexeso/main.go b/examples/pexeso/main.go index 73b18fb..56bd6e6 100644 --- a/examples/pexeso/main.go +++ b/examples/pexeso/main.go @@ -68,10 +68,9 @@ func Tile(env gui.Env, pair chan PairMsg, r image.Rectangle, clr color.Color) { env.Draw() <- redraw(1.0) for event := range env.Events() { - var x, y int - switch { - case event.Matches("mo/down/%d/%d", &x, &y): - if image.Pt(x, y).In(r) { + switch event := event.(type) { + case win.MoDown: + if event.Point.In(r) { for c := 32; c >= 0; c-- { env.Draw() <- redraw(float64(c) / 32) time.Sleep(time.Second / 32 / 4) @@ -143,8 +142,8 @@ func run() { } for event := range env.Events() { - switch { - case event.Matches("wi/close"): + switch event.(type) { + case win.WiClose: close(env.Draw()) } } @@ -34,12 +34,10 @@ func NewMux(env Env) (mux *Mux, master Env) { go func() { for e := range env.Events() { - if e.Matches("resize/") { - mux.mu.Lock() - mux.lastResize = e - mux.mu.Unlock() - } mux.mu.Lock() + if resize, ok := e.(Resize); ok { + mux.lastResize = resize + } for _, eventsIn := range mux.eventsIns { eventsIn <- e } @@ -79,7 +77,7 @@ func (mux *Mux) makeEnv(master bool) Env { mux.eventsIns = append(mux.eventsIns, eventsIn) // make sure to always send a resize event to a new Env if we got the size already // that means it missed the resize event by the root Env - if mux.lastResize != "" { + if mux.lastResize != nil { eventsIn <- mux.lastResize } mux.mu.Unlock() diff --git a/win/events.go b/win/events.go new file mode 100644 index 0000000..90fab52 --- /dev/null +++ b/win/events.go @@ -0,0 +1,89 @@ +package win + +import ( + "fmt" + "image" +) + +// Button indicates a mouse button in an event. +type Button string + +// List of all mouse buttons. +const ( + ButtonLeft Button = "left" + ButtonRight Button = "right" + ButtonMiddle Button = "middle" +) + +// Key indicates a keyboard key in an event. +type Key string + +// List of all keyboard keys. +const ( + KeyLeft Key = "left" + KeyRight Key = "right" + KeyUp Key = "up" + KeyDown Key = "down" + KeyEscape Key = "escape" + KeySpace Key = "space" + KeyBackspace Key = "backspace" + KeyDelete Key = "delete" + KeyEnter Key = "enter" + KeyTab Key = "tab" + KeyHome Key = "home" + KeyEnd Key = "end" + KeyPageUp Key = "pageup" + KeyPageDown Key = "pagedown" + KeyShift Key = "shift" + KeyCtrl Key = "ctrl" + KeyAlt Key = "alt" +) + +type ( + // WiClose is an event that happens when the user presses the close button on the window. + WiClose struct{} + + // MoMove is an event that happens when the mouse gets moved across the window. + MoMove struct{ image.Point } + + // MoDown is an event that happens when a mouse button gets pressed. + MoDown struct { + image.Point + Button Button + } + + // MoUp is an event that happens when a mouse button gets released. + MoUp struct { + image.Point + Button Button + } + + // MoScroll is an event that happens on scrolling the mouse. + // + // The Point field tells the amount scrolled in each direction. + MoScroll struct{ image.Point } + + // KbType is an event that happens when a Unicode character gets typed on the keyboard. + KbType struct{ Rune rune } + + // KbDown is an event that happens when a key on the keyboard gets pressed. + KbDown struct{ Key Key } + + // KbUp is an event that happens when a key on the keyboard gets released. + KbUp struct{ Key Key } + + // KbRepeat is an event that happens when a key on the keyboard gets repeated. + // + // This happens when its held down for some time. + KbRepeat struct{ Key Key } +) + +func (wc WiClose) String() string { return "wi/close" } +func (mm MoMove) String() string { return fmt.Sprintf("mo/move/%d/%d", mm.X, mm.Y) } +func (md MoDown) String() string { return fmt.Sprintf("mo/down/%d/%d/%s", md.X, md.Y, md.Button) } +func (mu MoUp) String() string { return fmt.Sprintf("mo/up/%d/%d/%s", mu.X, mu.Y, mu.Button) } +func (ms MoScroll) String() string { return fmt.Sprintf("mo/scroll/%d/%d", ms.X, ms.Y) } +func (kt KbType) String() string { return fmt.Sprintf("kb/type/%d", kt.Rune) } +func (kd KbDown) String() string { return fmt.Sprintf("kb/down/%s", kd.Key) } +func (ku KbUp) String() string { return fmt.Sprintf("kb/up/%s", ku.Key) } +func (kr KbRepeat) String() string { return fmt.Sprintf("kb/repeat/%s", kr.Key) } @@ -127,27 +127,6 @@ func makeGLFWWin(o *options) (*glfw.Window, error) { // It receives its events from the OS and it draws to the surface of the window. // // Warning: only one window can be open at a time. This will be fixed. -// -// Here are all kinds of events that a window can produce, along with descriptions. -// Things enclosed in <> are values that are filled in. -// -// resize/<x0>/<y0>/<x1>/<y1> Window resized to (x0, y0, x1, y1). -// wi/close Window close button pressed. -// mo/move/<x>/<y> Mouse moved to (x, y). -// mo/down/<x>/<y>/<button> A mouse button pressed on (x, y). -// mo/up/<x>/<y>/<button> A mouse button released on (x, y). -// mo/scroll/<x>/<y> Mouse scrolled by (x, y). -// kb/type/<code> A unicode character typed on the keyboard. -// kb/down/<key> A key on the keyboard pressed. -// kb/up/<key> A key on the keyboard released. -// kb/repeat/<key> A key on the keyboard repeated (happens when held). -// -// <x0>, <y0>, <x1>, <y1>, <w>, <h>, <x>, <y>, and <code> are numbers (%d). -// <button> is one of: -// left right middle -// <key> is one of: -// left right up down escape space backspace delete enter -// tab home end pageup pagedown shift ctrl alt type Win struct { eventsOut <-chan gui.Event eventsIn chan<- gui.Event @@ -167,33 +146,33 @@ func (w *Win) Events() <-chan gui.Event { return w.eventsOut } // Draw returns the draw channel of the window. func (w *Win) Draw() chan<- func(draw.Image) image.Rectangle { return w.draw } -var buttonNames = map[glfw.MouseButton]string{ - glfw.MouseButtonLeft: "left", - glfw.MouseButtonRight: "right", - glfw.MouseButtonMiddle: "middle", +var buttons = map[glfw.MouseButton]Button{ + glfw.MouseButtonLeft: ButtonLeft, + glfw.MouseButtonRight: ButtonRight, + glfw.MouseButtonMiddle: ButtonMiddle, } -var keyNames = map[glfw.Key]string{ - glfw.KeyLeft: "left", - glfw.KeyRight: "right", - glfw.KeyUp: "up", - glfw.KeyDown: "down", - glfw.KeyEscape: "escape", - glfw.KeySpace: "space", - glfw.KeyBackspace: "backspace", - glfw.KeyDelete: "delete", - glfw.KeyEnter: "enter", - glfw.KeyTab: "tab", - glfw.KeyHome: "home", - glfw.KeyEnd: "end", - glfw.KeyPageUp: "pageup", - glfw.KeyPageDown: "pagedown", - glfw.KeyLeftShift: "shift", - glfw.KeyRightShift: "shift", - glfw.KeyLeftControl: "ctrl", - glfw.KeyRightControl: "ctrl", - glfw.KeyLeftAlt: "alt", - glfw.KeyRightAlt: "alt", +var keys = map[glfw.Key]Key{ + glfw.KeyLeft: KeyLeft, + glfw.KeyRight: KeyRight, + glfw.KeyUp: KeyUp, + glfw.KeyDown: KeyDown, + glfw.KeyEscape: KeyEscape, + glfw.KeySpace: KeySpace, + glfw.KeyBackspace: KeyBackspace, + glfw.KeyDelete: KeyDelete, + glfw.KeyEnter: KeyEnter, + glfw.KeyTab: KeyTab, + glfw.KeyHome: KeyHome, + glfw.KeyEnd: KeyEnd, + glfw.KeyPageUp: KeyPageUp, + glfw.KeyPageDown: KeyPageDown, + glfw.KeyLeftShift: KeyShift, + glfw.KeyRightShift: KeyShift, + glfw.KeyLeftControl: KeyCtrl, + glfw.KeyRightControl: KeyCtrl, + glfw.KeyLeftAlt: KeyAlt, + glfw.KeyRightAlt: KeyAlt, } func (w *Win) eventThread() { @@ -201,57 +180,57 @@ func (w *Win) eventThread() { w.w.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { moX, moY = int(x), int(y) - w.eventsIn <- gui.Eventf("mo/move/%d/%d", moX*w.ratio, moY*w.ratio) + w.eventsIn <- MoMove{image.Pt(moX*w.ratio, moY*w.ratio)} }) w.w.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) { - b, ok := buttonNames[button] + b, ok := buttons[button] if !ok { return } switch action { case glfw.Press: - w.eventsIn <- gui.Eventf("mo/down/%d/%d/%s", moX*w.ratio, moY*w.ratio, b) + w.eventsIn <- MoDown{image.Pt(moX*w.ratio, moY*w.ratio), b} case glfw.Release: - w.eventsIn <- gui.Eventf("mo/up/%d/%d/%s", moX*w.ratio, moY*w.ratio, b) + w.eventsIn <- MoUp{image.Pt(moX*w.ratio, moY*w.ratio), b} } }) w.w.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { - w.eventsIn <- gui.Eventf("mo/scroll/%d/%d", int(xoff), int(yoff)) + w.eventsIn <- MoScroll{image.Pt(int(xoff), int(yoff))} }) w.w.SetCharCallback(func(_ *glfw.Window, r rune) { - w.eventsIn <- gui.Eventf("kb/type/%d", r) + w.eventsIn <- KbType{r} }) w.w.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, _ int, action glfw.Action, _ glfw.ModifierKey) { - k, ok := keyNames[key] + k, ok := keys[key] if !ok { return } switch action { case glfw.Press: - w.eventsIn <- gui.Eventf("kb/down/%s", k) + w.eventsIn <- KbDown{k} case glfw.Release: - w.eventsIn <- gui.Eventf("kb/up/%s", k) + w.eventsIn <- KbUp{k} case glfw.Repeat: - w.eventsIn <- gui.Eventf("kb/repeat/%s", k) + w.eventsIn <- KbRepeat{k} } }) w.w.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { r := image.Rect(0, 0, width, height) w.newSize <- r - w.eventsIn <- gui.Eventf("resize/%d/%d/%d/%d", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y) + w.eventsIn <- gui.Resize{Rectangle: r} }) w.w.SetCloseCallback(func(_ *glfw.Window) { - w.eventsIn <- gui.Eventf("wi/close") + w.eventsIn <- WiClose{} }) r := w.img.Bounds() - w.eventsIn <- gui.Eventf("resize/%d/%d/%d/%d", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y) + w.eventsIn <- gui.Resize{Rectangle: r} for { select { |