summaryrefslogtreecommitdiff
path: root/areahandler.go
diff options
context:
space:
mode:
Diffstat (limited to 'areahandler.go')
-rw-r--r--areahandler.go301
1 files changed, 301 insertions, 0 deletions
diff --git a/areahandler.go b/areahandler.go
new file mode 100644
index 0000000..0e06395
--- /dev/null
+++ b/areahandler.go
@@ -0,0 +1,301 @@
+// 13 december 2015
+
+package ui
+
+import (
+ "unsafe"
+)
+
+// #include "pkgui.h"
+import "C"
+
+// no need to lock this; only the GUI thread can access it
+var areahandlers = make(map[*C.uiAreaHandler]AreaHandler)
+
+// AreaHandler defines the functionality needed for handling events
+// from an Area. Each of the methods on AreaHandler is called from
+// the GUI thread, and every parameter (other than the Area itself)
+// should be assumed to only be valid during the life of the method
+// call (so for instance, do not save AreaDrawParams.AreaWidth, as
+// that might change without generating an event).
+//
+// Coordinates to Draw and MouseEvent are given in points. Points
+// are generic, floating-point, device-independent coordinates with
+// (0,0) at the top left corner. You never have to worry about the
+// mapping between points and pixels; simply draw everything using
+// points and you get nice effects like looking sharp on high-DPI
+// monitors for free. Proper documentation on the matter is being
+// written. In the meantime, there are several referenes to this kind of
+// drawing, most notably on Apple's website: https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html#//apple_ref/doc/uid/TP40012302-CH4-SW1
+//
+// For a scrolling Area, points are automatically offset by the scroll
+// position. So if the mouse moves to position (5,5) while the
+// horizontal scrollbar is at position 10 and the horizontal scrollbar is
+// at position 20, the coordinate stored in the AreaMouseEvent
+// structure is (15,25). The same applies to drawing.
+type AreaHandler interface {
+ // Draw is sent when a part of the Area needs to be drawn.
+ // dp will contain a drawing context to draw on, the rectangle
+ // that needs to be drawn in, and (for a non-scrolling area) the
+ // size of the area. The rectangle that needs to be drawn will
+ // have been cleared by the system prior to drawing, so you are
+ // always working on a clean slate.
+ //
+ // If you call Save on the drawing context, you must call Release
+ // before returning from Draw, and the number of calls to Save
+ // and Release must match. Failure to do so results in undefined
+ // behavior.
+ Draw(a *Area, dp *AreaDrawParams)
+
+ // MouseEvent is called when the mouse moves over the Area
+ // or when a mouse button is pressed or released. See
+ // AreaMouseEvent for more details.
+ //
+ // If a mouse button is being held, MouseEvents will continue to
+ // be generated, even if the mouse is not within the area. On
+ // some systems, the system can interrupt this behavior;
+ // see DragBroken.
+ MouseEvent(a *Area, me *AreaMouseEvent)
+
+ // MouseCrossed is called when the mouse either enters or
+ // leaves the Area. It is called even if the mouse buttons are being
+ // held (see MouseEvent above). If the mouse has entered the
+ // Area, left is false; if it has left the Area, left is true.
+ //
+ // If, when the Area is first shown, the mouse is already inside
+ // the Area, MouseCrossed will be called with left=false.
+ // TODO what about future shows?
+ MouseCrossed(a *Area, left bool)
+
+ // DragBroken is called if a mouse drag is interrupted by the
+ // system. As noted above, when a mouse button is held,
+ // MouseEvent will continue to be called, even if the mouse is
+ // outside the Area. On some systems, this behavior can be
+ // stopped by the system itself for a variety of reasons. This
+ // method is provided to allow your program to cope with the
+ // loss of the mouse in this case. You should cope by cancelling
+ // whatever drag-related operation you were doing.
+ //
+ // Note that this is only generated on some systems under
+ // specific conditions. Do not implement behavior that only
+ // takes effect when DragBroken is called.
+ DragBroken(a *Area)
+
+ // KeyEvent is called when a key is pressed while the Area has
+ // keyboard focus (if the Area has been tabbed into or if the
+ // mouse has been clicked on it). See AreaKeyEvent for specifics.
+ //
+ // Because some keyboard events are handled by the system
+ // (for instance, menu accelerators and global hotkeys), you
+ // must return whether you handled the key event; return true
+ // if you did or false if you did not. If you wish to ignore the
+ // keyboard outright, the correct implementation of KeyEvent is
+ // func (h *MyHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) {
+ // return false
+ // }
+ // DO NOT RETURN TRUE UNCONDITIONALLY FROM THIS
+ // METHOD. BAD THINGS WILL HAPPEN IF YOU DO.
+ KeyEvent(a *Area, ke *AreaKeyEvent) (handled bool)
+}
+
+func registerAreaHandler(ah AreaHandler) *C.uiAreaHandler {
+ uah := C.pkguiAllocAreaHandler()
+ areahandlers[uah] = ah
+ return uah
+}
+
+func unregisterAreaHandler(uah *C.uiAreaHandler) {
+ delete(areahandlers, uah)
+ C.pkguiFreeAreaHandler(uah)
+}
+
+// AreaDrawParams provides a drawing context that can be used
+// to draw on an Area and tells you where to draw. See AreaHandler
+// for introductory information.
+type AreaDrawParams struct {
+ // Context is the drawing context to draw on. See DrawContext
+ // for how to draw.
+ Context *DrawContext
+
+ // AreaWidth and AreaHeight provide the size of the Area for
+ // non-scrolling Areas. For scrolling Areas both values are zero.
+ //
+ // To reiterate the AreaHandler documentation, do NOT save
+ // these values for later; they can change without generating
+ // an event.
+ AreaWidth float64
+ AreaHeight float64
+
+ // These four fields define the rectangle that needs to be
+ // redrawn. The system will not draw anything outside this
+ // rectangle, but you can make your drawing faster if you
+ // also stay within the lines.
+ ClipX float64
+ ClipY float64
+ ClipWidth float64
+ ClipHeight float64
+}
+
+//export pkguiDoAreaHandlerDraw
+func pkguiDoAreaHandlerDraw(uah *C.uiAreaHandler, ua *C.uiArea, udp *C.uiAreaDrawParams) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ dp := &AreaDrawParams{
+ Context: &DrawContext{udp.Context},
+ AreaWidth: float64(udp.AreaWidth),
+ AreaHeight: float64(udp.AreaHeight),
+ ClipX: float64(udp.ClipX),
+ ClipY: float64(udp.ClipY),
+ ClipWidth: float64(udp.ClipWidth),
+ ClipHeight: float64(udp.ClipHeight),
+ }
+ ah.Draw(a, dp)
+}
+
+// TODO document all these
+//
+// TODO note that in the case of a drag, X and Y can be out of bounds, or in the event of a scrolling area, in places that are not visible
+type AreaMouseEvent struct {
+ X float64
+ Y float64
+
+ // AreaWidth and AreaHeight provide the size of the Area for
+ // non-scrolling Areas. For scrolling Areas both values are zero.
+ //
+ // To reiterate the AreaHandler documentation, do NOT save
+ // these values for later; they can change without generating
+ // an event.
+ AreaWidth float64
+ AreaHeight float64
+
+ Down uint
+ Up uint
+ Count uint
+ Modifiers Modifiers
+ Held []uint
+}
+
+func appendBits(out []uint, held C.uint64_t) []uint {
+ n := uint(1)
+ for i := 0; i < 64; i++ {
+ if held & 1 != 0 {
+ out = append(out, n)
+ }
+ held >>= 1
+ n++
+ }
+ return out
+}
+
+//export pkguiDoAreaHandlerMouseEvent
+func pkguiDoAreaHandlerMouseEvent(uah *C.uiAreaHandler, ua *C.uiArea, ume *C.uiAreaMouseEvent) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ me := &AreaMouseEvent{
+ X: float64(ume.X),
+ Y: float64(ume.Y),
+ AreaWidth: float64(ume.AreaWidth),
+ AreaHeight: float64(ume.AreaHeight),
+ Down: uint(ume.Down),
+ Up: uint(ume.Up),
+ Count: uint(ume.Count),
+ Modifiers: Modifiers(ume.Modifiers),
+ Held: make([]uint, 0, 64),
+ }
+ me.Held = appendBits(me.Held, ume.Held1To64)
+ ah.MouseEvent(a, me)
+}
+
+//export pkguiDoAreaHandlerMouseCrossed
+func pkguiDoAreaHandlerMouseCrossed(uah *C.uiAreaHandler, ua *C.uiArea, left C.int) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ ah.MouseCrossed(a, tobool(left))
+}
+
+//export pkguiDoAreaHandlerDragBroken
+func pkguiDoAreaHandlerDragBroken(uah *C.uiAreaHandler, ua *C.uiArea) {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ ah.DragBroken(a)
+}
+
+// TODO document all these
+type AreaKeyEvent struct {
+ Key rune
+ ExtKey ExtKey
+ Modifier Modifiers
+ Modifiers Modifiers
+ Up bool
+}
+
+//export pkguiDoAreaHandlerKeyEvent
+func pkguiDoAreaHandlerKeyEvent(uah *C.uiAreaHandler, ua *C.uiArea, uke *C.uiAreaKeyEvent) C.int {
+ ah := areahandlers[uah]
+ a := ControlFromLibui(uintptr(unsafe.Pointer(ua))).(*Area)
+ ke := &AreaKeyEvent{
+ Key: rune(uke.Key),
+ ExtKey: ExtKey(uke.ExtKey),
+ Modifier: Modifiers(uke.Modifier),
+ Modifiers: Modifiers(uke.Modifiers),
+ Up: tobool(uke.Up),
+ }
+ return frombool(ah.KeyEvent(a, ke))
+}
+
+// TODO document
+//
+// Note: these must be numerically identical to their libui equivalents.
+type Modifiers uint
+const (
+ Ctrl Modifiers = 1 << iota
+ Alt
+ Shift
+ Super
+)
+
+// TODO document
+//
+// Note: these must be numerically identical to their libui equivalents.
+type ExtKey int
+const (
+ Escape ExtKey = iota + 1
+ Insert // equivalent to "Help" on Apple keyboards
+ Delete
+ Home
+ End
+ PageUp
+ PageDown
+ Up
+ Down
+ Left
+ Right
+ F1 // F1..F12 are guaranteed to be consecutive
+ F2
+ F3
+ F4
+ F5
+ F6
+ F7
+ F8
+ F9
+ F10
+ F11
+ F12
+ N0 // numpad keys; independent of Num Lock state
+ N1 // N0..N9 are guaranteed to be consecutive
+ N2
+ N3
+ N4
+ N5
+ N6
+ N7
+ N8
+ N9
+ NDot
+ NEnter
+ NAdd
+ NSubtract
+ NMultiply
+ NDivide
+)