diff options
Diffstat (limited to 'docs/source/techspecs/layout_script.rst')
-rw-r--r-- | docs/source/techspecs/layout_script.rst | 405 |
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). |