// 14 march 2014 package ui import ( "fmt" "sync" "image" "unsafe" "reflect" ) // Area represents a blank canvas upon which programs may draw anything and receive arbitrary events from the user. // 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. // 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. // The size of an Area must be at least 1x1 (that is, neither its width nor its height may be zero or negative). // // To handle events to the Area, an Area must be paired with an AreaHandler. // See AreaHandler for details. // // Do not use an Area if you intend to read text. // 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. // [FOR FUTURE PLANNING Use TextArea instead, providing a TextAreaHandler.] // // To facilitate development and debugging, for the time being, Areas only work on GTK+. type Area struct { lock sync.Mutex created bool sysData *sysData handler AreaHandler initwidth int initheight int } // AreaHandler represents the events that an Area should respond to. // 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. // (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. // 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. // 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) } // img := image.NewRGBA(imgFromFile.Rect) // draw.Draw(img, img.Rect, imgFromFile, image.ZP, draw.Over) // // ... // func (h *myAreaHandler) Paint(rect image.Rectangle) *image.RGBA { // return img.SubImage(rect).(*image.RGBA) // } Paint(cliprect image.Rectangle) *image.RGBA // 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. // If repaint is true, the Area is marked as needing to be redrawn. // 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). Mouse(e MouseEvent) (repaint bool) // Key is called when the Area receives a keyboard event. // You are allowed to do nothing in this handler (to ignore keyboard events). // See KeyEvent for details. // If repaint is true, the Area is marked as needing to be redrawn. // 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) } // 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. // If additional buttons are supported, they will be returned with 4 being the first additional button. // 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. // 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. // The examples here are NOT a guarantee as to how many buttons maximum will be available on a given system. // (TODO find out if there's a way to query available button count) type MouseEvent struct { // Pos is the position of the mouse in the Area at the time of the event. 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. // If Down contains nonzero, the Area will also receive keyboard focus. Down uint // If the event was generated by a mouse button being released, Up contains the ID of that button. // Otherwise, Up contains 0. // If both Down and Up are 0, the event represents mouse movement (with optional held buttons for dragging; see below). // Down and Up shall not both be nonzero. Up uint // 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 -> ... 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. // Held will not include Down and Up. // 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. // // 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. 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 } // A KeyEvent represents a keypress in an Area. // // 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. // // If a key is pressed that is not supported by Key, ExtKey, // or Modifiers, no KeyEvent will be produced. type KeyEvent struct { // 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 // - ' ' (space) if the spacebar was pressed // - '\t' if Tab was pressed, regardless of Modifiers // - '\n' if the typewriter Enter key was pressed // - '\b' if the typewriter Backspace key was pressed // If this value is zero, see ExtKey. Key byte // If Key is zero, ExtKey contains a predeclared identifier // naming an extended key. See ExtKey for details. // If both Key and ExtKey are zero, a Modifier by itself // was pressed. Key and ExtKey will not both be nonzero. ExtKey ExtKey // 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. 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 } // ExtKey represents keys that are not in the typewriter section of the keyboard. type ExtKey uintptr const ( Escape ExtKey = iota + 1 Insert 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 _nextkeys // for sanity check ) // 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 } k := e.ExtKey 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 '/' } return 0 } // Modifiers indicates modifier keys being held during an event. // There is no way to differentiate between left and right modifier keys. // As such, what KeyEvents get sent if the user does something unusual with both of a certain modifier key at once is undefined. type Modifiers uintptr const ( 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 Shift // the Shift keys Super // the Super keys on platforms that have one, or the Windows keys on Windows, or the Command keys on Mac OS X ) 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)) } } // NewArea creates a new Area with the given size and handler. // It panics if handler is nil or if width or height is zero or negative. func NewArea(width int, height int, handler AreaHandler) *Area { checkAreaSize(width, height, "NewArea()") if handler == nil { panic("handler passed to NewArea() must not be nil") } return &Area{ sysData: mksysdata(c_area), handler: handler, initwidth: width, initheight: height, } } // SetSize sets the Area's internal drawing size. // It has no effect on the actual control size. // 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. // It panics if width or height is zero or negative. func (a *Area) SetSize(width int, height int) { a.lock.Lock() defer a.lock.Unlock() checkAreaSize(width, height, "Area.SetSize()") if a.created { a.sysData.setAreaSize(width, height) return } a.initwidth = width a.initheight = height } func (a *Area) make(window *sysData) error { a.lock.Lock() defer a.lock.Unlock() a.sysData.handler = a.handler err := a.sysData.make(window) if err != nil { return err } a.sysData.setAreaSize(a.initwidth, a.initheight) a.created = true return nil } func (a *Area) setRect(x int, y int, width int, height int, rr *[]resizerequest) { *rr = append(*rr, resizerequest{ sysData: a.sysData, x: x, y: y, width: width, height: height, }) } func (a *Area) preferredSize() (width int, height int) { return a.sysData.preferredSize() } // internal function, but shared by all system implementations: &img.Pix[0] is not necessarily the first pixel in the image func pixelDataPos(img *image.RGBA) int { return img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y) } func pixelData(img *image.RGBA) *uint8 { return &img.Pix[pixelDataPos(img)] } // 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 // 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) { var realbits []byte rbs := (*reflect.SliceHeader)(unsafe.Pointer(&realbits)) rbs.Data = memory rbs.Len = 4 * i.Rect.Dx() * i.Rect.Dy() rbs.Cap = rbs.Len p := pixelDataPos(i) q := 0 for y := i.Rect.Min.Y; y < i.Rect.Max.Y; y++ { nextp := p + i.Stride nextq := q + memstride 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 q = nextq } }