.. _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 ` 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 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 `. 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 ` 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 ` 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 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 ` 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 `. * Looks up the :ref:`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 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 ` object. Has a ``device`` property for obtaining the :ref:`device ` that caused the layout file to be loaded, and a ``views`` property for obtaining the layout’s :ref:`views ` (indexed by name). * ``machine`` giving MAME’s current :ref:`running 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 `, :ref:`bounds ` and :ref:`colour ` 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 `. * ``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. Pointer updated ``view:set_pointer_updated_callback(cb)`` Called when a pointer enters, moves or changes button state over the view. The callback function is passed nine arguments: * The pointer type as a string. This will be ``mouse``, ``pen``, ``touch`` or ``unknown``, and will not change for the lifetime of a pointer. * The pointer ID. This will be a non-negative integer that will not change for the lifetime of a pointer. Pointer ID values are recycled aggressively. * The device ID. This will be a non-negative integer that can be used to group pointers for recognising multi-touch gestures. * The horizontal position of the pointer in layout coordinates. * The vertical position of the pointer in layout coordinates. * A bit mask representing the currently pressed buttons. The primary button is the least significant bit. * A bit mask representing the buttons that were pressed in this update. The primary button is the least significant bit. * A bit mask representing the buttons that were released in this update. The primary button is the least significant bit. * The click count. This is positive for multi-click actions, or negative if a click is turned into a hold or drag. This only applies to the primary button. The callback function has no return value. Call with ``nil`` as the argument to remove the event handler. Pointer left ``view:set_pointer_left_callback(cb)`` Called when a pointer leaves the view normally. After receiving this event, the pointer ID may be reused for a new pointer. The callback function is passed seven arguments: * The pointer type as a string. This will be ``mouse``, ``pen``, ``touch`` or ``unknown``, and will not change for the lifetime of a pointer. * The pointer ID. This will be a non-negative integer that will not change for the lifetime of a pointer. Pointer ID values are recycled aggressively. * The device ID. This will be a non-negative integer that can be used to group pointers for recognising multi-touch gestures. * The horizontal position of the pointer in layout coordinates. * The vertical position of the pointer in layout coordinates. * A bit mask representing the buttons that were released in this update. The primary button is the least significant bit. * The click count. This is positive for multi-click actions, or negative if a click is turned into a hold or drag. This only applies to the primary button. The callback function has no return value. Call with ``nil`` as the argument to remove the event handler. Pointer aborted ``view:set_pointer_aborted_callback(cb)`` Called when a pointer leaves the view abnormally. After receiving this event, the pointer ID may be reused for a new pointer. The callback function is passed seven arguments: * The pointer type as a string. This will be ``mouse``, ``pen``, ``touch`` or ``unknown``, and will not change for the lifetime of a pointer. * The pointer ID. This will be a non-negative integer that will not change for the lifetime of a pointer. Pointer ID values are recycled aggressively. * The device ID. This will be a non-negative integer that can be used to group pointers for recognising multi-touch gestures. * The horizontal position of the pointer in layout coordinates. * The vertical position of the pointer in layout coordinates. * A bit mask representing the buttons that were released in this update. The primary button is the least significant bit. * The click count. This is positive for multi-click actions, or negative if a click is turned into a hold or drag. This only applies to the primary button. The callback function has no return value. Call with ``nil`` as the argument to remove the event handler. Forget pointers ``view:set_forget_pointers_callback(cb)`` Called when the view should stop processing pointer input. This can happen in a number of situations, including: * The user activated a menu. * The view configuration will change. * The view will be deactivated. 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. Call with ``nil`` as the argument to remove the event handler.