summaryrefslogtreecommitdiffstatshomepage
path: root/docs/source/techspecs/layout_script.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/source/techspecs/layout_script.rst')
-rw-r--r--docs/source/techspecs/layout_script.rst695
1 files changed, 695 insertions, 0 deletions
diff --git a/docs/source/techspecs/layout_script.rst b/docs/source/techspecs/layout_script.rst
new file mode 100644
index 00000000000..be6166a7146
--- /dev/null
+++ b/docs/source/techspecs/layout_script.rst
@@ -0,0 +1,695 @@
+.. _layscript:
+
+MAME Layout Scripting
+=====================
+
+.. contents:: :local:
+
+
+.. _layscript-intro:
+
+Introduction
+------------
+
+MAME layout files can embed Lua script to provide enhanced functionality.
+Although there’s a lot you can do with conditionally drawn components and
+parameter animation, some things can only be done with scripting. MAME uses an
+event-based model. Scripts can supply functions that will be called after
+certain events, or when certain data is required.
+
+Layout scripting requires the :ref:`layout plugin <plugins-layout>` to be
+enabled. For example, to run BWB Double Take with the Lua script in the layout
+enabled, you might use this command::
+
+ mame -plugins -plugin layout v4dbltak
+
+You may want to add the settings to enable the layout plugin to an INI file to
+save having to enable it every time you start a system. See :ref:`plugins` for
+more information about using plugins with MAME.
+
+
+.. _layscript-examples:
+
+Practical examples
+------------------
+
+Before diving into the technical details of how it works, we’ll start with some
+example layout files using Lua script for enhancement. It’s assumed that you’re
+familiar with MAME’s artwork system and have a basic understanding of Lua
+scripting. For details on MAME’s layout file, see :ref:`layfile`; for detailed
+descriptions of MAME’s Lua interface, see :ref:`luascript`.
+
+.. _layscript-examples-espial:
+
+Espial: joystick split across ports
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Take a look at the player input definitions for Espial:
+
+.. code-block:: C++
+
+ PORT_START("IN1")
+ PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_START1 )
+ PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_START2 )
+ PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_8WAY PORT_COCKTAIL
+ PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_8WAY PORT_COCKTAIL
+ PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_JOYSTICK_UP ) PORT_8WAY PORT_COCKTAIL
+ PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_DOWN ) PORT_8WAY
+ PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_DOWN ) PORT_8WAY PORT_COCKTAIL
+ PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_COCKTAIL
+
+ PORT_START("IN2")
+ PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_UNKNOWN )
+ PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_COIN1 )
+ PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_UNKNOWN )
+ PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_8WAY
+ PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_JOYSTICK_UP ) PORT_8WAY
+ PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_COCKTAIL
+ PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_BUTTON1 )
+ PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_8WAY
+
+There are two joysticks, one used for both players on an upright cabinet or the
+first player on a cocktail cabinet, and one used for the second player on a
+cocktail cabinet. Notice that the switches for the first joystick are split
+across the two I/O ports.
+
+There’s no layout file syntax to build the element state using bits from
+multiple I/O ports. It’s also inconvenient if each joystick needs to be defined
+as a separate element because the bits for the switches aren’t arranged the same
+way.
+
+We can overcome these limitations using a script to read the player inputs and
+set the items’ element state:
+
+.. code-block:: XML
+
+ <?xml version="1.0"?>
+ <mamelayout version="2">
+
+ <!-- element for drawing a joystick -->
+ <!-- up = 1 (bit 0), down = 2 (bit 1), left = 4 (bit 2), right = 8 (bit 3) -->
+ <element name="stick" defstate="0">
+ <image state="0x0" file="stick_c.svg" />
+ <image state="0x1" file="stick_u.svg" />
+ <image state="0x9" file="stick_ur.svg" />
+ <image state="0x8" file="stick_r.svg" />
+ <image state="0xa" file="stick_dr.svg" />
+ <image state="0x2" file="stick_d.svg" />
+ <image state="0x6" file="stick_dl.svg" />
+ <image state="0x4" file="stick_l.svg" />
+ <image state="0x5" file="stick_ul.svg" />
+ </element>
+
+ <!-- we'll warn the user if the layout plugin isn't enabled -->
+ <!-- draw only when state is 1, and set the default state to 1 so warning is visible initially -->
+ <element name="warning" defstate="1">
+ <text state="1" string="This view requires the layout plugin." />
+ </element>
+
+ <!-- view showing the screen and joysticks on a cocktail cabinet -->
+ <view name="Joystick Display">
+ <!-- draw the screen with correct aspect ratio -->
+ <screen index="0">
+ <bounds x="0" y="0" width="4" height="3" />
+ </screen>
+
+ <!-- first joystick, id attribute allows script to find item -->
+ <!-- no bindings, state will be set by the script -->
+ <element id="joy_p1" ref="stick">
+ <!-- position below the screen -->
+ <bounds xc="2" yc="3.35" width="0.5" height="0.5" />
+ </element>
+
+ <!-- second joystick, id attribute allows script to find item -->
+ <!-- no bindings, state will be set by the script -->
+ <element id="joy_p2" ref="stick">
+ <!-- screen is flipped for second player, so rotate by 180 degrees -->
+ <orientation rotate="180" />
+ <!-- position above the screen -->
+ <bounds xc="2" yc="-0.35" width="0.5" height="0.5" />
+ </element>
+
+ <!-- warning text item also has id attribute so the script can find it -->
+ <element id="warning" ref="warning">
+ <!-- position over the screen near the bottom -->
+ <bounds x="0.2" y="2.6" width="3.6" height="0.2" />
+ </element>
+ </view>
+
+ <!-- the content of the script element will be called as a function by the layout plugin -->
+ <!-- use CDATA block to avoid the need to escape angle brackets and ampersands -->
+ <script><![CDATA[
+ -- file is the layout file object
+ -- set a function to call after resolving tags
+ file:set_resolve_tags_callback(
+ function ()
+ -- file.device is the device that caused the layout to be loaded
+ -- in this case, it's the root machine driver for espial
+ -- look up the two I/O ports we need to be able to read
+ local in1 = file.device:ioport("IN1")
+ local in2 = file.device:ioport("IN2")
+
+ -- look up the view items for showing the joystick state
+ local p1_stick = file.views["Joystick Display"].items["joy_p1"]
+ local p2_stick = file.views["Joystick Display"].items["joy_p2"]
+
+ -- set a function to call before adding the view items to the render target
+ file.views["Joystick Display"]:set_prepare_items_callback(
+ function ()
+ -- read the two player input I/O ports
+ local in1_val = in1:read()
+ local in2_val = in2:read()
+
+ -- set element state for first joystick
+ p1_stick:set_state(
+ ((in2_val & 0x10) >> 4) | -- shift up from IN2 bit 4 to bit 0
+ ((in1_val & 0x20) >> 4) | -- shift down from IN1 bit 5 to bit 1
+ ((in2_val & 0x80) >> 5) | -- shift left from IN2 bit 7 to bit 2
+ (in2_val & 0x08)) -- right is in IN2 bit 3
+
+ -- set element state for second joystick
+ p2_stick:set_state(
+ ((in1_val & 0x10) >> 4) | -- shift up from IN1 bit 4 to bit 0
+ ((in1_val & 0x40) >> 5) | -- shift down from IN1 bit 6 to bit 1
+ (in1_val & 0x04) | -- left is in IN1 bit 2
+ (in1_val & 0x08)) -- right is in IN1 bit 3
+ end)
+
+ -- hide the warning, since if we got here the script is running
+ file.views["Joystick Display"].items["warning"]:set_state(0)
+ end)
+ ]]></script>
+
+ </mamelayout>
+
+The layout has a ``script`` element containing the Lua script. This is called
+as a function by the layout plugin when the layout file is loaded. The layout
+views have been built at this point, but the emulated system has not finished
+starting. In particular, it’s not safe to access inputs and outputs at this
+time. The key variable in the script environment is ``file``, which gives the
+script access to its :ref:`layout file <luascript-ref-renderlayfile>`.
+
+We supply a function to be called after tags in the layout file have been
+resolved. At this point, the emulated system will have completed starting.
+This function does the following tasks:
+
+* Looks up the two :ref:`I/O ports <luascript-ref-ioport>` used for player
+ input. I/O ports can be looked up by tag relative to the device that caused
+ the layout file to be loaded.
+* Looks up the two :ref:`view items <luascript-ref-renderlayitem>` used to
+ display joystick state. Views can be looked up by name (i.e. value of the
+ ``name`` attribute), and items within a view can be looked up by ID (i.e. the
+ value of the ``id`` attribute).
+* Supplies a function to be called before view items are added to the render
+ target when drawing a frame.
+* Hides the warning that reminds the user to enable the layout plugin by setting
+ the element state for the item to 0 (the text component is only drawn when
+ the element state is 1).
+
+The function called before view items are added to the render target reads the
+player inputs, and shuffles the bits into the order needed by the joystick
+element.
+
+.. _layscript-examples-starwars:
+
+Star Wars: animation on two axes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We’ll make a layout that shows the position of the flight yoke for Atari Star
+Wars. The input ports are straightforward – each analog axis produces a value
+in the range from 0x00 (0) to 0xff (255), inclusive:
+
+.. code-block:: C++
+
+ PORT_START("STICKY")
+ PORT_BIT( 0xff, 0x80, IPT_AD_STICK_Y ) PORT_SENSITIVITY(70) PORT_KEYDELTA(30)
+
+ PORT_START("STICKX")
+ PORT_BIT( 0xff, 0x80, IPT_AD_STICK_X ) PORT_SENSITIVITY(50) PORT_KEYDELTA(30)
+
+Here’s our layout file:
+
+.. code-block:: XML
+
+ <?xml version="1.0"?>
+ <mamelayout version="2">
+
+ <!-- a square with a white outline 1% of its width -->
+ <element name="outline">
+ <rect><bounds x="0.00" y="0.00" width="1.00" height="0.01" /></rect>
+ <rect><bounds x="0.00" y="0.99" width="1.00" height="0.01" /></rect>
+ <rect><bounds x="0.00" y="0.00" width="0.01" height="1.00" /></rect>
+ <rect><bounds x="0.99" y="0.00" width="0.01" height="1.00" /></rect>
+ </element>
+
+ <!-- a rectangle with a vertical line 10% of its width down the middle -->
+ <element name="line">
+ <!-- use a transparent rectangle to force element dimensions -->
+ <rect>
+ <bounds x="0" y="0" width="0.1" height="1" />
+ <color alpha="0" />
+ </rect>
+ <!-- this is the visible white line -->
+ <rect><bounds x="0.045" y="0" width="0.01" height="1" /></rect>
+ </element>
+
+ <!-- an outlined square inset by 20% with lines 10% of the element width/height -->
+ <element name="box">
+ <!-- use a transparent rectangle to force element dimensions -->
+ <rect>
+ <bounds x="0" y="0" width="0.1" height="0.1" />
+ <color alpha="0" />
+ </rect>
+ <!-- draw the outline of a square -->
+ <rect><bounds x="0.02" y="0.02" width="0.06" height="0.01" /></rect>
+ <rect><bounds x="0.02" y="0.07" width="0.06" height="0.01" /></rect>
+ <rect><bounds x="0.02" y="0.02" width="0.01" height="0.06" /></rect>
+ <rect><bounds x="0.07" y="0.02" width="0.01" height="0.06" /></rect>
+ </element>
+
+ <!-- we'll warn the user if the layout plugin isn't enabled -->
+ <!-- draw only when state is 1, and set the default state to 1 so warning is visible initially -->
+ <element name="warning" defstate="1">
+ <text state="1" string="This view requires the layout plugin." />
+ </element>
+
+ <!-- view showing the screen and flight yoke position -->
+ <view name="Analog Control Display">
+ <!-- draw the screen with correct aspect ratio -->
+ <screen index="0">
+ <bounds x="0" y="0" width="4" height="3" />
+ </screen>
+
+ <!-- draw the white outlined square to the right of the screen near the bottom -->
+ <!-- the script uses the size of this item to determine movement ranges -->
+ <element id="outline" ref="outline">
+ <bounds x="4.1" y="1.9" width="1.0" height="1.0" />
+ </element>
+
+ <!-- vertical line for displaying X axis input -->
+ <element id="vertical" ref="line">
+ <!-- element draws a vertical line, no need to rotate it -->
+ <orientation rotate="0" />
+ <!-- centre it in the square horizontally, using the full height -->
+ <bounds x="4.55" y="1.9" width="0.1" height="1" />
+ </element>
+
+ <!-- horizontal line for displaying Y axis input -->
+ <element id="horizontal" ref="line">
+ <!-- rotate the element by 90 degrees to get a horizontal line -->
+ <orientation rotate="90" />
+ <!-- centre it in the square vertically, using the full width -->
+ <bounds x="4.1" y="2.35" width="1" height="0.1" />
+ </element>
+
+ <!-- draw a small box at the intersection of the vertical and horizontal lines -->
+ <element id="box" ref="box">
+ <bounds x="4.55" y="2.35" width="0.1" height="0.1" />
+ </element>
+
+ <!-- draw the warning text over the screen near the bottom -->
+ <element id="warning" ref="warning">
+ <bounds x="0.2" y="2.6" width="3.6" height="0.2" />
+ </element>
+ </view>
+
+ <!-- the content of the script element will be called as a function by the layout plugin -->
+ <!-- use CDATA block to avoid the need to escape angle brackets and ampersands -->
+ <script><![CDATA[
+ -- file is the layout file object
+ -- set a function to call after resolving tags
+ file:set_resolve_tags_callback(
+ function ()
+ -- file.device is the device that caused the layout to be loaded
+ -- in this case, it's the root machine driver for starwars
+ -- find the analog axis inputs
+ local x_input = file.device:ioport("STICKX")
+ local y_input = file.device:ioport("STICKY")
+
+ -- find the outline item
+ local outline_item = file.views["Analog Control Display"].items["outline"]
+
+ -- variables for keeping state across callbacks
+ local outline_bounds -- bounds of the outlined square
+ local width, height -- width and height for animated items
+ local x_scale, y_scale -- ratios of axis units to render coordinates
+ local x_pos, y_pos -- display positions for the animated items
+
+ -- set a function to call when view dimensions have been recalculated
+ -- this can happen when when the window is resized or scaling options are changed
+ file.views["Analog Control Display"]:set_recomputed_callback(
+ function ()
+ -- get the bounds of the outlined square
+ outline_bounds = outline_item.bounds
+ -- animated items use 10% of the width/height of the square
+ width = outline_bounds.width * 0.1
+ height = outline_bounds.height * 0.1
+ -- calculate ratios of axis units to render coordinates
+ -- animated items leave 90% of the width/height for the movement range
+ -- the end of the range of each axis is at 0xff
+ x_scale = outline_bounds.width * 0.9 / 0xff
+ y_scale = outline_bounds.height * 0.9 / 0xff
+ end)
+
+ -- set a function to call before adding the view items to the render target
+ file.views["Analog Control Display"]:set_prepare_items_callback(
+ function ()
+ -- read analog axes, reverse Y axis as zero is at the bottom
+ local x = x_input:read() & 0xff
+ local y = 0xff - (y_input:read() & 0xff)
+ -- convert the input values to layout coordinates
+ -- use the top left corner of the outlined square as the origin
+ x_pos = outline_bounds.x0 + (x * x_scale)
+ y_pos = outline_bounds.y0 + (y * y_scale)
+ end)
+
+ -- set a function to supply the bounds for the vertical line
+ file.views["Analog Control Display"].items["vertical"]:set_bounds_callback(
+ function ()
+ -- create a new render bounds object (starts as a unit square)
+ local result = emu.render_bounds()
+ -- set left, top, width and height
+ result:set_wh(
+ x_pos, -- calculated X position for animated items
+ outline_bounds.y0, -- top of outlined square
+ width, -- 10% of width of outlined square
+ outline_bounds.height) -- full height of outlined square
+ return result
+ end)
+
+ -- set a function to supply the bounds for the horizontal line
+ file.views["Analog Control Display"].items["horizontal"]:set_bounds_callback(
+ function ()
+ -- create a new render bounds object (starts as a unit square)
+ local result = emu.render_bounds()
+ -- set left, top, width and height
+ result:set_wh(
+ outline_bounds.x0, -- left of outlined square
+ y_pos, -- calculated Y position for animated items
+ outline_bounds.width, -- full width of outlined square
+ height) -- 10% of height of outlined square
+ return result
+ end)
+
+ -- set a function to supply the bounds for the box at the intersection of the lines
+ file.views["Analog Control Display"].items["box"]:set_bounds_callback(
+ function ()
+ -- create a new render bounds object (starts as a unit square)
+ local result = emu.render_bounds()
+ -- set left, top, width and height
+ result:set_wh(
+ x_pos, -- calculated X position for animated items
+ y_pos, -- calculated Y position for animated items
+ width, -- 10% of width of outlined square
+ height) -- 10% of height of outlined square
+ return result
+ end)
+
+ -- hide the warning, since if we got here the script is running
+ file.views["Analog Control Display"].items["warning"]:set_state(0)
+ end)
+ ]]></script>
+
+ </mamelayout>
+
+The layout has a ``script`` element containing the Lua script, to be called as a
+function by the layout plugin when the layout file is loaded. This happens
+after the layout views have been build, but before the emulated system has
+finished starting. The :ref:`layout file <luascript-ref-renderlayfile>` object
+is supplied to the script in the ``file`` variable.
+
+We supply a function to be called after tags in the layout file have been
+resolved. This function does the following:
+
+* Looks up the analog axis :ref:`inputs <luascript-ref-ioport>`.
+* Looks up the :ref:`view item <luascript-ref-renderlayitem>` that draws the
+ outline of area where the yoke position is displayed.
+* Declares some variables to hold calculated values across function calls.
+* Supplies a function to be called when the view’s dimensions have been
+ recomputed.
+* Supplies a function to be called before adding view items to the render
+ container when drawing a frame.
+* Supplies functions that will supply the bounds for the animated items.
+* Hides the warning that reminds the user to enable the layout plugin by setting
+ the element state for the item to 0 (the text component is only drawn when
+ the element state is 1).
+
+The view is looked up by name (value of its ``name`` attribute), and items
+within the view are looked up by ID (values of their ``id`` attributes).
+
+Layout view dimensions are recomputed in response to several events, including
+the window being resized, entering/leaving full screen mode, toggling visibility
+of item collections, and changing the zoom to screen area setting. When this
+happens, we need to update our size and animation scale factors. We get the
+bounds of the square where the yoke position is displayed, calculate the size
+for the animated items, and calculate the ratios of axis units to render target
+coordinates in each direction. It’s more efficient to do these calculations
+only when the results may change.
+
+Before view items are added to the render target, we read the analog axis inputs
+and convert the values to coordinates positions for the animated items. The Y
+axis input uses larger values to aim higher, so we need to reverse the value by
+subtracting it from 0xff (255). We add in the coordinates of the top left
+corner of the square where we’re displaying the yoke position. We do this once
+each time the layout is drawn for efficiency, since we can use the values for
+all three animated items.
+
+Finally, we supply bounds for the animated items when required. These functions
+need to return ``render_bounds`` objects giving the position and size of the
+items in render target coordinates.
+
+(Since the vertical and horizontal line elements each only move on a single
+axis, it would be possible to animate them using the layout file format’s item
+animation features. Only the box at the intersection of the line actually
+requires scripting. It’s done entirely using scripting here for illustrative
+purposes.)
+
+
+.. _layscript-environment:
+
+The layout script environment
+-----------------------------
+
+The Lua environment is provided by the layout plugin. It’s fairly minimal, only
+providing what’s needed:
+
+* ``file`` giving the script’s :ref:`layout file <luascript-ref-renderlayfile>`
+ object. Has a ``device`` property for obtaining the :ref:`device
+ <luascript-ref-device>` that caused the layout file to be loaded, and a
+ ``views`` property for obtaining the layout’s :ref:`views
+ <luascript-ref-renderlayview>` (indexed by name).
+* ``machine`` giving MAME’s current :ref:`running machine
+ <luascript-ref-machine>`.
+* ``emu.device_enumerator``, ``emu.palette_enumerator``,
+ ``emu.screen_enumerator``, ``emu.cassette_enumerator``,
+ ``emu.image_enumerator`` and ``emu.slot_enumerator`` functions for obtaining
+ specific device interfaces.
+* ``emu.attotime``, ``emu.render_bounds`` and ``emu.render_color`` functions for
+ creating :ref:`attotime <luascript-ref-attotime>`, :ref:`bounds
+ <luascript-ref-renderbounds>` and :ref:`colour <luascript-ref-rendercolor>`
+ objects.
+* ``emu.bitmap_ind8``, ``emu.bitmap_ind16``, ``emu.bitmap_ind32``,
+ ``emu.bitmap_ind64``, ``emu.bitmap_yuy16``, ``emu.bitmap_rgb32`` and
+ ``emu.bitmap_argb32`` objects for creating
+ :ref:`bitmaps <luascript-ref-bitmap>`.
+* ``emu.print_verbose``, ``emu.print_error``, ``emu.print_warning``,
+ ``emu.print_info`` and ``emu.print_debug`` functions for diagnostic output.
+* Standard Lua ``tonumber``, ``tostring``, ``pairs`` and ``ipairs`` functions,
+ and ``table`` and ``string`` objects for manipulating strings, tables and
+ other containers.
+* Standard Lua ``print`` function for text output to the console.
+
+
+.. _layscript-events:
+
+Layout events
+-------------
+
+MAME layout scripting uses an event-based model. Scripts can supply functions
+to be called after events occur, or when data is needed. There are three levels
+of events: layout file events, layout view events, and layout view item events.
+
+.. _layscript-events-file:
+
+Layout file events
+~~~~~~~~~~~~~~~~~~
+
+Layout file events apply to the file as a whole, and not to an individual view.
+
+Resolve tags
+ ``file:set_resolve_tags_callback(cb)``
+
+ Called after the emulated system has finished starting, input and output
+ tags in the layout have been resolved, and default item callbacks have been
+ set up. This is a good time to look up inputs and set up view item event
+ handlers.
+
+ The callback function has no return value and takes no parameters. Call
+ with ``nil`` as the argument to remove the event handler.
+
+.. _layscript-events-view:
+
+Layout view events
+~~~~~~~~~~~~~~~~~~
+
+Layout view events apply to an individual view.
+
+Prepare items
+ ``view:set_prepare_items_callback(cb)``
+
+ Called before the view’s items are added to the render target in preparation
+ for drawing a video frame.
+
+ The callback function has no return value and takes no parameters. Call
+ with ``nil`` as the argument to remove the event handler.
+Preload
+ ``view:set_preload_callback(cb)``
+
+ Called after pre-loading visible view elements. This can happen when the
+ view is selected for the first time in a session, or when the user toggles
+ visibility of an element collection on. Be aware that this can be called
+ multiple times in a session and avoid repeating expensive tasks.
+
+ The callback function has no return value and takes no parameters. Call
+ with ``nil`` as the argument to remove the event handler.
+Dimensions recomputed
+ ``view:set_recomputed_callback(cb)``
+
+ Called after view dimensions are recomputed. This happens in several
+ situations, including the window being resized, entering or leaving full
+ screen mode, toggling visibility of item collections, and changes to the
+ rotation and zoom to screen area settings. If you’re animating the position
+ of view items, this is a good time to calculate positions and scale factors.
+
+ The callback function has no return value and takes no parameters. Call
+ with ``nil`` as the argument to remove the event handler.
+
+.. _layscript-events-item:
+
+Layout view item events
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Layout view item callbacks apply to individual items within a view. They are
+used to override items’ default element state, animation state, bounds and
+colour behaviour.
+
+Get element state
+ ``item:set_element_state_callback(cb)``
+
+ Set callback for getting the item’s element state. This controls how the
+ item’s element is drawn, for components that change appearance depending on
+ state, conditionally-drawn components, and component bounds/colour
+ animation. Do not attempt to access the item’s ``element_state`` property
+ from the callback, as it will result in infinite recursion.
+
+ The callback function must return an integer, and takes no parameters. Call
+ with ``nil`` as the argument to restore the default element state
+ handler (based on the item’s XML attributes).
+Get animation state
+ ``item:set_animation_state_callback(cb)``
+
+ Set callback for getting the item’s animation state. This is used for item
+ bounds/colour animation. Do not attempt to access the item’s
+ ``animation_state`` property from the callback, as it will result in
+ infinite recursion.
+
+ The callback function must return an integer, and takes no parameters. Call
+ with ``nil`` as the argument to restore the default animation state handler
+ (based on the item’s XML attributes and ``animate`` child element).
+Get item bounds
+ ``item:set_bounds_callback(cb)``
+
+ Set callback for getting the item’s bounds (position and size). Do not
+ attempt to access the item’s ``bounds`` property from the callback, as it
+ will result in infinite recursion.
+
+ The callback function must return a render bounds object representing the
+ item’s bounds in render target coordinates (usually created by calling
+ ``emu.render_bounds``), and takes no parameters. Call with ``nil`` as the
+ argument to restore the default bounds handler (based on the item’s
+ animation state and ``bounds`` child elements).
+Get item colour
+ ``item:set_color_callback(cb)``
+
+ Set callback for getting the item’s colour (the element texture’s colours
+ multiplied by this colour). Do not attempt to access the item’s ``color``
+ property from the callback, as it will result in infinite recursion.
+
+ The callback function must return a render colour object representing the
+ ARGB colour (usually created by calling ``emu.render_color``), and takes no
+ parameters. Call with ``nil`` as the argument to restore the default colour
+ handler (based on the item’s animation state and ``color`` child elements).
+Get item horizontal scroll window size
+ ``item:set_scroll_size_x_callback(cb)``
+
+ Set callback for getting the item’s horizontal scroll window size. This
+ allows the script to control how much of the element is displayed by the
+ item. Do not attempt to access the item’s ``scroll_size_x`` property from
+ the callback, as it will result in infinite recursion.
+
+ The callback function must return a floating-point number representing the
+ horizontal window size as a proportion of the associated element’s width,
+ and takes no parameters. A value of 1.0 will display the entire width of
+ the element; smaller values will display proportionally smaller parts of the
+ element. Call with ``nil`` as the argument to restore the default
+ horizontal scroll window size handler (based on the ``xscroll`` child
+ element).
+Get item vertical scroll window size
+ ``item:set_scroll_size_y_callback(cb)``
+
+ Set callback for getting the item’s vertical scroll window size. This
+ allows the script to control how much of the element is displayed by the
+ item. Do not attempt to access the item’s ``scroll_size_y`` property from
+ the callback, as it will result in infinite recursion.
+
+ The callback function must return a floating-point number representing the
+ vertical window size as a proportion of the associated element’s height, and
+ takes no parameters. A value of 1.0 will display the entire height of the
+ element; smaller values will display proportionally smaller parts of the
+ element. Call with ``nil`` as the argument to restore the default
+ vertical scroll window size handler (based on the ``xscroll`` child
+ element).
+Get item horizontal scroll position
+ ``item:set_scroll_pos_x_callback(cb)``
+
+ Set callback for getting the item’s horizontal scroll position. This allows
+ the script to control which part of the element is displayed by the item.
+ Do not attempt to access the item’s ``scroll_pos_x`` property from the
+ callback, as this will result in infinite recursion.
+
+ The callback must return a floating-point number, and takes no parameters.
+ A value of 0.0 aligns the left edge of the element with the left edge of the
+ item; larger values pan right. Call with ``nil`` as the argument to restore
+ the default horizontal scroll position handler (based on bindings in the
+ ``xscroll`` child element).
+Get item vertical scroll position
+ ``item:set_scroll_pos_y_callback(cb)``
+
+ Set callback for getting the item’s vertical scroll position. This allows
+ the script to control which part of the element is displayed by the item.
+ Do not attempt to access the item’s ``scroll_pos_y`` property from the
+ callback, as this will result in infinite recursion.
+
+ The callback must return a floating-point number, and takes no parameters.
+ A value of 0.0 aligns the top edge of the element with the top edge of the
+ item; larger values pan down. Call with ``nil`` as the argument to restore
+ the default vertical scroll position handler (based on bindings in the
+ ``yscroll`` child element).
+
+.. _layscript-events-element:
+
+Layout element events
+~~~~~~~~~~~~~~~~~~~~~
+
+Layout element events apply to an individual visual element definition.
+
+Draw
+ ``element:set_draw_callback(cb)``
+
+ Set callback for additional drawing after the element’s components have been
+ drawn. This gives the script direct control over the final texture when an
+ element item is drawn.
+
+ The callback is passed two arguments: the element state (an integer) and the
+ 32-bit ARGB bitmap at the required size. The callback must not attempt to
+ resize the bitmap.