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.rst405
1 files changed, 404 insertions, 1 deletions
diff --git a/docs/source/techspecs/layout_script.rst b/docs/source/techspecs/layout_script.rst
index 968a8fe81fb..2291a3c4eb4 100644
--- a/docs/source/techspecs/layout_script.rst
+++ b/docs/source/techspecs/layout_script.rst
@@ -33,7 +33,13 @@ 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.
+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 an
+introduction to Lua scripting in MAME, see :ref:`luaengine`; for detailed
+descriptions of MAME’s Lua classes, see :ref:`luareference`.
+
+.. _layscript-examples-espial:
Espial: joystick split across ports
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -201,3 +207,400 @@ This function does the following tasks:
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 outlined 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 horizotnally, 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 horiztonal 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 layout file 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 inputs.
+* Looks up the view item 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.
+* 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 layout file object. Has a ``device`` property
+ for obtaining the device that caused the layout file to be loaded, and a
+ ``views`` property for obtaining the layout’s views (indexed by name).
+* ``machine`` giving MAME’s current running machine.
+* ``emu.render_bounds`` and ``emu.render_color`` functions for creating bounds
+ and colour objects.
+* ``emu.print_error``, ``emu.print_info`` and ``emu.print_debug`` functions for
+ diagnostic output.
+* Standard Lua ``pairs``, ``ipairs``, ``table.insert`` and ``table.remove``
+ functions for manipulating tables and other containers.
+* Standard Lua ``print`` function for text output to the console.
+* Standard Lua ``string.format`` function for string formatting.
+
+
+.. _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).