2014-03-14 15:43:46 -05:00
// 14 march 2014
package ui
import (
2014-04-10 19:42:01 -05:00
"fmt"
2014-03-14 15:43:46 -05:00
"sync"
"image"
2014-04-11 17:00:51 -05:00
"unsafe"
2014-04-11 20:30:19 -05:00
"reflect"
2014-03-14 15:43:46 -05:00
)
// Area represents a blank canvas upon which programs may draw anything and receive arbitrary events from the user.
2014-04-13 00:59:45 -05:00
// An Area has an explicit size, represented in pixels, that may be different from the size shown in its Window; Areas have both horizontal and vertical scrollbars that are hidden when not needed.
2014-03-15 16:27:42 -05:00
// The coordinate system of an Area always has an origin of (0,0) which maps to the top-left corner; all image.Points and image.Rectangles sent across Area's channels conform to this.
2014-04-10 19:42:01 -05:00
// The size of an Area must be at least 1x1 (that is, neither its width nor its height may be zero or negative).
2014-03-16 20:40:33 -05:00
//
// To handle events to the Area, an Area must be paired with an AreaHandler.
// See AreaHandler for details.
//
2014-03-23 16:08:46 -05:00
// Do not use an Area if you intend to read text.
2014-03-28 20:10:42 -05:00
// Area reads keys based on their position on a standard
// 101-key keyboard, and does no character processing.
// Character processing methods differ across operating
// systems; trying ot recreate these yourself is only going
// to lead to trouble.
2014-03-23 16:08:46 -05:00
// [Use TextArea instead, providing a TextAreaHandler.]
//
2014-03-23 19:54:11 -05:00
// To facilitate development and debugging, for the time being, Areas only work on GTK+.
2014-03-14 15:43:46 -05:00
type Area struct {
2014-03-23 19:54:11 -05:00
lock sync . Mutex
created bool
sysData * sysData
handler AreaHandler
initwidth int
initheight int
2014-03-14 15:43:46 -05:00
}
2014-03-16 20:40:33 -05:00
// AreaHandler represents the events that an Area should respond to.
2014-04-12 13:07:31 -05:00
// These methods are all executed on the main goroutine, not necessarily the same one that you created the AreaHandler in; you are responsible for the thread safety of any members of the actual type that implements ths interface.
2014-03-16 20:40:33 -05:00
// (Having to use this interface does not strike me as being particularly Go-like, but the nature of Paint makes channel-based event handling a non-option; in practice, deadlocks occur.)
type AreaHandler interface {
// Paint is called when the Area needs to be redrawn.
2014-03-29 18:02:09 -05:00
// The part of the Area that needs to be redrawn is stored in cliprect.
// Before Paint() is called, this region is cleared with a system-defined background color.
2014-03-16 20:40:33 -05:00
// You MUST handle this event, and you MUST return a valid image, otherwise deadlocks and panicking will occur.
// The image returned must have the same size as rect (but does not have to have the same origin points).
// Example:
// imgFromFile, _, err := image.Decode(file)
// if err != nil { panic(err) }
2014-04-11 12:54:50 -05:00
// img := image.NewRGBA(imgFromFile.Rect)
2014-03-16 20:40:33 -05:00
// draw.Draw(img, img.Rect, imgFromFile, image.ZP, draw.Over)
// // ...
2014-04-11 12:54:50 -05:00
// func (h *myAreaHandler) Paint(rect image.Rectangle) *image.RGBA {
// return img.SubImage(rect).(*image.RGBA)
2014-03-16 20:40:33 -05:00
// }
2014-04-11 12:54:50 -05:00
Paint ( cliprect image . Rectangle ) * image . RGBA
2014-03-16 20:40:33 -05:00
// Mouse is called when the Area receives a mouse event.
// You are allowed to do nothing in this handler (to ignore mouse events).
// See MouseEvent for details.
2014-03-27 19:31:23 -05:00
// If repaint is true, the Area is marked as needing to be redrawn.
2014-06-02 10:27:37 -05:00
// After handling the mouse event, package ui will decide whether to perform platform-dependent event chain continuation based on that platform's designated action (so it is not possible to override global mouse events this way).
2014-03-27 19:31:23 -05:00
Mouse ( e MouseEvent ) ( repaint bool )
2014-03-23 16:08:46 -05:00
// Key is called when the Area receives a keyboard event.
2014-06-02 10:27:37 -05:00
// You are allowed to do nothing in this handler (to ignore keyboard events).
2014-03-23 16:08:46 -05:00
// See KeyEvent for details.
2014-03-27 19:31:23 -05:00
// If repaint is true, the Area is marked as needing to be redrawn.
2014-06-02 10:27:37 -05:00
// After handling the key event, package ui will decide whether to perform platform-dependent event chain continuation based on that platform's designated action (so it is not possible to override global key events, such as Alt-Tab this way).
Key ( e KeyEvent ) ( repaint bool )
2014-03-14 15:43:46 -05:00
}
2014-03-15 20:36:10 -05:00
// MouseEvent contains all the information for a mous event sent by Area.Mouse.
// Mouse button IDs start at 1, with 1 being the left mouse button, 2 being the middle mouse button, and 3 being the right mouse button.
2014-04-29 15:26:29 -05:00
// If additional buttons are supported, they will be returned with 4 being the first additional button.
2014-05-07 16:51:04 -05:00
// For example, on Unix systems where mouse buttons 4 through 7 are pseudobuttons for the scroll wheel directions, the next button, button 8, will be returned as 4, 9 as 5, etc.
2014-04-29 15:26:29 -05:00
// The association between button numbers and physical buttons are system-defined.
// For example, on Windows, buttons 4 and 5 are mapped to what are internally referred to as "XBUTTON1" and "XBUTTON2", which often correspond to the dedicated back/forward navigation buttons on the sides of many mice.
2014-04-29 15:27:16 -05:00
// The examples here are NOT a guarantee as to how many buttons maximum will be available on a given system.
2014-04-29 11:54:15 -05:00
// (TODO find out if there's a way to query available button count)
2014-03-15 20:36:10 -05:00
type MouseEvent struct {
// Pos is the position of the mouse in the Area at the time of the event.
2014-03-15 21:29:47 -05:00
// TODO rename to Pt or Point?
2014-03-15 20:36:10 -05:00
Pos image . Point
// If the event was generated by a mouse button being pressed, Down contains the ID of that button.
// Otherwise, Down contains 0.
2014-04-14 22:11:28 -05:00
// If Down contains nonzero, the Area will also receive keyboard focus.
2014-03-15 20:36:10 -05:00
Down uint
// If the event was generated by a mouse button being released, Up contains the ID of that button.
// Otherwise, Up contains 0.
2014-05-10 13:27:29 -05:00
// If both Down and Up are 0, the event represents mouse movement (with optional held buttons for dragging; see below).
2014-03-16 20:40:33 -05:00
// Down and Up shall not both be nonzero.
2014-03-15 20:36:10 -05:00
Up uint
2014-05-23 22:48:17 -05:00
// If Down is nonzero, Count indicates the number of clicks: 1 for single-click, 2 for double-click, 3 for triple-click, and so on.
// The order of events will be Down:Count=1 -> Up -> Down:Count=2 -> Up -> Down:Count=3 -> Up -> ...
2014-03-15 20:36:10 -05:00
Count uint
// Modifiers is a bit mask indicating the modifier keys being held during the event.
Modifiers Modifiers
// Held is a slice of button IDs that indicate which mouse buttons are being held during the event.
2014-03-15 21:39:16 -05:00
// Held will not include Down and Up.
2014-05-07 16:51:04 -05:00
// Held will be sorted.
// Only buttons 1, 2, and 3 are guaranteed to be detected by Held properly; whether or not any others are is implementation-defined.
2014-05-10 13:27:29 -05:00
//
// If Held is non-empty but Up and Down are both zero, the mouse is being dragged, with all the buttons in Held being held.
// Whether or not a drag into an Area generates MouseEvents is implementation-defined.
// Whether or not a drag over an Area when the program is inactive generates MouseEvents is also implementation-defined.
// Moving the mouse over an Area when the program is inactive and no buttons are held will, however, generate MouseEvents.
2014-03-15 20:36:10 -05:00
Held [ ] uint
}
// HeldBits returns Held as a bit mask.
// Bit 0 maps to button 1, bit 1 maps to button 2, etc.
func ( e MouseEvent ) HeldBits ( ) ( h uintptr ) {
for _ , x := range e . Held {
h |= uintptr ( 1 ) << ( x - 1 )
}
return h
}
2014-03-23 16:08:46 -05:00
// A KeyEvent represents a keypress in an Area.
//
2014-03-28 20:10:42 -05:00
// Key presses are based on their positions on a standard
// 101-key keyboard found on most computers. The
// names chosen for keys here are based on their names
// on US English QWERTY keyboards; see Key for details.
2014-03-23 16:08:46 -05:00
//
2014-03-28 20:10:42 -05:00
// If a key is pressed that is not supported by Key, ExtKey,
2014-06-02 10:27:37 -05:00
// or Modifiers, no KeyEvent will be produced.
2014-03-23 16:08:46 -05:00
type KeyEvent struct {
2014-03-28 20:10:42 -05:00
// Key is a byte representing a character pressed
// in the typewriter section of the keyboard.
// The value, which is independent of whether the
// Shift key is held, is a constant with one of the
// following (case-sensitive) values, drawn according
// to the key's position on the keyboard.
// ` 1 2 3 4 5 6 7 8 9 0 - =
// q w e r t y u i o p [ ] \
// a s d f g h j k l ; '
// z x c v b n m , . /
// The actual key entered will be the key at the respective
// position on the user's keyboard, regardless of the actual
// layout. (Some keyboards move \ to either the row above
// or the row below but in roughly the same spot; this is
// accounted for. Some keyboards have an additonal key
// to the left of 'z' or additional keys to the right of '='; these
// cannot be read.)
// In addition, Key will contain
2014-03-23 16:08:46 -05:00
// - ' ' (space) if the spacebar was pressed
// - '\t' if Tab was pressed, regardless of Modifiers
2014-03-28 20:10:42 -05:00
// - '\n' if the typewriter Enter key was pressed
2014-03-23 16:08:46 -05:00
// - '\b' if the typewriter Backspace key was pressed
// If this value is zero, see ExtKey.
2014-03-28 20:10:42 -05:00
Key byte
2014-03-23 16:08:46 -05:00
2014-03-28 20:10:42 -05:00
// If Key is zero, ExtKey contains a predeclared identifier
2014-03-23 16:08:46 -05:00
// naming an extended key. See ExtKey for details.
2014-03-28 20:10:42 -05:00
// If both Key and ExtKey are zero, a Modifier by itself
// was pressed. Key and ExtKey will not both be nonzero.
2014-03-23 16:08:46 -05:00
ExtKey ExtKey
2014-05-29 10:42:17 -05:00
// If both Key and ExtKey are zero, Modifier will contain exactly one of its bits set, indicating which Modifier was pressed or released.
// As with Modifiers itself, there is no way to differentiate between left and right modifier keys.
// As such, the result of pressing and/or releasing both left and right of the same Modifier is system-defined.
// Furthermore, the result of holding down a Key or ExtKey, then pressing a Modifier, and then releasing the original key is system-defined.
// Under no condition shall Key, ExtKey, AND Modifier all be zero.
Modifier Modifiers
// Modifiers contains all the modifier keys currently being held at the time of the KeyEvent.
// If Modifier is nonzero, Modifiers will not contain Modifier itself.
2014-03-23 16:08:46 -05:00
Modifiers Modifiers
// If Up is true, the key was released; if not, the key was pressed.
// There is no guarantee that all pressed keys shall have
// corresponding release events (for instance, if the user switches
// programs while holding the key down, then releases the key).
// Keys that have been held down are reported as multiple
// key press events.
Up bool
}
2014-03-28 20:10:42 -05:00
// ExtKey represents keys that are not in the typewriter section of the keyboard.
2014-03-23 16:08:46 -05:00
type ExtKey uintptr
const (
Escape ExtKey = iota + 1
Insert
Delete
Home
End
PageUp
PageDown
Up
Down
Left
Right
2014-03-28 20:10:42 -05:00
F1 // F1..F12 are guaranteed to be consecutive
2014-03-23 16:08:46 -05:00
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
2014-03-29 12:01:42 -05:00
N0 // numpad keys; independent of Num Lock state
2014-03-28 20:10:42 -05:00
N1 // N0..N9 are guaranteed to be consecutive
N2
N3
N4
N5
N6
N7
N8
N9
NDot
NEnter
NAdd
NSubtract
NMultiply
NDivide
2014-03-29 12:01:42 -05:00
_nextkeys // for sanity check
2014-03-23 16:08:46 -05:00
)
2014-03-28 20:10:42 -05:00
// EffectiveKey returns e.Key if it is set.
// Otherwise, if e.ExtKey denotes a numpad key,
// EffectiveKey returns the equivalent e.Key value
// ('0'..'9', '.', '\n', '+', '-', '*', or '/').
// Otherwise, EffectiveKey returns zero.
func ( e KeyEvent ) EffectiveKey ( ) byte {
if e . Key != 0 {
return e . Key
}
2014-03-29 12:01:42 -05:00
k := e . ExtKey
2014-03-28 20:10:42 -05:00
switch {
case k >= N0 && k <= N9 :
return byte ( k - N0 ) + '0'
case k == NDot :
return '.'
case k == NEnter :
return '\n'
case k == NAdd :
return '+'
case k == NSubtract :
return '-'
case k == NMultiply :
return '*'
case k == NDivide :
return '/'
}
2014-03-29 12:01:42 -05:00
return 0
2014-03-28 20:10:42 -05:00
}
2014-03-23 16:08:46 -05:00
// Modifiers indicates modifier keys being held during an event.
2014-03-15 20:36:10 -05:00
// There is no way to differentiate between left and right modifier keys.
2014-03-30 18:57:24 -05:00
// As such, what KeyEvents get sent if the user does something unusual with both of a certain modifier key at once is (presently; TODO) undefined.
2014-03-15 20:36:10 -05:00
type Modifiers uintptr
const (
2014-05-16 17:16:25 -05:00
Ctrl Modifiers = 1 << iota // the keys labelled Ctrl or Control on all platforms
Alt // the keys labelled Alt or Option or Meta on all platforms
2014-03-15 20:36:10 -05:00
Shift // the Shift keys
2014-05-16 17:16:25 -05:00
Super // the Super keys on platforms that have one, or the Windows keys on Windows, or the Command keys on Mac OS X
2014-03-15 20:36:10 -05:00
)
2014-04-10 19:42:01 -05:00
func checkAreaSize ( width int , height int , which string ) {
if width <= 0 || height <= 0 {
panic ( fmt . Errorf ( "invalid size %dx%d in %s" , width , height , which ) )
}
}
2014-03-23 19:54:11 -05:00
// NewArea creates a new Area with the given size and handler.
2014-04-10 19:42:01 -05:00
// It panics if handler is nil or if width or height is zero or negative.
2014-03-23 19:54:11 -05:00
func NewArea ( width int , height int , handler AreaHandler ) * Area {
2014-04-10 19:42:01 -05:00
checkAreaSize ( width , height , "NewArea()" )
2014-03-16 20:40:33 -05:00
if handler == nil {
panic ( "handler passed to NewArea() must not be nil" )
}
2014-03-14 15:43:46 -05:00
return & Area {
2014-03-23 19:54:11 -05:00
sysData : mksysdata ( c_area ) ,
handler : handler ,
initwidth : width ,
initheight : height ,
2014-03-14 15:43:46 -05:00
}
}
2014-03-23 19:54:11 -05:00
// SetSize sets the Area's internal drawing size.
// It has no effect on the actual control size.
2014-04-12 13:07:31 -05:00
// SetSize is safe for concurrent use; if the Area is being repainted or is handling an event, SetSize will wait for that to complete before changing the Area's size.
2014-04-10 19:42:01 -05:00
// It panics if width or height is zero or negative.
2014-03-23 19:54:11 -05:00
func ( a * Area ) SetSize ( width int , height int ) {
a . lock . Lock ( )
defer a . lock . Unlock ( )
2014-04-10 19:42:01 -05:00
checkAreaSize ( width , height , "Area.SetSize()" )
2014-03-23 19:54:11 -05:00
if a . created {
a . sysData . setAreaSize ( width , height )
return
}
a . initwidth = width
a . initheight = height
}
2014-03-14 15:43:46 -05:00
func ( a * Area ) make ( window * sysData ) error {
a . lock . Lock ( )
defer a . lock . Unlock ( )
2014-03-16 20:40:33 -05:00
a . sysData . handler = a . handler
2014-04-01 15:43:56 -05:00
err := a . sysData . make ( window )
2014-03-14 15:43:46 -05:00
if err != nil {
return err
}
2014-03-23 19:54:11 -05:00
a . sysData . setAreaSize ( a . initwidth , a . initheight )
2014-03-14 15:43:46 -05:00
a . created = true
return nil
}
2014-03-17 20:09:03 -05:00
func ( a * Area ) setRect ( x int , y int , width int , height int , rr * [ ] resizerequest ) {
* rr = append ( * rr , resizerequest {
2014-03-17 19:42:36 -05:00
sysData : a . sysData ,
x : x ,
y : y ,
width : width ,
height : height ,
2014-03-17 20:09:03 -05:00
} )
2014-03-14 15:43:46 -05:00
}
func ( a * Area ) preferredSize ( ) ( width int , height int ) {
return a . sysData . preferredSize ( )
}
2014-04-09 17:26:20 -05:00
// internal function, but shared by all system implementations: &img.Pix[0] is not necessarily the first pixel in the image
2014-04-11 12:54:50 -05:00
func pixelDataPos ( img * image . RGBA ) int {
2014-04-09 17:26:20 -05:00
return img . PixOffset ( img . Rect . Min . X , img . Rect . Min . Y )
}
2014-04-11 12:54:50 -05:00
func pixelData ( img * image . RGBA ) * uint8 {
2014-04-09 17:26:20 -05:00
return & img . Pix [ pixelDataPos ( img ) ]
}
2014-04-11 17:00:51 -05:00
// some platforms require pixels in ARGB order in their native endianness (because they treat the pixel array as an array of uint32s)
// this does the conversion
2014-04-12 12:48:59 -05:00
// you need to convert somewhere (Windows and cairo give us memory to use; Windows has stride==width but cairo might not)
func toARGB ( i * image . RGBA , memory uintptr , memstride int ) {
2014-04-11 20:30:19 -05:00
var realbits [ ] byte
rbs := ( * reflect . SliceHeader ) ( unsafe . Pointer ( & realbits ) )
rbs . Data = memory
2014-04-20 22:29:38 -05:00
// TODO BUG (we're lucky this didn't break on GTK+) - use stride here
2014-04-11 20:30:19 -05:00
rbs . Len = 4 * i . Rect . Dx ( ) * i . Rect . Dy ( )
rbs . Cap = rbs . Len
2014-04-11 17:00:51 -05:00
p := pixelDataPos ( i )
q := 0
for y := i . Rect . Min . Y ; y < i . Rect . Max . Y ; y ++ {
nextp := p + i . Stride
2014-04-12 12:48:59 -05:00
nextq := q + memstride
2014-04-11 17:00:51 -05:00
for x := i . Rect . Min . X ; x < i . Rect . Max . X ; x ++ {
argb := uint32 ( i . Pix [ p + 3 ] ) << 24 // A
argb |= uint32 ( i . Pix [ p + 0 ] ) << 16 // R
argb |= uint32 ( i . Pix [ p + 1 ] ) << 8 // G
argb |= uint32 ( i . Pix [ p + 2 ] ) // B
// the magic of conversion
native := ( * [ 4 ] byte ) ( unsafe . Pointer ( & argb ) )
realbits [ q + 0 ] = native [ 0 ]
realbits [ q + 1 ] = native [ 1 ]
realbits [ q + 2 ] = native [ 2 ]
realbits [ q + 3 ] = native [ 3 ]
p += 4
q += 4
}
p = nextp
2014-04-12 12:48:59 -05:00
q = nextq
2014-04-11 17:00:51 -05:00
}
}