summaryrefslogtreecommitdiffstatshomepage
path: root/docs/source/techspecs/layout_script.rst
blob: f3ff505c9df90966288bf1912d17de31e30063ab (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
.. _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 ``math``, ``table`` and ``string`` objects for manipulating numbers,
  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.