diff options
| author | faiface <faiface2202@gmail.com> | 2019-05-03 23:55:33 +0200 |
|---|---|---|
| committer | faiface <faiface2202@gmail.com> | 2019-05-03 23:55:33 +0200 |
| commit | a7b47475c04f70a6d6d0026819ff3c2cfab0b60e (patch) | |
| tree | cb663e517d4d9951448a19307cfc6cdfe370cff9 | |
| parent | 91457661307f0bf38303c33136e2477001db478d (diff) | |
| download | gui-a7b47475c04f70a6d6d0026819ff3c2cfab0b60e.zip | |
add some documentation
| -rw-r--r-- | env.go | 20 | ||||
| -rw-r--r-- | event.go | 57 | ||||
| -rw-r--r-- | win/win.go | 69 |
3 files changed, 145 insertions, 1 deletions
@@ -5,6 +5,26 @@ import ( "image/draw" ) +// Env is the most important thing in this package. It is an interactive graphical +// environment, such as a window. +// +// It has two channels: Events() and Draw(). +// +// The Events() channel produces events, like mouse and keyboard presses, while the +// Draw() channel receives drawing functions. A drawing function draws onto the +// supplied draw.Image, which is the drawing area of the Env and returns a rectangle +// covering the whole part of the image that got changed. +// +// The Events() channel must be unlimited in capacity. Use MakeEventsChan() to create +// a channel of events with an unlimited capacity. +// +// The Draw() channel may be synchronous. +// +// Drawing functions sent to the Draw() channel are not guaranteed to be executed. +// +// Closing the Draw() channel results in closing the Env. The Env will subsequently +// close the Events() channel. On the other hand, when the Events() channel gets closed +// the user of the Env should subsequently close the Draw() channel. type Env interface { Events() <-chan Event Draw() chan<- func(draw.Image) image.Rectangle @@ -2,17 +2,74 @@ package gui import "fmt" +// Event is a string encoding of an event. This may sound dangerous at first, but +// it enables nice pattern matching. +// +// Here are some examples of events (wi=window, mo=mouse, kb=keyboard): +// +// wi/close +// mo/down/421/890 +// kb/type/98 +// resize/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) +// case event.Matches("mo/down/%d/%d", &x, &y): +// // mouse pressed on (x, y) +// case event.Matches("mo/up/%d/%d", &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", &w, &h): +// // environment resized to (w, h) +// } +// +// 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 +// 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...)) } +// 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 } +// MakeEventsChan implements a channel of events with an unlimited capacity. It does so +// by creating a goroutine that queues incoming events. Sending to this channel never blocks +// and no events get lost. +// +// The unlimited capacity channel is very suitable for delivering events because the consumer +// may be unavailable for some time (doing a heavy computation), but will get to the events +// later. +// +// An unlimited capacity channel has its dangers in general, but is completely fine for +// the purpose of delivering events. This is because the production of events is fairly +// infrequent and should never out-run their consumption in the long term. func MakeEventsChan() (<-chan Event, chan<- Event) { out, in := make(chan Event), make(chan Event) @@ -13,6 +13,7 @@ import ( "github.com/go-gl/glfw/v3.2/glfw" ) +// Option is a functional option to the window constructor New. type Option func(*options) type options struct { @@ -21,12 +22,14 @@ type options struct { resizable bool } +// Title option sets the title (caption) of the window. func Title(title string) Option { return func(o *options) { o.title = title } } +// Size option sets the width and height of the window. func Size(width, height int) Option { return func(o *options) { o.width = width @@ -34,12 +37,16 @@ func Size(width, height int) Option { } } +// Resizable option makes the window resizable by the user. func Resizable() Option { return func(o *options) { o.resizable = true } } +// New creates a new window with all the supplied options. +// +// The default title is empty and the default size is 640x480. func New(opts ...Option) (*Win, error) { o := options{ title: "", @@ -100,6 +107,9 @@ func makeGLFWWin(o *options) (*glfw.Window, error) { return w, nil } +// Win is an Env that handles an actual graphical window. +// +// It receives its events from the OS and it draws to the surface of the window. type Win struct { eventsOut <-chan gui.Event eventsIn chan<- gui.Event @@ -112,7 +122,10 @@ type Win struct { img *image.RGBA } -func (w *Win) Events() <-chan gui.Event { return w.eventsOut } +// Events returns the events channel of the window. +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 } func (w *Win) eventThread() { @@ -132,6 +145,60 @@ func (w *Win) eventThread() { w.eventsIn <- gui.Eventf("mo/move/%d/%d", moX, moY) }) + w.w.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, _ int, action glfw.Action, _ glfw.ModifierKey) { + k := -1 + + switch key { + case glfw.KeyLeft: + k = gui.KeyLeft + case glfw.KeyRight: + k = gui.KeyRight + case glfw.KeyUp: + k = gui.KeyUp + case glfw.KeyDown: + k = gui.KeyDown + case glfw.KeyEscape: + k = gui.KeyEscape + case glfw.KeySpace: + k = gui.KeySpace + case glfw.KeyBackspace: + k = gui.KeyBackspace + case glfw.KeyDelete: + k = gui.KeyDelete + case glfw.KeyEnter: + k = gui.KeyEnter + case glfw.KeyTab: + k = gui.KeyTab + case glfw.KeyHome: + k = gui.KeyHome + case glfw.KeyEnd: + k = gui.KeyEnd + case glfw.KeyPageUp: + k = gui.KeyPageUp + case glfw.KeyPageDown: + k = gui.KeyPageDown + case glfw.KeyLeftShift, glfw.KeyRightShift: + k = gui.KeyShift + case glfw.KeyLeftControl, glfw.KeyRightControl: + k = gui.KeyCtrl + case glfw.KeyLeftAlt, glfw.KeyRightAlt: + k = gui.KeyAlt + } + + if k == -1 { + return + } + + switch action { + case glfw.Press: + w.eventsIn <- gui.Eventf("kb/down/%d", k) + case glfw.Release: + w.eventsIn <- gui.Eventf("kb/up/%d", k) + case glfw.Repeat: + w.eventsIn <- gui.Eventf("kb/repeat/%d", k) + } + }) + w.w.SetCharCallback(func(_ *glfw.Window, r rune) { w.eventsIn <- gui.Eventf("kb/type/%d", r) }) |