is used in this document and examples because it is
short.
Using html tags, the structure of the interface (as well as a few
cosmetic options) can be specified directly. However, for any kind
of interaction, javascript is needed. It is comparable to glade xml
files.
Ids may be assigned to controls and used with root_panel.find_id():
// ...
function f()
{
var rp = ui.create_root_panel("ui");
rp.find_id("label").caption("this is a label");
}
The root element, here named "ui", serves as the root panel. After
creating the structure, canui will remove that element (and its
children) from the dom and replace it with a canvas element.
When errors are found in the structure (such as having a child element
inside a control that is not a container), the top level element (here
"ui") is replaced by a red string with the error message instead of a
canvas.
These are the recognized attributes:
data-type
The type of the control to create. For a data-type "x", canui will
try to do a "new ui.x()". Invalid if the type doesn't exist.
id
The id of the control in the structure. Can be empty.
data-layout
This string is eval'd and passed to container.layout(). Invalid if
the control is not a container.
data-layoutinfo
This string is eval'd and passed to container.add() when adding the
control to its parent. This is used when the parent's layout need
additional information, such as for a border layout:
data-options
This must be a JSON structure. It is eval'd and passed to
control.options().
These are the recognized events.
onclick
This string is eval'd and added to the 'clicked' signal of the
control. Invalid if the control doesn't have that signal.
onchange
This string is eval'd and added to the 'changed' signal of the
control. Invalid if the control doesn't have that signal.
Note that the string in an event is eval'd and must evaluate to a
function that will be called from the signal. Therefore,
onclick="f()"
will eval "f()" and add its return value to the signal. To call "f"
when something is clicked, do
onclick="f"
Finally, all the text between tags is trimmed and passed to the
caption() member function of the control, if not empty. If there is
a non-whitespace text as a child element of a control and that control
has no caption() member function, the structure is ill-formed:
text in textbox
invalid, image doesn't have caption()
3 Controls
======================================================================
In the following examples, 'rp' is a always a root_panel.
3.1 button
The button can either manage its label (with caption()) or use any
kind of control as its child (with label().) It supports the usual
interactions:
. hovering it changes its color
. pressing the left mouse button will show it as being pressed,
unless the mouse is not hovering anymore
. releasing the left mouse button while hovering will fire a
'clicked' signal
Other types of buttons include checkboxes and radios. Both are
considered "buttons" because they behave the same way: clicking them
changes their state and they handle moving the mouse in and out while
holding the left mouse button down.
Radio buttons are mutually exclusive within the same container. When
clicked, a radio button will walk all the children of its parent and
turn the radio buttons off.
Options:
Example:
function on_click()
{
}
function f(rp)
{
// regular button, calls on_click
rp.add(new ui.button({caption: "foo", clicked: on_click}));
// button, also calls on_click
var b = new ui.button({caption: "bar"});
b.clicked.add(on_click);
rp.add(b);
// changes the caption
b.caption("frob");
}
3.2 combobox
The combobox has a textbox that be either editable or not
("unresponsive"). When unresponsive, clicking the textbox opens the
list.
When an item is clicked, it is selected, its caption is shown in
the textbox and the list is closed.
When the combobox has focus, items can be navigated with the up/down
arrows. Items can also be searched for by typing; if more than one
second elapses between two keys, the search string is reset.
Options:
. dropstyle (edit, list), default: list
if 'edit', the textbox is editable
Example:
function f(rp)
{
var c = new ui.combobox();
for (var i=0; i<10; ++i)
c.add_item("item " + (i + 1));
rp.add(c);
}
3.3 image
This is a simple control that displays an Image object. It can also
have a second image that will be overlaid on the first, with a
possibly different alpha value.
Both the main and overlay images will be drawn when loaded.
It will automatically create grayscale versions of the images that
will be used when the control is disabled. Whether the overlay is
also grayed depends on the options.
The control tries to be smart about images failing to load. See
[5.2.1].
Note that disabled images cannot be created if the image comes from
another origin [w3c-origin]. See [5.2.2].
Options:
. margin (positive integer), default: 5
space around the image
. halign (left, center, right), default: center
horizontal alignement of the image
. valign (top, center, bottom), default: center
vertical alignment of the image
. overlay_halign (left, center, right), default: center
horizontal alignment of the overlay (if present) over the main
image (not the available space, ignored if both images have the
same size)
. overlay_valign (top, center, bottom), default: center
vertical alignment of the overlay (if present) over the main
image (not the available space, ignored if both images have the
same size)
. overlay_grayed (true/false), default: true
when the control is disabled, also gray out the overlay
. alpha ([0.0, 1.0]), default: 1.0
transparency of the overlay
Example:
// an image control with image.png on it
//
function f(rp)
{
var i = new Image();
i.src = "image.png";
require_loaded(i, function()
{
rp.add(new ui.image(i));
});
}
// an image control with image.png and overlay.png on it; this
// assumes 'i' and 'o' are two already loaded images
//
function g(rp, i, o)
{
// the image will be aligned to the left side of the control (if
// the control is bigger than the image); disabling the control
// will show a grayed image 'i', but a full color 'o'; the overlay
// will be drawn at 0.5 alpha
rp.add(new ui.image(i, o,
{halign: left, overlay_grayed: false, alpha: 0.5}));
}
3.4 label
Draws a simple text caption.
Options:
. halign (left, center, right), default: left
horizontal position of the rectangle bounding both the image and
the caption
. valign (top, center, bottom), default: center
vertical position of the rectangle bounding both the image and
the caption
. color (color object), default: ui.theme.text_color()
text color
Example:
function f(rp)
{
// label with a simple caption
rp.add(new ui.label("caption"));
}
3.5 separator
This is just a horizontal or vertical line.
Options:
. size (positive integer), default: 1
width or height in pixels of the line
. margin (positive integer), default: 5
space around the line
. color (color object), default: black
color of the line
Example:
function f(rp)
{
// two pixel high line, with 10 pixels of margin on each side
rp.add(new ui.separator(2, {margin: 10}));
}
3.6 link
This is a label with a blue and underlined font. The cursor when
hovering is the usual "hand". Clicking the link will open the
specified url. The url is also displayed as a tooltip.
Options:
. url (string), default: ""
the url to open when clicked
. target (string), default: "_blank"
the target in which to open the link; this is the same as the
"target" attribute of the "a" element:
_blank: new window/tab
_parent: parent frame
_self: current window/tab
_top: top frame
anything else: the name of a window
3.7 list
This is a list of items with columns. It only supports "details" view
for now, with single and multiple selections.
Adding a large number of items works fine. In fact, adding 200k+ items
in firefox 5 takes less than 2 seconds with the proper options.
Chrome and IE are a lot slower with 10+ seconds. Once the items are
loaded, scrolling and selection is instant. Sorting will of course
always be slow.
"column_resize" needs to be either "none" or "header". Setting it to
"auto" will loop through all the items and use measureText() to find
their width, which takes forever. The list also needs to be frozen so
that updates are deferred.
var list = new ui.list({column_resize: "none"});
// two columns, first one has a width of 100px and the second takes
// the remaining space (because expand_header is set by default)
list.add_column("col 1", 100);
list.add_column("col 2");
list.frozen(true);
for (var i=0; i<200000; ++i)
list.add_item(["item " + i, "col " + i]);
list.frozen(false);
Options:
. item_height (positive integer), default: g_line_height + 4
height in pixels of an item; the text will be vertically
centered
. padding (positive integer), default: 5
space on the left side of the items
. column_resize (none, content, header, auto), default: auto
specifies how columns are resized: "content" uses the width of
the largest item and therefore might be slow for very long
lists; "header" uses the width of the column caption; "none"
requires the user to set the width manually; "auto" uses the
largest between content and header
. show_header (true/false), default: true
whether the column headers should be visible
. expand_header (true/false), default: false
whether the last column should be expanded to take the remaining
horizontal space
. item_scroll (true/false), default: false
whether scrolling is per-pixel or per-item
Example:
function f(rp)
{
var list = new ui.list();
list.add_column("col 1");
list.add_column("col 2");
for (var i=0; i<10; ++i)
list.add_item(["item " + i, "subitem " + 1]);
rp.add(list);
}
3.8 menu
todo
3.9 panel
This is a container with a background. It is mainly used when multiple
layouts are needed.
Options:
none
Example:
function f(rp)
{
// a panel with a button in it
var p = new ui.panel();
p.add(new ui.button({caption: "foo"}));
// a panel within a panel with different layouts
//
// (spacing is exaggerated)
// +---root_panel-----------------------------------+
// | |
// | +---p1-(border_layout)---------------------+ |
// | | | |
// | | +---p2-(horizontal_layout)-----------+ | |
// | | | | | |
// | | | +button-+ +button-+ | | |
// | | | | foo | | bar | | | |
// | | | +-------+ +-------+ | | |
// | | | | | |
// | | +------------------------------------+ | |
// | | | |
// | | +---button---------------------------+ | |
// | | | | | |
// | | | | | |
// | | | frob | | |
// | | | | | |
// | | | | | |
// | | +------------------------------------+ | |
// | | | |
// | +------------------------------------------+ |
// | |
// +------------------------------------------------+
var p1 = new ui.panel(new ui.border_layout());
var p2 = new ui.panel(new ui.horizontal_layout());
p2.add(new ui.button({caption: "foo"}));
p2.add(new ui.button({caption: "bar"}));
p1.add(p2, ui.sides.top);
p1.add(new ui.button({caption: "frob"}), ui.sides.center);
rp.add(p1);
}
3.10 progress
A rectangle in which a part is filled, depending on the current value.
It can also be in indeterminate mode, in which case it will be
regularly redrawn with a block that moves back and forth.
The limits of the bar are hardcoded to [0,100]. Passing -1 to value()
will put it in indeterminate mode.
Options:
Example:
function f(rp)
{
var p = new ui.progress();
p.value(-1);
// 5 seconds of indeterminate mode
setTimeout(function()
{
p.value(0);
// tick twice a second
setInterval(function()
{
p.value(p.value() + 1);
}, 500);
}, 5000);
}
3.11 scrollbar
This is a slider [3.13] with up/down or left/right buttons. A scroller
[3.12] can be used to automatically manage scrolling a child control,
but standalone scrollbars can also be used. The list control [3.7]
uses them.
function f(rp)
{
f.layout(new ui.border_layout());
var sb = new ui.scrollbar();
// the value will be constrained within the limits; this also
// determines the size of the thumb (see 3.10)
sb.limits(0, 30);
sb.value(12);
// this determines the amount scrolled by clicking the empty
// space around the thumb
sb.page_size(5);
// fired when the value changes
sb.changed.add(function(v)
{
alert("value is " + v);
});
rp.add(sb, ui.sides.right);
}
3.12 scroller
This is a panel with scrollbars. Its only child will always have its
preferred size and will be moved around when scrolling.
A scroller uses sliders [3.13] and special buttons that support holding
them down to continuously scroll.
Options:
none
Example:
function f(rp)
{
// assumes that the image is larger than the canvas
var i = new Image();
i.src = "large_image.png");
require_loaded(i, function()
{
var s = new ui.scroller();
s.add(new ui.image(i));
rp.add(s);
});
}
3.13 slider
This is a thin panel with a button that can be moved around,
constrained to the panel. It also supports clicking in the empty
spaces around the thumb to move it faster. It will fire the changed()
signal when the value changes.
A slider has a value that is between user-defined limits. The limits
are unimportant from the slider's point of view, it only scales the
value.
While dragging the thumb, it will reset to its original value if the
mouse moves too far.
The thumb will also resize itself proportionally depending on the
limits and the size of the slider. In this mode, the slider uses the
range within the limits (max minus min) to shrink the thumb: the
range is the number of pixels to shrink the thumb by, up to the
minimum size of the thumb. Therefore, a limit of 10,20 will leave
10 pixels for the thumb to move around.
Options:
. orientation (horizontal, vertical), default: horizontal
whether the slider is horizontal or vertical
. outside_limit (positive integer), default: 130
the amount of pixels the mouse can move away from the thumb
before it resets
. page_size (positive integer), default: 1
the value increment while the mouse is held down in the empty
space
. proportional (true/false), default: false
whether the thumb size should be proportional to the limits
and slider size
Example:
function (rp)
{
// horizontal slider
var s = new ui.slider();
s.limits(10, 50);
rp.add(s);
}
3.14 textbox
Textboxes are hard. There's even a long section from whatwg saying
that "authors should avoid implementing text editing controls using
the canvas element. Doing so has a large number of disadvantages."
[whatwg-best]
However, most of the disadvantages are implemented, except for the
native stuff:
. copy/paste: possible within the canvas, bespin uses flash to
access the system clipboard
. spell-checking: I consider this to be candy, but it is a nice
thing to have
. native keyboard shortcuts: I don't consider this to be
particularly significant
Other things are not specific to textboxes:
. page-wide text search: nice to have, but that's a problem with
text on canvas in general, not only textboxes.
whatwg says that "a future version of the 2D context API may
provide a way to render fragments of documents, rendered using
CSS, straight to the canvas." [whatwg2] This may perhaps allow the
browser to recognize the text, but I doubt it.
This is also a problem with images in browsers, or more generally
any kind of application that doesn't use standard widgets (such
as games.)
. text services and IMEs, bidirectional text, accessibility:
basically any kind of i18n or accessibility. That would indeed
be nice and, although parts of it may be implemented, _that_ would
be hard work.
I do not consider those to be show-stoppers, but merely limitations
inherent to having a browser in a sandbox. Some are not implemented
yet, but not for technical reasons; they will be available eventually:
. drag-and-drop
. multiline editing
. undo/redo: not system wide, but available within the textbox
The rest works fine:
. mouse placement of the caret
. keyboard movement
. scrolling (todo)
. selection
Options:
none
Example:
function f(rp)
{
rp.add(new ui.textbox("initial text"));
}
3.15 tooltip
A tooltip is nothing special, except that it has a yellow background
and borders. Every control can have a tooltip (set by calling
tooltip() on it), but the control given can be anything. It will be
shown at the mouse position after a short delay if the the mouse
stands still.
Options:
none
Example:
function f(rp)
{
var lb = new ui.label("a label");
lb.tooltip(new ui.tooltip("text"));
rp.add(lb);
}
3.16 control and container base classes
All controls inherit from the 'control' class, which has some member
functions of its own. For example, it allows for changing the mouse
cursor while hovering the control, changing the font, setting options,
disabling, etc. It also has hooks for derived classes, such as
on_keyup() or on_focus().
All containers (such as panels) inherit from the 'container' class.
A container has a layout manager and can add or remove children.
Every control has what is called its 'best dimension' which is
respected as much as possible by the layout managers. It is sometimes
not possible (such as when a control would overflow its container) or
not used (such as the center part of a border_layout.)
The best dimension of a control depends on its content: a label will
return the size of the string plus its margins and an image will
return the size of the image plus its margins.
4 Layouts
======================================================================
There are several layout managers that will position and resize
children in different ways.
Layouts usually ask all their children for their best dimension,
fiddles with them and returns the dimension needed to accommodate all
the children.
4.1 absolute_layout
This layout won't touch its children at all. They must be positioned
and dimensioned manually with bounds() or some equivalent member
function.
Because this layout doesn't move or resize children, its preferred
size must be set manually with set_best_dimension().
Options:
none
Example:
function f(rp)
{
var p = new ui.panel(new ui.absolute_layout());
// without this, 'p' would report that its best dimension is 0x0
p.layout().set_best_dimension(new dimension(50, 50));
var b = new ui.button();
b.bounds(new rectangle(20, 20, 42, 19));
p.add(b);
}
4.2 border_layout
This layout has four sides plus the center. The sides will give their
child their best dimension and the center control will take the
remaining space.
It tries to be somewhat smart with the center control: if it exceeds
its maximum dimension, the excess will be redistributed to the sides
(in which case they won't have their best dimension anymore.) However,
the combination of the size of the container and the control's best
dimension might give unexpected results if the controls need strict
dimensions.
Options:
. margin (positive integer), default: 0
space between the borders of the container and the controls
. padding (positive integer), default: 5
space between an area in which there is a control and the center
Example:
function f(rp)
{
var p = new ui.panel(new ui.border_layout());
// +--------------------------------------------+
// | +-----+ |
// | | top | |
// | +-----+ |
// | +-------------------------+ |
// |+------+| |+-------+|
// || left || center || right ||
// |+------+| |+-------+|
// | +-------------------------+ |
// | +--------+ |
// | | bottom | |
// | +--------+ |
// +--------------------------------------------+
p.add(new ui.label("top"), ui.sides.top);
p.add(new ui.label("left"), ui.sides.left);
p.add(new ui.label("bottom"), ui.sides.bottom);
p.add(new ui.label("right"), ui.sides.right);
p.add(new ui.center("center"), ui.sides.center);
rp.add(p);
}
4.3 grid_layout
This layout arranges its children in rows of X controls with padding
between them. The height of a row is normally the height of its
largest control, giving uneven rows. However, if the option
'same_size' is given, all the rows have the height of the largest
control among all the children. In any case, all the children have the
same width, which is the container's width (minus the margin and
padding) divided by the number of controls on a line.
Options:
. same_size (true/false), default: false
use the height of the highest control for every control
. margin (positive integer), default: 0
space between the borders of the container and the controls
. hpadding (positive integer), default: 0
horizontal space between two children
. vpadding (positive integer), default: 0
vertical space between two children
. padding (positive integer), default: none
sets both hpadding and vpadding to the given value
Example:
function make_panel(opts)
{
// two child per row
var p = new ui.panel(new ui.grid_layout(2, opts));
p.add(new label("single line"));
p.add(new button("button"));
p.add(new label("on\nthree\nlines"));
p.add(new button("button"));
return p;
}
function f(rp)
{
// +---------------------------------+
// | +-------------+ +-------------+ |
// | | single line | | button | |
// | +-------------+ +-------------+ |
// | +-------------+ +-------------+ |
// | | on | | | |
// | | three | | button | |
// | | lines | | | |
// | +-------------+ +-------------+ |
// +---------------------------------+
rp.add(make_panel({}));
// +---------------------------------+
// | +-------------+ +-------------+ |
// | | | | | |
// | | single line | | button | |
// | | | | | |
// | +-------------+ +-------------+ |
// | +-------------+ +-------------+ |
// | | on | | | |
// | | three | | button | |
// | | lines | | | |
// | +-------------+ +-------------+ |
// +---------------------------------+
rp.add(make_panel({same_size: true}));
}
4.4 horizontal_layout
Lays out its children one next to the other, always respecting their
best width. This may overflow the container (todo: and probably
break in the process)
There are several alignment options and two ways of deciding the
height of the controls.
The control's height is normally its preferred height. However, if
'same_size' is given, the height will be that of the largest control.
If 'expand' is given instead, the height will be that of the container
(minus the margin, etc.) Obviously, 'same_size' is ignored with
'expand'.
The controls may be aligned horizontally on the left side, center or
right side:
left:
+-------------------------------------+
| +--------+ +--------+ |
| | label1 | | label2 | |
| +--------+ +--------+ |
+-------------------------------------+
center:
+-------------------------------------+
| +--------+ +--------+ |
| | label1 | | label2 | |
| +--------+ +--------+ |
+-------------------------------------+
right:
+-------------------------------------+
| +--------+ +--------+|
| | label1 | | label2 ||
| +--------+ +--------+|
+-------------------------------------+
The valign option has the same behaviour, but vertically:
top:
+-------------------------------------+
| +--------+ +--------+ |
| | label1 | | label2 | |
| +--------+ +--------+ |
| |
| |
| |
| |
+-------------------------------------+
center:
+-------------------------------------+
| |
| |
| +--------+ +--------+ |
| | label1 | | label2 | |
| +--------+ +--------+ |
| |
| |
+-------------------------------------+
bottom:
+-------------------------------------+
| |
| |
| |
| |
| +--------+ +--------+ |
| | label1 | | label2 | |
| +--------+ +--------+ |
+-------------------------------------+
The valign option is ignored when 'expand' is given, since the
controls take all the available height.
Options:
. expand (true, false), default: false
controls will expand to the height of the container instead of
using their preferred height
. same_size (true, false), default: false
controls will have the height of the highest instead of their
preferred height
note: ignored with expand
. halign (left, center, right), default: left
the rectangle bounding all the controls will be aligned to the
left, center or right of the container
. valign (top, center, bottom), default: top
all controls will be aligned to the top, center or bottom of the
container
note: ignored with expand
. margin (positive integer), default: 0
space between the borders of the container and the controls
. padding (positive integer), default: 5
space between each control
Example:
function f(rp)
{
var p = new ui.panel(new ui.horizontal_layout({halign: "center"));
p.add(new ui.label("label1"));
p.add(new ui.label("label2"));
rp.add(p);
}
4.5 vertical_layout
See 4.4, but with 'width' and 'height' reversed.
5 Utilities
======================================================================
5.1 geometry
There are several classes that are used throughout canui:
. point (x, y)
. dimension (w, h)
. rectangle (x, y, w, h)
These are self-explanatory.
5.2 graphics
The 'color' class has r, g, b and a values between 0.0 and 1.0. There
is also several helper functions that wrap common graphical
operations, such as outline_rect() or fill_rect().
Playing with text is somewhat hard, since there is no reliable way
of getting the bounding rectangle of a particular string and font.
measureText() returns the width, but the text may "spill out of the
[...] width returned by measureText()"[whatwg-canvas]. There is no way
to get any kind of height information.
canui hardcodes a line height of 16 pixels and a spacing between two
lines of 3 pixels. These seem to be correct for both firefox and
chrome. ymmv.
5.2.1 images
All the controls in canui that expect images (such as the image
control) need image_holder objects instead of the dom Image element.
This is because determining whether an image is loaded and ready to
be drawn is difficult and is browser-dependent.
For example, firefox will set the 'complete' flag to true for 404
images that are cached (how's that for an oxymoron) without firing
either onload() or onerror().
Therefore, image_holder tries to be smart about whether the image is
loaded or not, but it cannot be fullproof. Relying on width and height
being 0 will give a false-positive for empty images (which may not be
too bad), but will also fail on browsers that report the dimension of
the alt-text there.
Using a combination of the complete flag, the onload and onerror
events and the naturalWidth and naturalHeight properties (available in
some browsers), image_holder gets rather close to determining the real
status of the image.
load_image() takes filename and an alt string and returns an
image_holder. This is the safest way of creating image_holders. It
will also cache the image in an internal array so that multiple calls
to load_image() with the same filename will return the same
image_holder.
5.2.2 grayscale images
create_grayscale() takes an image_holder, creates a temporary canvas,
draws the image, gets the pixels, converts them to a light grayscale
and returns a new Image with that data. It is used by the image
control so images are automatically grayed when the control is
disabled.
The grayscale image is cached so that future calls to
create_grayscale() with the same image_holder won't have to recreate
it.
Note that this will fail if the image comes from a different origin
than the script. To quote w3c [w3c-sec]:
"Information leakage can occur if scripts from one origin can
access information (e.g. read pixels) from images from
another origin (one that isn't the same).
To mitigate this, canvas elements are defined to have a flag
indicating whether they are origin-clean."
One of the circumstances where the flag is set is:
"The element's 2D context's drawImage() method is called
with an HTMLImageElement or an HTMLVideoElement whose origin
is not the same as that of the Document object that owns
the canvas element."
In this case, create_grayscale() will return the given image.
5.3 utility
There are several helper classes and functions used throughout canui.
. font
This has the font family, size and some css attributes, such as
bold and italic.
. theme
This is currently underused, but the plan is to put all the color
and size information in there.
The controls currently have a classic Windows look, which was
the intention, as I mostly run Windows 7 with the classic look.
Most of the metrics and behaviour is based on Windows 7.
. bind and mem_fun
The former can bind the arguments of a function and the latter
makes sure a particular member function is called on the right
object.
function a()
{
this.frob = function()
{
}
}
function b(a, b)
{
console.log(a + b);
}
var o = new a();
var f = mem_fun('frob', o);
f(); // calls o.frob()
f = bind(b, 3, 5);
f(); // logs 8
These are used heavily with signals.
. assert
This will break in a debugger (if present) if the given value is
false. There are lots of assertions throughout canui, such as for
making sure things have the right type. I am not fond of weakly-
typed languages.
. signal
A signal is a container of functions (slots) that are called when
the signal is fired. This is used for callbacks, such as when a
button is clicked:
function f1(a)
{
console.log(a);
}
function f2(a)
{
console.log(a);
}
var s = new signal();
s.add(f1);
s.add(f2);
s.fire(42); // logs 42 twice
A slot can return a strict false to get detached automatically.
Returning anything else (or nothing) will keep the slot attached.
. make_enum()
There are several 'enumerations' in canui that are created with
this function. It takes an array of strings and returns an object
with "string: n", where n is an increasing integer value. This
mimics the behaviour of enums in most other languages.
6 Custom controls
======================================================================
A custom control can either be derived from 'control' or 'container'
depending on whether you need children or not.
All the member functions in the control class can be overriden. Unless
the default behaviour needs to be avoided, the base class versions
should always be called. There are cases where it is pretty much
mandatory, such as in draw() where control__draw() clears the dirty
flag. Without it, the canvas would continually redraw itself.
These are the member functions that are most often overriden. In the
following, 'mp' is always the mouse coordinates relative to the
control.
best_dimension()
returns the best dimension for this control. The container base
class will ask the layout manager for its best dimension, which is
based on the layout itself and the dimensions of the children.
a custom control should return the dimension it needs to display
itself properly.
on_mouse_move(mp)
called when the mouse moves over the control, or anywhere if the
control has captured the mouse
on_mouse_left_down(mp)
called when the mouse left button is pressed over the control, or
anywhere if the control has captured the mouse
on_mouse_left_up(mp)
called when the mouse left button is released over the control, or
anywhere if the control has captured the mouse
on_double_click(mp)
called when the mouse left button is double clicked over the
control, or anywhere if the control has captured the mouse.
on_mouse_enter(mp)
called when the mouse moves over the control, but was previously
over a different control
on_mouse_leave()
called when the mouse moves away from the control
on_focus()
called when the left mouse button is pressed for the first time
while over the control; this gives the focus to the control
on_blur()
called when the left mouse button is pressed on another control and
this control had the focus
on_keypress(c)
called when a key on the keyboard is pressed, or at regular
intervals when the key is kept pressed; 'c' is the key code. This
is not called for control keys, such as the arrow keys.
on_keydown(c)
called when a key on the keyboard is pressed, or at regular
intervals when the key is kept pressed; 'c' is the key code. This
is only called for control keys, such as the arrow keys.
on_keyup(c)
called when a key on the keyboard is released; 'c' is the key code.
This is called for all keys.
on_mouse_scroll(mp, v)
called when the mouse wheel is turned over a control, or anywhere
if the control has captured the mouse; 'v' is a normalized value
where 1 is a full tick.
on_bounds_changed()
called when the bounds of the control (position or dimension)
changed
draw(c)
called when the control needs to redraw itself; 'c' is the canvas
context. Inside draw(), the context has been transformed so that
coordinates are relative to the parent and clipped to the parent's
bounds. Since bounds are always relative to the parent,
self.bounds() can be used directly as the rectangle in which to
draw.
typename()
debug, should return a meaningful name for this control; this is
used in dump() for example, to display the control hierarchy
Example:
// a 20x20 control (when possible) that fills itself with blue
//
function c()
{
ui.inherit_control(this);
self.best_dimension = function()
{
return new dimension(20, 20);
};
self.draw = function(context)
{
self.control__draw(context);
fill_rect(context, new color().blue(), self.bounds();
};
}
function f(rp)
{
rp.add(new c());
}
7 Internals
======================================================================
canui uses some kind of inheritance where member functions are
prefixed when the class name. Each base class has a "vtable" at the
end, so that these function may be overridden by derived classes.
function inherit_base(self)
{
self.base__frob = function()
{
console.log("base__frob");
};
self.frob = self.base__frob;
}
function derived()
{
inherit_base(this);
this.derived__frob = function()
{
this.base__frob();
console.log("derived__frob");
}
this.frob = this.derived__frob;
}
var d = new derived();
// base__frob
// derived__frob
d.frob();
8 References
======================================================================
[whatwg-canvas]
http://www.whatwg.org/specs/web-apps/current-work/multipage/
the-canvas-element.html
[whatwg-best]
http://www.whatwg.org/specs/web-apps/current-work/multipage/
the-canvas-element.html#best-practices
[w3c-data]
http://www.w3.org/TR/html5/elements.html#
embedding-custom-non-visible-data-with-the-data-attributes
[w3c-origin]
http://dev.w3.org/html5/spec/origin-0.html#same-origin
eof