//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 10 Jun 08 Brian Frank Creation
//
using gfx
//
// TODO:
// Widgets:
// - ScrollPane
// Eventing
// - focus management
// Graphics:
// - affine transformations
//
**
** Widget is the base class for all UI widgets.
**
** See [pod doc]`pod-doc#widgets` for details.
**
@Js
abstract class Widget
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
**
** Internal constructor: subclass `Canvas` or `Pane`.
**
internal new make() {}
//////////////////////////////////////////////////////////////////////////
// State
//////////////////////////////////////////////////////////////////////////
**
** Enabled is used to control whether this widget can
** accept user input. Disabled controls are "grayed out".
**
native Bool enabled
**
** Controls whether this widget is visible or hidden.
**
native Bool visible
**
** Mouse cursor to use when the mouse passes over the control.
** If not specified cursor of the parent control will appear.
**
native Cursor? cursor
**
** Meta-data that can be used by `Pane` for layout.
**
Obj? layout := null
//////////////////////////////////////////////////////////////////////////
// Eventing
//////////////////////////////////////////////////////////////////////////
**
** Callback for key pressed event on this widget. To cease propagation
** and processing of the event, then [consume]`Event.consume` it.
**
** Event id fired:
** - `EventId.keyDown`
**
** Event fields:
** - `Event.keyChar`: unicode character represented by key event
** - `Event.key`: key code including the modifiers
**
once EventListeners onKeyDown()
{
EventListeners() { onModify = |->| { checkKeyListeners } }
}
internal native Void checkKeyListeners()
**
** Callback for key released events on this widget. To cease propagation
** and processing of the event, then [consume]`Event.consume` it.
**
** Event id fired:
** - `EventId.keyUp`
**
** Event fields:
** - `Event.keyChar`: unicode character represented by key event
** - `Event.key`: key code including the modifiers
**
once EventListeners onKeyUp()
{
EventListeners() { onModify = |->| { checkKeyListeners } }
}
**
** Callback for mouse button pressed event on this widget.
**
** Event id fired:
** - `EventId.mouseDown`
**
** Event fields:
** - `Event.pos`: coordinate of mouse
** - `Event.count`: number of clicks
** - `Event.key`: key modifiers
**
once EventListeners onMouseDown() { EventListeners() }
**
** Callback for mouse button released event on this widget.
**
** Event id fired:
** - `EventId.mouseUp`
**
** Event fields:
** - `Event.pos`: coordinate of mouse
** - `Event.count`: number of clicks
** - `Event.key`: key modifiers
**
once EventListeners onMouseUp() { EventListeners() }
**
** Callback when mouse enters this widget's bounds.
**
** Event id fired:
** - `EventId.mouseEnter`
**
** Event fields:
** - `Event.pos`: coordinate of mouse
**
once EventListeners onMouseEnter() { EventListeners() }
**
** Callback when mouse exits this widget's bounds.
**
** Event id fired:
** - `EventId.mouseExit`
**
** Event fields:
** - `Event.pos`: coordinate of mouse
**
once EventListeners onMouseExit() { EventListeners() }
**
** Callback when mouse hovers for a moment over this widget.
**
** Event id fired:
** - `EventId.mouseHover`
**
** Event fields:
** - `Event.pos`: coordinate of mouse
**
once EventListeners onMouseHover() { EventListeners() }
**
** Callback when mouse moves over this widget.
**
** Event id fired:
** - `EventId.mouseMove`
**
** Event fields:
** - `Event.pos`: coordinate of mouse
**
once EventListeners onMouseMove() { EventListeners() }
**
** Callback when mouse wheel is scrolled and this widget has focus.
**
** Event id fired:
** - `EventId.mouseWheel`
**
** Event fields:
** - `Event.pos`: coordinate of mouse
** - `Event.count`: positive or negative number of scroll
**
once EventListeners onMouseWheel() { EventListeners() }
**
** Callback for focus gained event on this widget.
**
** Event id fired:
** - `EventId.focus`
**
** Event fields:
** - none
**
once EventListeners onFocus()
{
EventListeners() { onModify = |->| { checkFocusListeners } }
}
internal native Void checkFocusListeners()
**
** Callback for focus lost event on this widget.
**
** Event id fired:
** - `EventId.blur`
**
** Event fields:
** - none
**
once EventListeners onBlur()
{
EventListeners() { onModify = |->| { checkFocusListeners } }
}
//////////////////////////////////////////////////////////////////////////
// Focus
//////////////////////////////////////////////////////////////////////////
**
** Return if this widget is the focused widget which
** is currently receiving all keyboard input.
**
native Bool hasFocus()
**
** Attempt for this widget to take the keyboard focus.
**
native Void focus()
//////////////////////////////////////////////////////////////////////////
// Bounds
//////////////////////////////////////////////////////////////////////////
**
** Position of this widget relative to its parent.
** If this a window, this is the position on the screen.
**
@Transient
native Point pos
**
** Size of this widget.
**
@Transient
native Size size
**
** Position and size of this widget relative to its parent.
** If this a window, this is the position on the screen.
**
Rect bounds
{
get { return Rect.makePosSize(pos, size) }
set { pos = it.pos; size = it.size }
}
**
** Get the position of this widget relative to the window.
** If not on mounted on the screen then return null.
**
native Point? posOnWindow()
**
** Get the position of this widget on the screen coordinate's
** system. If not on mounted on the screen then return null.
**
native Point? posOnDisplay()
//////////////////////////////////////////////////////////////////////////
// Widget Tree
//////////////////////////////////////////////////////////////////////////
**
** Get this widget's parent or null if not mounted.
**
@Transient Widget? parent { private set }
internal Void setParent(Widget p) { parent = p } // for Window.make
**
** Get this widget's parent window or null if not
** mounted under a Window widget.
**
Window? window()
{
Widget? x := this
while (x != null)
{
if (x is Window) return (Window)x
x = x.parent
}
return null
}
**
** Iterate the children widgets.
**
Void each(|Widget w, Int i| f)
{
kids.each(f)
}
**
** Get the children widgets.
**
Widget[] children() { return kids.ro }
**
** Add a child widget. If child is null, then do nothing.
** If child is already parented throw ArgErr. Return this.
**
@Operator virtual This add(Widget? child)
{
if (child == null) return this
if (child.parent != null)
throw ArgErr("Child already parented: $child")
child.parent = this
kids.add(child)
try { child.attach } catch (Err e) { e.trace }
return this
}
**
** Add all widgets in list by calling `add` on each widget.
** Return this.
**
virtual This addAll(Widget?[] children)
{
children.each |kid| { add(kid) }
return this
}
**
** Insert child index at given index. If child is already
** parented throw ArgErr. Returns this.
**
@NoDoc virtual This insert(Int index, Widget child)
{
if (child.parent != null)
throw ArgErr("Child already parented: $child")
child.parent = this
kids.insert(index, child)
try { child.attach } catch (Err e) { e.trace }
return this
}
**
** Insert all widgets in list by calling `index` on each
** widget. Returns this.
**
@NoDoc virtual This insertAll(Int index, Widget[] children)
{
children.each |kid,i| { insert(index+i, kid) }
return this
}
**
** Remove a child widget. If child is null, then do
** nothing. If this widget is not the child's current
** parent throw ArgErr. Return this.
**
virtual This remove(Widget? child)
{
if (child == null) return this
try { child.detach } catch (Err e) { e.trace }
if (kids.removeSame(child) == null)
throw ArgErr("not my child: $child")
child.parent = null
return this
}
**
** Remove all child widgets. Return this.
**
virtual This removeAll()
{
kids.dup.each |Widget kid| { remove(kid) }
return this
}
//////////////////////////////////////////////////////////////////////////
// Layout
//////////////////////////////////////////////////////////////////////////
**
** Relayout this widget. This method is called when something
** has changed and we need to recompute the layout of this
** widget's children. Return this.
**
native This relayout()
**
** Set this widget's size to its preferred size. Return this.
**
native This pack()
**
** Compute the preferred size of this widget. The hints indicate
** constraints the widget should consider in its calculations.
** If no constraints are known for width, then 'hints.w' will be
** null. If no constraints are known for height, then 'hints.h'
** will be null.
**
virtual native Size prefSize(Hints hints := Hints.defVal)
//////////////////////////////////////////////////////////////////////////
// Painting
//////////////////////////////////////////////////////////////////////////
**
** Repaint this widget. If the dirty rectangle is null,
** then the whole widget is repainted.
**
native Void repaint(Rect? dirty := null)
//////////////////////////////////////////////////////////////////////////
// Peer
//////////////////////////////////////////////////////////////////////////
** Is this widget attached to a native peer?
internal native Bool attached()
** Attach to a native peer
private native Void attach()
** Detach from native peer
private native Void detach()
//////////////////////////////////////////////////////////////////////////
// Private
//////////////////////////////////////////////////////////////////////////
@Transient
internal Widget[] kids := Widget[,]
}