data XYListener{
XYMouseObject object
XYListener next
XYListener prev
}
data GraphicsItem{
GraphicsObject object
GraphicsItem next
GraphicsItem prev
}
data ClickableItem{
ClickableObject object
ClickableItem next
ClickableItem prev
}
data GlobalHotKeyItem{
ClickableObject object
HotKey hotKeys[]
GlobalHotKeyItem next
GlobalHotKeyItem prev
}
component provides Window requires io.Output out, data.IntUtil iu, IOWindow, ui.ContextMenu, locale.KeyMapping keyMapping {
Thread waitThread
GraphicsItem buffer
GraphicsItem bufferEnd
ContextMenu contextMenu
ClickableObject contextOwner
HotKey focusHotKeys[]
KeyEventObject keyboardFocus
ClickableItem clickItems
ClickableItem clickItemsEnd
GlobalHotKeyItem hotKeyItems
GlobalHotKeyItem hotKeyItemsEnd
//we store the item on which a mouse button was pressed down, to know if the button is release on the same item (triggering a click)
GraphicsObject clickDown
XYListener mouseListeners
XYListener mouseListenersEnd
Mutex paintLock = new Mutex()
XYMouseObject mouseOver
int mouseX = 0
int mouseY = 0
int currentX = 100
int currentY = 80
int currentWidth = 500
int currentHeight = 500
int windowedWidth = 500
int windowedHeight = 500
bool isMaximised
int restoredWidth
int restoredHeight
int restoredX
int restoredY
bool fullScreen
byte keyState //used for hot key triggers
//note if we currently have an override cursor in place, and if so also remember what the "base" cursor to return to is
bool cursorOverride
CursorSetEvent baseCursor
bool closed
Mutex closeLock = new Mutex()
Color borderColor = new Color(190, 190, 200, 255)
IOWindow canvas
eventsink Events(EventData ed)
{
if (ed.type == IOWindow.[mouseUp])
{
MouseEvent me = ed.details
mouseUpClick(me.x, me.y, me.button, me.exd1)
}
else if (ed.type == IOWindow.[mouseDown])
{
MouseEvent me = ed.details
mouseDown(me.x, me.y, me.button)
}
else if (ed.type == IOWindow.[mouseMove])
{
MouseEvent me = ed.details
mouseMove(me.x, me.y)
}
else if (ed.type == IOWindow.[mouseWheel])
{
MouseEvent me = ed.details
mouseWheel(me.x, me.exd1, me.y, me.exd2)
}
else if (ed.type == IOWindow.[fileDrop])
{
DropEvent de = ed.details
dropFile(de.x, de.y, de.path)
}
else if (ed.type == IOWindow.[keyDown])
{
KeyEvent ke = ed.details
keyDown(ke.keyCode)
}
else if (ed.type == IOWindow.[keyUp])
{
KeyEvent ke = ed.details
keyUp(ke.keyCode)
}
else if (ed.type == IOWindow.[resizeWindow])
{
WH wh = ed.details
currentWidth = wh.width
currentHeight = wh.height
windowedWidth = currentWidth
windowedHeight = currentHeight
emitevent resize(wh)
postRepaint()
}
else if (ed.type == IOWindow.[moveWindow])
{
Point xy = ed.details
currentX = xy.x
currentY = xy.y
}
else if (ed.type == IOWindow.[closeWindow])
{
emitevent close()
}
}
eventsink PaintEvents(EventData ed)
{
paint(null)
}
eventsink ContextMenuEvents(EventData ed)
{
if (ed.type == ContextMenu.[repaint])
{
paint(null)
}
else if (ed.type == ContextMenu.[menuClick])
{
contextOwner.contextClick(ed.details)
stopevent ContextMenuEvents(contextMenu)
contextMenu = null
postRepaint()
}
}
eventsink FocusEvents(EventData ed)
{
if (ed.type == ClickableObject.[requestFocus])
{
changeFocusTo(ed.source)
}
else if (ed.type == ClickableObject.[contextMenuOn])
{
GraphicsObject srcObj = ed.source
contextOwner = ed.source
ContextMenuSpec spec = ed.details
if (spec.owner != null) contextOwner = spec.owner
//create contextMenu, based on spec
contextMenu = new ContextMenu()
contextMenu.setPosition(spec.xAnchor + srcObj.getPosition().x, spec.yAnchor + srcObj.getPosition().y)
contextMenu.setItems(spec.items)
sinkevent ContextMenuEvents(contextMenu)
postRepaint()
}
else if (ed.type == ClickableObject.[contextMenuOff])
{
stopevent ContextMenuEvents(contextMenu)
contextMenu = null
postRepaint()
}
}
eventsink CursorEvents(EventData ed)
{
if (ed.type == XYMouseObject.[setCursor])
{
CursorSetEvent cse = ed.details
if (!cursorOverride)
{
canvas.setCursor(cse.type, cse.cursor)
}
baseCursor = cse
}
}
Window:Window(char title[])
{
canvas = new IOWindow()
canvas.setTitle(title)
repaintOn = true
sinkevent Events(canvas)
}
void Window:setVisible(bool b)
{
canvas.setVisible(b)
}
void Window:setResizable(bool b)
{
canvas.setResizable(b)
}
void Window:setFullScreen(bool b)
{
canvas.setFullScreen(b)
fullScreen = b
if (b)
{
windowedWidth = currentWidth
windowedHeight = currentHeight
WH sz = canvas.getResolution()
currentWidth = sz.width
currentHeight = sz.height
}
else
{
currentWidth = windowedWidth
currentHeight = windowedHeight
}
postRepaint()
}
void Window:setTitle(char title[])
{
canvas.setTitle(title)
}
void Window:setIcon(PixelMap p)
{
canvas.setIcon(p)
}
void Window:setCursor(byte cursorType, Cursor c)
{
if (cursorType != IOWindow.CURSOR_DEFAULT)
{
cursorOverride = true
canvas.setCursor(cursorType, c)
}
else
{
cursorOverride = false
if (baseCursor != null)
{
canvas.setCursor(baseCursor.type, baseCursor.cursor)
}
else
{
canvas.setCursor(IOWindow.CURSOR_DEFAULT)
}
}
}
void Window:setPosition(int x, int y)
{
currentX = x
currentY = y
canvas.setPosition(x, y)
}
void Window:setSize(int x, int y)
{
if (!fullScreen)
{
currentWidth = x
currentHeight = y
windowedWidth = currentWidth
windowedHeight = currentHeight
canvas.setSize(x, y)
}
else
{
WH sz = canvas.getResolution()
currentWidth = sz.width
currentHeight = sz.height
}
paint(null)
}
WH Window:getSize()
{
return new WH(currentWidth, currentHeight)
}
PixelMap Window:getPixels()
{
return canvas.getPixels()
}
void Window:close()
{
canvas.close()
mutex(closeLock)
{
if (waitThread != null)
{
waitThread.signal()
}
closed = true
}
}
void Window:addObject(GraphicsObject go, opt bool globalHotKeys)
{
GraphicsItem newItem = new GraphicsItem(go)
if (buffer == null)
buffer = newItem
else
bufferEnd.next = newItem
newItem.prev = bufferEnd
bufferEnd = newItem
if (go hastype ClickableObject)
{
ClickableObject ko = go
addClickTarget(go, globalHotKeys)
sinkevent FocusEvents(ko)
}
if (go hastype XYMouseObject)
{
addMouseEventTarget(go)
XYMouseObject mo = go
sinkevent CursorEvents(mo)
}
sinkevent PaintEvents(go)
paint(null)
}
void Window:remObject(GraphicsObject go)
{
for (GraphicsItem bi = buffer; bi != null; bi = bi.next)
{
if (bi.object === go)
{
GraphicsItem td = bi
stopevent PaintEvents(go)
if (td.prev == null)
buffer = td.next
else
td.prev.next = td.next
if (td.next == null)
bufferEnd = td.prev
else
td.next.prev = td.prev
if (go hastype ClickableObject)
{
remClickTarget(go)
}
if (go hastype XYMouseObject)
{
remMouseEventTarget(go)
XYMouseObject mo = go
stopevent CursorEvents(mo)
}
break
}
}
paint(null)
}
GraphicsObjectItem[] Window:getObjects()
{
return null
}
void Window:clearObjects()
{
GraphicsItem iw = buffer
while (iw != null)
{
GraphicsItem td = iw
iw = iw.next
td.next = null
td.prev = null
}
buffer = null
bufferEnd = null
ClickableItem ci = clickItems
while (ci != null)
{
ClickableItem td = ci
ci = ci.next
td.next = null
td.prev = null
}
clickItems = null
clickItemsEnd = null
XYListener mi = mouseListeners
while (mi != null)
{
XYListener td = mi
mi = mi.next
td.next = null
td.prev = null
}
mouseListeners = null
mouseListenersEnd = null
postRepaint()
}
void addClickTarget(store ClickableObject co, bool globalHotKeys)
{
ClickableItem nci = new ClickableItem(co)
if (clickItems == null)
clickItems = nci
else
clickItemsEnd.next = nci
nci.prev = clickItemsEnd
clickItemsEnd = nci
if (globalHotKeys)
{
GlobalHotKeyItem gci = new GlobalHotKeyItem(co, co.getHotKeys())
if (hotKeyItems == null)
hotKeyItems = gci
else
hotKeyItemsEnd.next = gci
gci.prev = hotKeyItemsEnd
hotKeyItemsEnd = gci
}
}
void remClickTarget(ClickableObject o)
{
for (ClickableItem ci = clickItems; ci != null; ci = ci.next)
{
if (ci.object === o)
{
ClickableItem td = ci
if (td.prev == null)
clickItems = td.next
else
td.prev.next = td.next
if (td.next == null)
clickItemsEnd = td.prev
else
td.next.prev = td.prev
break
}
}
for (GlobalHotKeyItem ci = hotKeyItems; ci != null; ci = ci.next)
{
if (ci.object === o)
{
GlobalHotKeyItem td = ci
if (td.prev == null)
hotKeyItems = td.next
else
td.prev.next = td.next
if (td.next == null)
hotKeyItemsEnd = td.prev
else
td.next.prev = td.prev
break
}
}
}
void addMouseEventTarget(store XYMouseObject m)
{
XYListener nci = new XYListener(m)
if (mouseListeners == null)
mouseListeners = nci
else
mouseListenersEnd.next = nci
nci.prev = mouseListenersEnd
mouseListenersEnd = nci
}
void remMouseEventTarget(XYMouseObject m)
{
for (XYListener ci = mouseListeners; ci != null; ci = ci.next)
{
if (ci.object === m)
{
XYListener td = ci
if (td.prev == null)
mouseListeners = td.next
else
td.prev.next = td.next
if (td.next == null)
mouseListenersEnd = td.prev
else
td.next.prev = td.prev
break
}
}
}
Rect Window:getRect()
{
return new Rect(currentX, currentY, currentWidth, currentHeight)
}
Point Window:getPosition()
{
return new Point(currentX, currentY)
}
void Window:setBackground(Color c)
{
canvas.setBackground(c)
}
Color Window:getBackground()
{
return null
}
void Window:paint(Canvas c)
{
mutex(paintLock)
{
if (repaintOn)
{
for (GraphicsItem bi = buffer; bi != null; bi = bi.next)
{
bi.object.paint(canvas)
}
}
canvas.rectOutline(new Rect2D(0, 0, currentWidth, currentHeight, borderColor))
if (contextMenu != null)
{
contextMenu.paint(canvas)
}
canvas.paint()
}
}
WH Window:getPreferredSize()
{
return new WH(currentWidth, currentHeight)
}
Rect Window:getBounds()
{
return new Rect(0, 0, currentWidth, currentHeight)
}
void Window:postRepaint()
{
paint(null)
}
void changeFocusTo(store KeyEventObject ko)
{
if (keyboardFocus !== ko)
{
if (keyboardFocus != null)
keyboardFocus.loseFocus()
}
ko.recvFocus()
focusHotKeys = ko.getHotKeys()
keyboardFocus = ko
}
void Window:click(int x, int y, int button)
{
if (contextMenu != null)
{
Rect r = contextMenu.getBounds()
if (clickDown === contextMenu && x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
contextMenu.click(x - r.x, y - r.y, button)
return
}
}
for (ClickableItem ci = clickItems; ci != null; ci = ci.next)
{
Rect r = ci.object.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
if (clickDown === ci.object)
{
if (contextMenu != null)
{
stopevent ContextMenuEvents(contextMenu)
contextMenu = null
postRepaint()
}
ci.object.click(x - r.x, y - r.y, button)
if (ci.object hastype KeyEventObject)
{
changeFocusTo(ci.object)
}
}
break
}
}
}
void multiClick(int x, int y, int button, int clicks)
{
for (ClickableItem ci = clickItems; ci != null; ci = ci.next)
{
Rect r = ci.object.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
if (clickDown === ci.object)
{
ci.object.clickMulti(x - r.x, y - r.y, button, clicks)
}
break
}
}
}
void Window:mouseDown(int x, int y, int button)
{
//first register what the mouse was pressed down on, for click handling
clickDown = null
if (contextMenu != null)
{
Rect r = contextMenu.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
clickDown = contextMenu
return
}
}
if (clickDown == null)
{
for (ClickableItem ci = clickItems; ci != null; ci = ci.next)
{
Rect r = ci.object.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
clickDown = ci.object
break
}
}
}
//now fire the actual mouse down event
for (XYListener ci = mouseListeners; ci != null; ci = ci.next)
{
Rect r = ci.object.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
if (clickDown == null) clickDown = ci.object
ci.object.mouseDown(x - r.x, y - r.y, button)
break
}
}
}
void mouseUpClick(int x, int y, int button, int clicks)
{
if (clickDown !== null)
{
if (clickDown hastype XYMouseObject)
{
XYMouseObject co = clickDown
Rect r = co.getBounds()
co.mouseUp(x - r.x, y - r.y, button)
}
}
else
{
for (XYListener ci = mouseListeners; ci != null; ci = ci.next)
{
Rect r = ci.object.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
//we only register a mouse-up if this was the thing that got the mouse-down
if (ci === clickDown)
{
ci.object.mouseUp(x - r.x, y - r.y, button)
}
else if (clickDown hastype XYMouseObject)
{
XYMouseObject co = clickDown
r = co.getBounds()
co.mouseUp(x - r.x, y - r.y, button)
}
break
}
}
}
if (clicks == 1)
click(x, y, button)
else
multiClick(x, y, button, clicks)
clickDown = null
}
void Window:mouseUp(int x, int y, int button)
{
}
void Window:mouseMove(int x, int y)
{
if (clickDown !== null)
{
if (clickDown hastype XYMouseObject)
{
XYMouseObject co = clickDown
Rect r = co.getBounds()
if (x < r.x) x = r.x
if (y < r.y) y = r.y
co.mouseMove(x - r.x, y - r.y)
}
}
else
{
mouseX = x
mouseY = y
bool overSomething
if (contextMenu != null)
{
Rect r = contextMenu.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
if (mouseOver !== contextMenu)
{
if (mouseOver != null)
{
mouseOver.mouseOut()
if (!cursorOverride) canvas.setCursor(IOWindow.CURSOR_DEFAULT)
baseCursor = null
}
mouseOver = contextMenu
mouseOver.mouseOver(x - r.x, y - r.y)
}
overSomething = true
contextMenu.mouseMove(x - r.x, y - r.y)
}
if (!overSomething && mouseOver != null)
{
mouseOver.mouseOut()
if (!cursorOverride) canvas.setCursor(IOWindow.CURSOR_DEFAULT)
baseCursor = null
mouseOver = null
}
return
}
for (XYListener ci = mouseListeners; ci != null; ci = ci.next)
{
Rect r = ci.object.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
if (mouseOver !== ci.object)
{
if (mouseOver != null)
{
mouseOver.mouseOut()
}
mouseOver = ci.object
CursorSetEvent cse = mouseOver.mouseOver(x - r.x, y - r.y)
if (cse != null)
canvas.setCursor(cse.type, cse.cursor)
else
canvas.setCursor(IOWindow.CURSOR_DEFAULT)
}
else
{
ci.object.mouseMove(x - r.x, y - r.y)
}
overSomething = true
break
}
}
if (!overSomething && mouseOver != null)
{
mouseOver.mouseOut()
if (!cursorOverride) canvas.setCursor(IOWindow.CURSOR_DEFAULT)
baseCursor = null
mouseOver = null
}
}
}
void Window:mouseWheel(int xAdd, int xSub, int yAdd, int ySub)
{
if (mouseOver != null)
{
mouseOver.mouseWheel(xAdd, xSub, yAdd, ySub)
}
}
void Window:dropFile(int x, int y, char path[])
{
for (XYListener ci = mouseListeners; ci != null; ci = ci.next)
{
Rect r = ci.object.getBounds()
if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height)
{
XYMouseObject co = ci.object
co.dropFile(x - r.x, y - r.y, path)
break
}
}
}
bool triggerHotKey(int keyCode)
{
//check global hotkeys
for (GlobalHotKeyItem ki = hotKeyItems; ki != null; ki = ki.next)
{
for (int i = 0; i < ki.hotKeys.arrayLength; i++)
{
if (keyState == ki.hotKeys[i].modifier && keyCode == ki.hotKeys[i].keyCode)
{
ki.object.hotKeyClick(ki.hotKeys[i])
return true
}
}
}
for (int i = 0; i < focusHotKeys.arrayLength; i++)
{
if (keyState == focusHotKeys[i].modifier && keyCode == focusHotKeys[i].keyCode)
{
keyboardFocus.hotKeyClick(focusHotKeys[i])
return true
}
}
return false
}
bool Window:keyDown(int keyCode)
{
//capture key state, for hot keys
int kCode
if ((kCode = keyMapping.getKeyCode(keyCode)) != KeyCode.OTHER)
{
if (kCode == KeyCode.SHIFT_LEFT || kCode == KeyCode.SHIFT_RIGHT)
{
keyState |= KeyState.KEY_SHIFT
}
if (kCode == KeyCode.CTRL_LEFT || kCode == KeyCode.CTRL_RIGHT)
{
keyState |= KeyState.KEY_CTRL
}
if (kCode == KeyCode.ALT_LEFT)
{
keyState |= KeyState.KEY_ALT
}
if (kCode == KeyCode.CMD)
{
keyState |= KeyState.KEY_CMD
}
}
//trigger a hot key, if any
if (!triggerHotKey(kCode))
{
//get the "keyboard focus" item, if any, and dispatch a key event to it
// (note this might be a panel, which in turn has a sub-item with keyboard focus)
if (keyboardFocus != null)
{
keyboardFocus.keyDown(keyCode)
}
}
return true
}
bool Window:keyUp(int keyCode)
{
//capture key state, for hot keys
int kCode
if ((kCode = keyMapping.getKeyCode(keyCode)) != KeyCode.OTHER)
{
if (kCode == KeyCode.SHIFT_LEFT || kCode == KeyCode.SHIFT_RIGHT)
{
keyState &= ~KeyState.KEY_SHIFT
}
if (kCode == KeyCode.CTRL_LEFT || kCode == KeyCode.CTRL_RIGHT)
{
keyState &= ~KeyState.KEY_CTRL
}
if (kCode == KeyCode.ALT_LEFT)
{
keyState &= ~KeyState.KEY_ALT
}
if (kCode == KeyCode.CMD)
{
keyState &= ~KeyState.KEY_CMD
}
}
//fire key to focused item, if any
if (keyboardFocus != null)
{
keyboardFocus.keyUp(keyCode)
}
return true
}
void Window:setDisabled(bool d)
{
disabled = d
postRepaint()
}
void Window:setFocus()
{
}
bool Window:recvFocus()
{
return false
}
HotKey[] Window:getHotKeys()
{
return null
}
void Window:loseFocus()
{
}
void Window:setRepaint(bool b)
{
repaintOn = b
if (repaintOn)
postRepaint()
}
}