From 5afce6cfab6320ee062da5f7b7438294672f9af1 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Thu, 18 Nov 2021 10:34:38 +1100 Subject: emu/render.cpp, emu/rendlay.cpp: Added scroll support for all elements. --- docs/source/techspecs/layout_files.rst | 57 +++- docs/source/techspecs/layout_script.rst | 64 +++- docs/source/techspecs/luareference.rst | 76 ++++- scripts/build/complay.py | 28 +- src/emu/emufwd.h | 9 +- src/emu/render.cpp | 85 +++-- src/emu/render.h | 2 +- src/emu/rendlay.cpp | 547 ++++++++++++++++++++++++-------- src/emu/rendlay.h | 295 ++++++++++------- src/frontend/mame/luaengine_render.cpp | 72 ++++- 10 files changed, 916 insertions(+), 319 deletions(-) diff --git a/docs/source/techspecs/layout_files.rst b/docs/source/techspecs/layout_files.rst index be4965a9e95..c43a7df8e1b 100644 --- a/docs/source/techspecs/layout_files.rst +++ b/docs/source/techspecs/layout_files.rst @@ -406,8 +406,8 @@ usually comes from an I/O port field or an emulated output (see :ref:`layfile-interact-elemstate` for information on connecting an element to an emulated I/O port or output). Any component of an element may be restricted to only drawing when the element’s state is a particular value. Some components -(e.g. multi-segment displays and reels) use the state directly to determine -their appearance. +(e.g. multi-segment displays) use the state directly to determine their +appearance. Each element has its own internal coordinate system. The bounds of the element’s coordinate system are computed as the union of the bounds of the @@ -591,10 +591,6 @@ simplecounter to set text alignment. If present, the ``align`` attribute must be an integer, where 0 (zero) means centred, 1 (one) means left-aligned, and 2 (two) means right-aligned; if absent, the text will be centred. -reel - Used for drawing slot machine reels. Supported attributes include - ``symbollist``, ``stateoffset``, ``numsymbolsvisible``, ``reelreversed``, - and ``beltreel``. An example element that draws a static left-aligned text string: @@ -839,6 +835,48 @@ have their colour and position/size animated by supplying multiple ``color`` and/or ``bounds`` child elements with ``state`` attributes. See :ref:`layfile-interact-itemanim` for details. +Layout elements (``element`` elements) may be configured to show only part of +the element’s width or height using ``xscroll`` and/or ``yscroll`` child +elements. This can be used for devices like slot machine reels. The +``xscroll`` and ``yscroll`` elements support the same attributes: + +size + The size of the horizontal or vertical scroll window, as a proportion of the + element’s width or height, respectively. Must be in the range 0.01 to 1.0, + inclusive, if present (1% of the width/height to the full width/height). By + default, the entire width and height of the element is shown. +wrap + Whether the element should wrap horizontally or vertically. Must be either + ``yes`` or ``no`` if present. By default, items do not wrap horizontally or + vertically. +inputtag + If present, the horizontal or vertical scroll position will be taken from + the value of the corresponding I/O port. Specifies the tag path of an I/O + port relative to the device that caused the layout file to be loaded. The + raw value from the input port is used, active-low switch values are not + normalised. +name + If present, the horizontal or vertical scroll position will be taken from + the correspondingly named output. +mask + If present, the horizontal or vertical scroll position will be masked with + the value and shifted to the right to remove trailing zeroes (for example a + mask of 0x05 will result in no shift, while a mask of 0x68 will result in + the value being shifted three bits to the right). Note that this applies to + output values (specified with the ``name`` attribute) as well as input port + values (specified with the ``inputtag`` attribute). Must be an integer + value if present. If not present, it is equivalent to all 32 bits being + set. +min + Minimum horizontal or vertical scroll position value. When the horizontal + or vertical scroll position has this value, the left or top edge or the + scroll window will be aligned with the left or top edge of the element. + Must be an integer value if present. Defaults to zero. +max + Maximum horizontal or vertical scroll position value. Must be an integer + value if present. Defaults to the ``mask`` value shifted to the right to + remove trailing zeroes. + .. _layfile-parts-collections: @@ -1149,9 +1187,8 @@ Clickable items item will activate the emulated switch. State-dependent components Some components will be drawn differently depending on the containing - element’s state. These include the dot matrix, multi-segment LED display, - simple counter and reel elements. See :ref:`layfile-parts-elements` for - details. + element’s state. These include the dot matrix, multi-segment LED display + and simple counter elements. See :ref:`layfile-parts-elements` for details. Conditionally-drawn components Components may be conditionally drawn or hidden depending on the containing element’s state by supplying ``state`` and/or ``statemask`` attributes. See @@ -1298,7 +1335,7 @@ If the ``animate`` child element has a ``mask`` attribute, the item’s animatio state will be masked with the ``mask`` value and shifted to the right to remove trailing zeroes (for example a mask of 0x05 will result in no shift, while a mask of 0xb0 will result in the value being shifted four bits to the right). -Note that the ``mask`` attribute applies to output value (specified with the +Note that the ``mask`` attribute applies to output values (specified with the ``name`` attribute) as well as input port values (specified with the ``inputtag`` attribute). If the ``mask`` attribute is present, it must be an integer value. If the ``mask`` attribute is not present, it is equivalent to diff --git a/docs/source/techspecs/layout_script.rst b/docs/source/techspecs/layout_script.rst index 7602de6f7f4..f2b8ef1cbc3 100644 --- a/docs/source/techspecs/layout_script.rst +++ b/docs/source/techspecs/layout_script.rst @@ -259,7 +259,7 @@ Here’s our layout file: - + @@ -289,7 +289,7 @@ Here’s our layout file: - + @@ -301,7 +301,7 @@ Here’s our layout file: - + @@ -595,7 +595,7 @@ Get item bounds 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)`` + ``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`` @@ -605,3 +605,59 @@ Get item colour 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 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 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). diff --git a/docs/source/techspecs/luareference.rst b/docs/source/techspecs/luareference.rst index 3cea683f547..85c7ea8cb8a 100644 --- a/docs/source/techspecs/luareference.rst +++ b/docs/source/techspecs/luareference.rst @@ -2815,7 +2815,7 @@ view.has_art Layout view item ~~~~~~~~~~~~~~~~ -Wraps MAME’s ``layout_view::item`` class, representing an item in a view. An +Wraps MAME’s ``layout_view_item`` class, representing an item in a view. An item is drawn as a rectangular textured surface. The texture is supplied by an emulated screen or a layout element. @@ -2832,7 +2832,7 @@ Methods item:set_state(state) Set the value used as the element state and animation state in the absence of bindings. The argument must be an integer. -item.set_element_state_callback(cb) +item:set_element_state_callback(cb) Set a function to call to obtain the element state for the item. The function must accept no arguments and return an integer. Call with ``nil`` to restore the default element state callback (based on bindings in the XML @@ -2844,7 +2844,7 @@ item.set_element_state_callback(cb) This callback will not be used to obtain the animation state for the item, even if the item lacks explicit animation state bindings in the XML layout file. -item.set_animation_state_callback(cb) +item:set_animation_state_callback(cb) Set a function to call to obtain the animation state for the item. The function must accept no arguments and return an integer. Call with ``nil`` to restore the default animation state callback (based on bindings in the @@ -2852,7 +2852,7 @@ item.set_animation_state_callback(cb) Note that the function must not access the item’s ``animation_state`` property, as this will result in infinite recursion. -item.set_bounds_callback(cb) +item:set_bounds_callback(cb) Set a function to call to obtain the bounds for the item. The function must accept no arguments and return a :ref:`render bounds ` object in render target @@ -2862,7 +2862,7 @@ item.set_bounds_callback(cb) Note that the function must not access the item’s ``bounds`` property, as this will result in infinite recursion. -item.set_color_callback(cb) +item:set_color_callback(cb) Set a function to call to obtain the multiplier colour for the item. The function must accept no arguments and return a :ref:`render colour ` object. Call with ``nil`` @@ -2871,6 +2871,50 @@ item.set_color_callback(cb) Note that the function must not access the item’s ``color`` property, as this will result in infinite recursion. +item:set_scroll_size_x_callback(cb) + Set a function to call to obtain the size of the horizontal scroll window as + a proportion of the associated element’s width. The function must accept no + arguments and return a floating-point value. Call with ``nil`` to restore + the default horizontal scroll window size callback (based on the ``xscroll`` + child element in the XML layout file). + + Note that the function must not access the item’s ``scroll_size_x`` + property, as this will result in infinite recursion. +item:set_scroll_size_y_callback(cb) + Set a function to call to obtain the size of the vertical scroll window as a + proportion of the associated element’s height. The function must accept no + arguments and return a floating-point value. Call with ``nil`` to restore + the default vertical scroll window size callback (based on the ``yscroll`` + child element in the XML layout file). + + Note that the function must not access the item’s ``scroll_size_y`` + property, as this will result in infinite recursion. +item:set_scroll_pos_x_callback(cb) + Set a function to call to obtain the horizontal scroll position. A value of + zero places the horizontal scroll window at the left edge of the associated + element. If the item does not wrap horizontally, a value of 1.0 places the + horizontal scroll window at the right edge of the associated element; if the + item wraps horizontally, a value of 1.0 corresponds to wrapping back to the + left edge of the associated element. The function must accept no arguments + and return a floating-point value. Call with ``nil`` to restore the default + horizontal scroll position callback (based on bindings in the ``xscroll`` + child element in the XML layout file). + + Note that the function must not access the item’s ``scroll_pos_x`` property, + as this will result in infinite recursion. +item:set_scroll_pos_y_callback(cb) + Set a function to call to obtain the vertical scroll position. A value of + zero places the vertical scroll window at the top edge of the associated + element. If the item does not wrap vertically, a value of 1.0 places the + vertical scroll window at the bottom edge of the associated element; if the + item wraps vertically, a value of 1.0 corresponds to wrapping back to the + left edge of the associated element. The function must accept no arguments + and return a floating-point value. Call with ``nil`` to restore the default + vertical scroll position callback (based on bindings in the ``yscroll`` + child element in the XML layout file). + + Note that the function must not access the item’s ``scroll_pos_y`` property, + as this will result in infinite recursion. Properties ^^^^^^^^^^ @@ -2892,6 +2936,28 @@ item.color (read-only) The item’s colour for the current state. The colour of the screen or element texture is multiplied by this colour. This is a :ref:`render colour ` object. +item.scroll_wrap_x (read-only) + A Boolean indicating whether the item wraps horizontally. +item.scroll_wrap_y (read-only) + A Boolean indicating whether the item wraps vertically. +item.scroll_size_x (read/write) + Get the item’s horizontal scroll window size for the current state, or set + the horizontal scroll window size to use in the absence of bindings. This + is a floating-point value representing a proportion of the associated + element’s width. +item.scroll_size_y (read/write) + Get the item’s vertical scroll window size for the current state, or set the + vertical scroll window size to use in the absence of bindings. This is a + floating-point value representing a proportion of the associated element’s + height. +item.scroll_pos_x (read/write) + Get the item’s horizontal scroll position for the current state, or set the + horizontal scroll position size to use in the absence of bindings. This is + a floating-point value. +item.scroll_pos_y (read/write) + Get the item’s vertical scroll position for the current state, or set the + vertical position size to use in the absence of bindings. This is a + floating-point value. item.blend_mode (read-only) Get the item’s blend mode. This is an integer value, where 0 means no blending, 1 means alpha blending, 2 means RGB multiplication, 3 means diff --git a/scripts/build/complay.py b/scripts/build/complay.py index 81516bc3ee7..996908ecde0 100755 --- a/scripts/build/complay.py +++ b/scripts/build/complay.py @@ -253,7 +253,7 @@ class LayoutChecker(Minifyer): if self.checkIntAttribute('orientation', attrs, 'rotate', 0) not in self.ORIENTATIONS: self.handleError('Element orientation attribute rotate "%s" is unsupported' % (attrs['rotate'], )) for name in ('swapxy', 'flipx', 'flipy'): - if (attrs.get(name, 'no') not in self.YESNO) and (not self.VARPATTERN.match(attrs['yesno'])): + if (attrs.get(name, 'no') not in self.YESNO) and (not self.VARPATTERN.match(attrs[name])): self.handleError('Element orientation attribute %s "%s" is not "yes" or "no"' % (name, attrs[name])) def checkColor(self, attrs): @@ -332,6 +332,8 @@ class LayoutChecker(Minifyer): self.have_bounds.append(None if 'group' == name else { }) self.have_orientation.append(False) self.have_color.append(None if 'group' == name else { }) + self.have_xscroll.append(None if ('group' == name) or ('screen' == name) else False) + self.have_yscroll.append(None if ('group' == name) or ('screen' == name) else False) def rootStartHandler(self, name, attrs): if 'mamelayout' != name: @@ -665,6 +667,24 @@ class LayoutChecker(Minifyer): else: self.handleError('Duplicate element color (previous %s)' % (self.have_color[-1], )) self.checkColor(attrs) + elif ('xscroll' == name) or ('yscroll' == name): + have_scroll = self.have_xscroll if 'xscroll' == name else self.have_yscroll + if have_scroll[-1] is None: + self.handleError('Encountered unexpected element %s' % (name, )) + elif have_scroll[-1]: + self.handleError('Duplicate element %s' % (name, )) + else: + have_scroll[-1] = self.formatLocation() + self.checkFloatAttribute(name, attrs, 'size', 1.0) + if (attrs.get('wrap', 'no') not in self.YESNO) and (not self.VARPATTERN.match(attrs['wrap'])): + self.handleError('Element %s attribute wrap "%s" is not "yes" or "no"' % (name, attrs['wrap'])) + if 'inputtag' in attrs: + if 'name' in attrs: + self.handleError('Element %s has both attribute inputtag and attribute name' % (name, )) + self.checkTag(attrs['inputtag'], name, 'inputtag') + self.checkIntAttribute(name, attrs, 'mask', None) + self.checkIntAttribute(name, attrs, 'min', None) + self.checkIntAttribute(name, attrs, 'max', None) else: self.handleError('Encountered unexpected element %s' % (name, )) self.ignored_depth = 1 @@ -673,6 +693,8 @@ class LayoutChecker(Minifyer): self.have_bounds.pop() self.have_orientation.pop() self.have_color.pop() + self.have_xscroll.pop() + self.have_yscroll.pop() self.handlers.pop() def setDocumentLocator(self, locator): @@ -688,6 +710,8 @@ class LayoutChecker(Minifyer): self.have_bounds = [ ] self.have_orientation = [ ] self.have_color = [ ] + self.have_xscroll = [ ] + self.have_yscroll = [ ] self.generated_element_names = False self.generated_group_names = False super(LayoutChecker, self).startDocument() @@ -709,6 +733,8 @@ class LayoutChecker(Minifyer): del self.have_bounds del self.have_orientation del self.have_color + del self.have_xscroll + del self.have_yscroll del self.generated_element_names del self.generated_group_names super(LayoutChecker, self).endDocument() diff --git a/src/emu/emufwd.h b/src/emu/emufwd.h index b3969a8fc99..8b8e59b9250 100644 --- a/src/emu/emufwd.h +++ b/src/emu/emufwd.h @@ -193,9 +193,6 @@ class network_manager; class output_manager; // declared in render.h -class layout_element; -class layout_view; -class layout_file; class render_container; class render_manager; class render_target; @@ -204,6 +201,12 @@ class render_texture; // declared in rendfont.h class render_font; +// declared in rendlay.h +class layout_element; +class layout_view_item; +class layout_view; +class layout_file; + // declared in romentry.h class rom_entry; diff --git a/src/emu/render.cpp b/src/emu/render.cpp index 0fef6bafe41..42a837062a3 100644 --- a/src/emu/render.cpp +++ b/src/emu/render.cpp @@ -1097,7 +1097,7 @@ unsigned render_target::configured_view(const char *viewname, int targetindex, i screen_device const &screen = screens[index() % screens.size()]; for (unsigned i = 0; !view && (m_views.size() > i); ++i) { - for (layout_view::item &viewitem : m_views[i].first.items()) + for (layout_view_item &viewitem : m_views[i].first.items()) { screen_device const *const viewscreen(viewitem.screen()); if (viewscreen == &screen) @@ -1286,7 +1286,7 @@ void render_target::compute_minimum_size(s32 &minwidth, s32 &minheight) throw emu_fatalerror("Mandatory artwork is missing"); // scan the current view for all screens - for (layout_view::item &curitem : current_view().items()) + for (layout_view_item &curitem : current_view().items()) { screen_device const *const screen = curitem.screen(); if (screen) @@ -1370,7 +1370,7 @@ render_primitive_list &render_target::get_primitives() { // we're running - iterate over items in the view current_view().prepare_items(); - for (layout_view::item &curitem : current_view().visible_items()) + for (layout_view_item &curitem : current_view().visible_items()) { // first apply orientation to the bounds render_bounds bounds = curitem.bounds(); @@ -1391,7 +1391,7 @@ render_primitive_list &render_target::get_primitives() if (curitem.screen()) add_container_primitives(list, root_xform, item_xform, curitem.screen()->container(), curitem.blend_mode()); else - add_element_primitives(list, item_xform, *curitem.element(), curitem.element_state(), curitem.blend_mode()); + add_element_primitives(list, item_xform, curitem); } } else @@ -1494,10 +1494,10 @@ bool render_target::map_point_container(s32 target_x, s32 target_y, render_conta auto const found(std::find_if( items.begin(), items.end(), - [&container] (layout_view::item &item) { return &item.screen()->container() == &container; })); + [&container] (layout_view_item &item) { return &item.screen()->container() == &container; })); if (items.end() != found) { - layout_view::item &item(*found); + layout_view_item &item(*found); render_bounds const bounds(item.bounds()); if (bounds.includes(target_f.first, target_f.second)) { @@ -1555,7 +1555,7 @@ bool render_target::map_point_input(s32 target_x, s32 target_y, ioport_port *&in { if (m_hit_test[i] && m_hit_test[items.size() + i]) { - layout_view::item &item(items[i]); + layout_view_item &item(items[i]); render_bounds const bounds(item.bounds()); if (bounds.includes(target_f.first, target_f.second)) { @@ -2523,11 +2523,13 @@ void render_target::add_container_primitives(render_primitive_list &list, const // for an element in the current state //------------------------------------------------- -void render_target::add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_element &element, int state, int blendmode) +void render_target::add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_view_item &item) { + layout_element &element(*item.element()); + int const blendmode(item.blend_mode()); + // limit state range to non-negative values - if (state < 0) - state = 0; + int const state((std::max)(item.element_state(), 0)); // get a pointer to the relevant texture render_texture *texture = element.state_texture(state); @@ -2537,29 +2539,70 @@ void render_target::add_element_primitives(render_primitive_list &list, const ob // configure the basics prim->color = xform.color; - prim->flags = PRIMFLAG_TEXORIENT(xform.orientation) | PRIMFLAG_BLENDMODE(blendmode) | PRIMFLAG_TEXFORMAT(texture->format()); + prim->flags = + PRIMFLAG_TEXORIENT(xform.orientation) | + PRIMFLAG_TEXFORMAT(texture->format()) | + PRIMFLAG_BLENDMODE(blendmode) | + PRIMFLAG_TEXWRAP((item.scroll_wrap_x() || item.scroll_wrap_y()) ? 1 : 0); // compute the bounds - s32 width = render_round_nearest(xform.xscale); - s32 height = render_round_nearest(xform.yscale); - prim->bounds.set_wh(render_round_nearest(xform.xoffs), render_round_nearest(xform.yoffs), float(width), float(height)); + float const primwidth(render_round_nearest(xform.xscale)); + float const primheight(render_round_nearest(xform.yscale)); + prim->bounds.set_wh(render_round_nearest(xform.xoffs), render_round_nearest(xform.yoffs), primwidth, primheight); prim->full_bounds = prim->bounds; - if (xform.orientation & ORIENTATION_SWAP_XY) - std::swap(width, height); - width = (std::min)(width, m_maxtexwidth); - height = (std::min)(height, m_maxtexheight); // get the scaled texture and append it - texture->get_scaled(width, height, prim->texture, list, prim->flags); + float const xsize(item.scroll_size_x()); + float const ysize(item.scroll_size_y()); + s32 texwidth = render_round_nearest(((xform.orientation & ORIENTATION_SWAP_XY) ? primwidth : primheight) / xsize); + s32 texheight = render_round_nearest(((xform.orientation & ORIENTATION_SWAP_XY) ? primheight : primwidth) / ysize); + texwidth = (std::min)(texwidth, m_maxtexwidth); + texheight = (std::min)(texheight, m_maxtexheight); + texture->get_scaled(texwidth, texheight, prim->texture, list, prim->flags); // compute the clip rect render_bounds cliprect = prim->bounds & m_bounds; // determine UV coordinates and apply clipping - prim->texcoords = oriented_texcoords[xform.orientation]; - bool clipped = render_clip_quad(&prim->bounds, &cliprect, &prim->texcoords); + float const xwindow((xform.orientation & ORIENTATION_SWAP_XY) ? primwidth : primheight); + float const ywindow((xform.orientation & ORIENTATION_SWAP_XY) ? primheight : primwidth); + float const xrange(float(texwidth) - (item.scroll_wrap_x() ? 0.0f : xwindow)); + float const yrange(float(texheight) - (item.scroll_wrap_y() ? 0.0f : ywindow)); + float const xoffset(render_round_nearest(item.scroll_pos_x() * xrange) / float(texwidth)); + float const yoffset(render_round_nearest(item.scroll_pos_y() * yrange) / float(texheight)); + float const xend(xoffset + (xwindow / float(texwidth))); + float const yend(yoffset + (ywindow / float(texheight))); + switch (xform.orientation) + { + default: + case 0: + prim->texcoords = render_quad_texuv{ { xoffset, yoffset }, { xend, yoffset }, { xoffset, yend }, { xend, yend } }; + break; + case ORIENTATION_FLIP_X: + prim->texcoords = render_quad_texuv{ { xend, yoffset }, { xoffset, yoffset }, { xend, yend }, { xoffset, yend } }; + break; + case ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xoffset, yend }, { xend, yend }, { xoffset, yoffset }, { xend, yoffset } }; + break; + case ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xend, yend }, { xoffset, yend }, { xend, yoffset }, { xoffset, yoffset } }; + break; + case ORIENTATION_SWAP_XY: + prim->texcoords = render_quad_texuv{ { xoffset, yoffset }, { xoffset, yend }, { xend, yoffset }, { xend, yend } }; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X: + prim->texcoords = render_quad_texuv{ { xoffset, yend }, { xoffset, yoffset }, { xend, yend }, { xend, yoffset } }; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xend, yoffset }, { xend, yend }, { xoffset, yoffset }, { xoffset, yend } }; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xend, yend }, { xend, yoffset }, { xoffset, yend }, { xoffset, yoffset } }; + break; + } // add to the list or free if we're clipped out + bool const clipped = render_clip_quad(&prim->bounds, &cliprect, &prim->texcoords); list.append_or_return(*prim, clipped); } } diff --git a/src/emu/render.h b/src/emu/render.h index 5833a036077..d1d8987ea9b 100644 --- a/src/emu/render.h +++ b/src/emu/render.h @@ -590,7 +590,7 @@ private: bool load_layout_file(const char *dirname, const internal_layout &layout_data, device_t *device = nullptr); bool load_layout_file(device_t &device, util::xml::data_node const &rootnode, const char *searchpath, const char *dirname); void add_container_primitives(render_primitive_list &list, const object_transform &root_xform, const object_transform &xform, render_container &container, int blendmode); - void add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_element &element, int state, int blendmode); + void add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_view_item &item); std::pair map_point_internal(s32 target_x, s32 target_y); // config callbacks diff --git a/src/emu/rendlay.cpp b/src/emu/rendlay.cpp index b6709626d97..41075c2573b 100644 --- a/src/emu/rendlay.cpp +++ b/src/emu/rendlay.cpp @@ -1120,6 +1120,121 @@ inline render_color interpolate_color(emu::render::detail::color_vector const &s } } + +unsigned get_state_shift(ioport_value mask) +{ + // get shift to right-align LSB + unsigned result(0U); + while (mask && !BIT(mask, 0)) + { + ++result; + mask >>= 1; + } + return result; +} + +std::string make_child_output_tag( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode, + char const *child) +{ + util::xml::data_node const *const childnode(itemnode.get_child(child)); + if (childnode) + return std::string(env.get_attribute_string(*childnode, "name")); + else + return std::string(); +} + +std::string make_child_input_tag( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode, + char const *child) +{ + util::xml::data_node const *const childnode(itemnode.get_child(child)); + return childnode ? env.get_attribute_subtag(*childnode, "inputtag") : std::string(); +} + +ioport_value make_child_mask( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode, + char const *child) +{ + util::xml::data_node const *const childnode(itemnode.get_child(child)); + return childnode ? env.get_attribute_int(*childnode, "mask", ~ioport_value(0)) : ~ioport_value(0); +} + +bool make_child_wrap( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode, + char const *child) +{ + util::xml::data_node const *const childnode(itemnode.get_child(child)); + return childnode ? env.get_attribute_bool(*childnode, "wrap", false) : false; +} + +float make_child_size( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode, + char const *child) +{ + util::xml::data_node const *const childnode(itemnode.get_child(child)); + return std::clamp(childnode ? env.get_attribute_float(*childnode, "size", 1.0f) : 1.0f, 0.01f, 1.0f); +} + +ioport_value make_child_min( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode, + char const *child) +{ + util::xml::data_node const *const childnode(itemnode.get_child(child)); + return childnode ? env.get_attribute_int(*childnode, "min", ioport_value(0)) : ioport_value(0); +} + +ioport_value make_child_max( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode, + char const *child, + ioport_value mask) +{ + util::xml::data_node const *const childnode(itemnode.get_child(child)); + ioport_value const dflt(mask >> get_state_shift(mask)); + return childnode ? env.get_attribute_int(*childnode, "max", dflt) : dflt; +} + +std::string make_input_tag( + emu::render::detail::view_environment &env, + util::xml::data_node const &itemnode) +{ + return env.get_attribute_subtag(itemnode, "inputtag"); +} + +int get_blend_mode(emu::render::detail::view_environment &env, util::xml::data_node const &itemnode) +{ + // see if there's a blend mode attribute + std::string const *const mode(itemnode.get_attribute_string_ptr("blend")); + if (mode) + { + if (*mode == "none") + return BLENDMODE_NONE; + else if (*mode == "alpha") + return BLENDMODE_ALPHA; + else if (*mode == "multiply") + return BLENDMODE_RGB_MULTIPLY; + else if (*mode == "add") + return BLENDMODE_ADD; + else + throw layout_syntax_error(util::string_format("unknown blend mode %s", *mode)); + } + + // fall back to implicit blend mode based on element type + if (!strcmp(itemnode.get_name(), "screen")) + return -1; // magic number recognised by render.cpp to allow per-element blend mode + else if (!strcmp(itemnode.get_name(), "overlay")) + return BLENDMODE_RGB_MULTIPLY; + else + return BLENDMODE_ALPHA; +} + } // anonymous namespace @@ -4096,7 +4211,7 @@ layout_view::~layout_view() // get_item - get item by ID //------------------------------------------------- -layout_view::item *layout_view::get_item(std::string const &id) +layout_view_item *layout_view::get_item(std::string const &id) { auto const found(m_items_by_id.find(id)); return (m_items_by_id.end() != found) ? &found->second : nullptr; @@ -4502,10 +4617,10 @@ std::string layout_view::make_name(layout_environment &env, util::xml::data_node //************************************************************************** //------------------------------------------------- -// item - constructor +// layout_view_item - constructor //------------------------------------------------- -layout_view::item::item( +layout_view_item::layout_view_item( view_environment &env, util::xml::data_node const &itemnode, element_map &elemmap, @@ -4514,11 +4629,29 @@ layout_view::item::item( render_color const &color) : m_element(find_element(env, itemnode, elemmap)) , m_output(env.device(), std::string(env.get_attribute_string(itemnode, "name"))) - , m_animoutput(env.device(), make_animoutput_tag(env, itemnode)) + , m_animoutput(env.device(), make_child_output_tag(env, itemnode, "animate")) + , m_scrollxoutput(env.device(), make_child_output_tag(env, itemnode, "xscroll")) + , m_scrollyoutput(env.device(), make_child_output_tag(env, itemnode, "yscroll")) , m_animinput_port(nullptr) + , m_scrollxinput_port(nullptr) + , m_scrollyinput_port(nullptr) + , m_scrollwrapx(make_child_wrap(env, itemnode, "xscroll")) + , m_scrollwrapy(make_child_wrap(env, itemnode, "yscroll")) , m_elem_state(m_element ? m_element->default_state() : 0) - , m_animmask(make_animmask(env, itemnode)) + , m_scrollsizex(make_child_size(env, itemnode, "xscroll")) + , m_scrollsizey(make_child_size(env, itemnode, "yscroll")) + , m_scrollposx(0.0f) + , m_scrollposy(0.0f) + , m_animmask(make_child_mask(env, itemnode, "animate")) + , m_scrollxmask(make_child_mask(env, itemnode, "xscroll")) + , m_scrollymask(make_child_mask(env, itemnode, "yscroll")) + , m_scrollxmin(make_child_min(env, itemnode, "xscroll")) + , m_scrollymin(make_child_min(env, itemnode, "yscroll")) + , m_scrollxmax(make_child_max(env, itemnode, "xscroll", m_scrollxmask)) + , m_scrollymax(make_child_max(env, itemnode, "yscroll", m_scrollymask)) , m_animshift(get_state_shift(m_animmask)) + , m_scrollxshift(get_state_shift(m_scrollxmask)) + , m_scrollyshift(get_state_shift(m_scrollymask)) , m_input_port(nullptr) , m_input_field(nullptr) , m_input_mask(env.get_attribute_int(itemnode, "inputmask", 0)) @@ -4531,11 +4664,15 @@ layout_view::item::item( , m_visibility_mask(env.visibility_mask()) , m_id(env.get_attribute_string(itemnode, "id")) , m_input_tag(make_input_tag(env, itemnode)) - , m_animinput_tag(make_animinput_tag(env, itemnode)) + , m_animinput_tag(make_child_input_tag(env, itemnode, "animate")) + , m_scrollxinput_tag(make_child_input_tag(env, itemnode, "xscroll")) + , m_scrollyinput_tag(make_child_input_tag(env, itemnode, "yscroll")) , m_rawbounds(make_bounds(env, itemnode, trans)) , m_have_output(!env.get_attribute_string(itemnode, "name").empty()) , m_input_raw(env.get_attribute_bool(itemnode, "inputraw", 0)) - , m_have_animoutput(!make_animoutput_tag(env, itemnode).empty()) + , m_have_animoutput(!make_child_output_tag(env, itemnode, "animate").empty()) + , m_have_scrollxoutput(!make_child_output_tag(env, itemnode, "xscroll").empty()) + , m_have_scrollyoutput(!make_child_output_tag(env, itemnode, "yscroll").empty()) , m_has_clickthrough(!env.get_attribute_string(itemnode, "clickthrough").empty()) { // fetch common data @@ -4562,6 +4699,14 @@ layout_view::item::item( { throw layout_syntax_error(util::string_format("item of type %s requires an element tag", itemnode.get_name())); } + else if (m_scrollxmin == m_scrollxmax) + { + throw layout_syntax_error(util::string_format("item X scroll minimum and maximum both equal to %u", m_scrollxmin)); + } + else if (m_scrollymin == m_scrollymax) + { + throw layout_syntax_error(util::string_format("item Y scroll minimum and maximum both equal to %u", m_scrollymin)); + } // this can be called before resolving tags, make it return something valid m_bounds = m_rawbounds; @@ -4570,10 +4715,10 @@ layout_view::item::item( //------------------------------------------------- -// item - destructor +// layout_view_item - destructor //------------------------------------------------- -layout_view::item::~item() +layout_view_item::~layout_view_item() { } @@ -4583,7 +4728,7 @@ layout_view::item::~item() // obtain element state value //------------------------------------------------- -void layout_view::item::set_element_state_callback(state_delegate &&handler) +void layout_view_item::set_element_state_callback(state_delegate &&handler) { if (!handler.isnull()) m_get_elem_state = std::move(handler); @@ -4597,7 +4742,7 @@ void layout_view::item::set_element_state_callback(state_delegate &&handler) // obtain animation state //------------------------------------------------- -void layout_view::item::set_animation_state_callback(state_delegate &&handler) +void layout_view_item::set_animation_state_callback(state_delegate &&handler) { if (!handler.isnull()) m_get_anim_state = std::move(handler); @@ -4611,7 +4756,7 @@ void layout_view::item::set_animation_state_callback(state_delegate &&handler) // bounds //------------------------------------------------- -void layout_view::item::set_bounds_callback(bounds_delegate &&handler) +void layout_view_item::set_bounds_callback(bounds_delegate &&handler) { if (!handler.isnull()) m_get_bounds = std::move(handler); @@ -4625,7 +4770,7 @@ void layout_view::item::set_bounds_callback(bounds_delegate &&handler) // color //------------------------------------------------- -void layout_view::item::set_color_callback(color_delegate &&handler) +void layout_view_item::set_color_callback(color_delegate &&handler) { if (!handler.isnull()) m_get_color = std::move(handler); @@ -4634,11 +4779,67 @@ void layout_view::item::set_color_callback(color_delegate &&handler) } +//------------------------------------------------- +// set_scroll_size_x_callback - set callback to +// obtain horizontal scroll window size +//------------------------------------------------- + +void layout_view_item::set_scroll_size_x_callback(scroll_size_delegate &&handler) +{ + if (!handler.isnull()) + m_get_scroll_size_x = std::move(handler); + else + m_get_scroll_size_x = default_get_scroll_size_x(); +} + + +//------------------------------------------------- +// set_scroll_size_y_callback - set callback to +// obtain vertical scroll window size +//------------------------------------------------- + +void layout_view_item::set_scroll_size_y_callback(scroll_size_delegate &&handler) +{ + if (!handler.isnull()) + m_get_scroll_size_y = std::move(handler); + else + m_get_scroll_size_y = default_get_scroll_size_y(); +} + + +//------------------------------------------------- +// set_scroll_pos_x_callback - set callback to +// obtain horizontal scroll position +//------------------------------------------------- + +void layout_view_item::set_scroll_pos_x_callback(scroll_pos_delegate &&handler) +{ + if (!handler.isnull()) + m_get_scroll_pos_x = std::move(handler); + else + m_get_scroll_pos_x = default_get_scroll_pos_x(); +} + + +//------------------------------------------------- +// set_scroll_pos_y_callback - set callback to +// obtain vertical scroll position +//------------------------------------------------- + +void layout_view_item::set_scroll_pos_y_callback(scroll_pos_delegate &&handler) +{ + if (!handler.isnull()) + m_get_scroll_pos_y = std::move(handler); + else + m_get_scroll_pos_y = default_get_scroll_pos_y(); +} + + //------------------------------------------------- // resolve_tags - resolve tags, if any are set //------------------------------------------------- -void layout_view::item::resolve_tags() +void layout_view_item::resolve_tags() { // resolve element state output and set default value if (m_have_output) @@ -4648,13 +4849,21 @@ void layout_view::item::resolve_tags() m_output = m_element->default_state(); } - // resolve animation state output + // resolve animation state and scroll outputs if (m_have_animoutput) m_animoutput.resolve(); + if (m_have_scrollxoutput) + m_scrollxoutput.resolve(); + if (m_have_scrollyoutput) + m_scrollyoutput.resolve(); - // resolve animation state input + // resolve animation state and scroll inputs if (!m_animinput_tag.empty()) m_animinput_port = m_element->machine().root_device().ioport(m_animinput_tag); + if (!m_scrollxinput_tag.empty()) + m_scrollxinput_port = m_element->machine().root_device().ioport(m_scrollxinput_tag); + if (!m_scrollyinput_tag.empty()) + m_scrollyinput_port = m_element->machine().root_device().ioport(m_scrollyinput_tag); // resolve element state input if (!m_input_tag.empty()) @@ -4684,6 +4893,10 @@ void layout_view::item::resolve_tags() m_get_anim_state = default_get_anim_state(); m_get_bounds = default_get_bounds(); m_get_color = default_get_color(); + m_get_scroll_size_x = default_get_scroll_size_x(); + m_get_scroll_size_y = default_get_scroll_size_y(); + m_get_scroll_pos_x = default_get_scroll_pos_x(); + m_get_scroll_pos_y = default_get_scroll_pos_y(); } @@ -4692,18 +4905,18 @@ void layout_view::item::resolve_tags() // state handler //------------------------------------------------- -layout_view::item::state_delegate layout_view::item::default_get_elem_state() +layout_view_item::state_delegate layout_view_item::default_get_elem_state() { if (m_have_output) - return state_delegate(&item::get_output, this); + return state_delegate(&layout_view_item::get_output, this); else if (!m_input_port) - return state_delegate(&item::get_state, this); + return state_delegate(&layout_view_item::get_state, this); else if (m_input_raw) - return state_delegate(&item::get_input_raw, this); + return state_delegate(&layout_view_item::get_input_raw, this); else if (m_input_field) - return state_delegate(&item::get_input_field_cached, this); + return state_delegate(&layout_view_item::get_input_field_cached, this); else - return state_delegate(&item::get_input_field_conditional, this); + return state_delegate(&layout_view_item::get_input_field_conditional, this); } @@ -4712,12 +4925,12 @@ layout_view::item::state_delegate layout_view::item::default_get_elem_state() // state handler //------------------------------------------------- -layout_view::item::state_delegate layout_view::item::default_get_anim_state() +layout_view_item::state_delegate layout_view_item::default_get_anim_state() { if (m_have_animoutput) - return state_delegate(&item::get_anim_output, this); + return state_delegate(&layout_view_item::get_anim_output, this); else if (m_animinput_port) - return state_delegate(&item::get_anim_input, this); + return state_delegate(&layout_view_item::get_anim_input, this); else return default_get_elem_state(); } @@ -4727,11 +4940,11 @@ layout_view::item::state_delegate layout_view::item::default_get_anim_state() // default_get_bounds - get default bounds handler //------------------------------------------------- -layout_view::item::bounds_delegate layout_view::item::default_get_bounds() +layout_view_item::bounds_delegate layout_view_item::default_get_bounds() { return (m_bounds.size() == 1U) ? bounds_delegate(&emu::render::detail::bounds_step::get, &m_bounds.front()) - : bounds_delegate(&item::get_interpolated_bounds, this); + : bounds_delegate(&layout_view_item::get_interpolated_bounds, this); } @@ -4739,11 +4952,65 @@ layout_view::item::bounds_delegate layout_view::item::default_get_bounds() // default_get_color - get default color handler //------------------------------------------------- -layout_view::item::color_delegate layout_view::item::default_get_color() +layout_view_item::color_delegate layout_view_item::default_get_color() { return (m_color.size() == 1U) ? color_delegate(&emu::render::detail::color_step::get, &const_cast(m_color.front())) - : color_delegate(&item::get_interpolated_color, this); + : color_delegate(&layout_view_item::get_interpolated_color, this); +} + + +//------------------------------------------------- +// default_get_scroll_size_x - get default +// horizontal scroll window size handler +//------------------------------------------------- + +layout_view_item::scroll_size_delegate layout_view_item::default_get_scroll_size_x() +{ + return scroll_size_delegate(&layout_view_item::get_scrollsizex, this); +} + + +//------------------------------------------------- +// default_get_scroll_size_y - get default +// vertical scroll window size handler +//------------------------------------------------- + +layout_view_item::scroll_size_delegate layout_view_item::default_get_scroll_size_y() +{ + return scroll_size_delegate(&layout_view_item::get_scrollsizey, this); +} + + +//------------------------------------------------- +// default_get_scroll_pos_x - get default +// horizontal scroll position handler +//------------------------------------------------- + +layout_view_item::scroll_pos_delegate layout_view_item::default_get_scroll_pos_x() +{ + if (m_have_scrollxoutput) + return scroll_pos_delegate(m_scrollwrapx ? &layout_view_item::get_scrollx_output : &layout_view_item::get_scrollx_output, this); + else if (m_scrollxinput_port) + return scroll_pos_delegate(m_scrollwrapx ? &layout_view_item::get_scrollx_input : &layout_view_item::get_scrollx_input, this); + else + return scroll_pos_delegate(&layout_view_item::get_scrollposx, this); +} + + +//------------------------------------------------- +// default_get_scroll_pos_y - get default +// vertical scroll position handler +//------------------------------------------------- + +layout_view_item::scroll_pos_delegate layout_view_item::default_get_scroll_pos_y() +{ + if (m_have_scrollyoutput) + return scroll_pos_delegate(m_scrollwrapy ? &layout_view_item::get_scrolly_output : &layout_view_item::get_scrolly_output, this); + else if (m_scrollyinput_port) + return scroll_pos_delegate(m_scrollwrapy ? &layout_view_item::get_scrolly_input : &layout_view_item::get_scrolly_input, this); + else + return scroll_pos_delegate(&layout_view_item::get_scrollposy, this); } @@ -4751,7 +5018,7 @@ layout_view::item::color_delegate layout_view::item::default_get_color() // get_state - get state when no bindings //------------------------------------------------- -int layout_view::item::get_state() const +int layout_view_item::get_state() const { return m_elem_state; } @@ -4761,7 +5028,7 @@ int layout_view::item::get_state() const // get_output - get element state output //------------------------------------------------- -int layout_view::item::get_output() const +int layout_view_item::get_output() const { assert(m_have_output); return int(s32(m_output)); @@ -4772,7 +5039,7 @@ int layout_view::item::get_output() const // get_input_raw - get element state input //------------------------------------------------- -int layout_view::item::get_input_raw() const +int layout_view_item::get_input_raw() const { assert(m_input_port); return int(std::make_signed_t((m_input_port->read() & m_input_mask) >> m_input_shift)); @@ -4783,7 +5050,7 @@ int layout_view::item::get_input_raw() const // get_input_field_cached - element state //------------------------------------------------- -int layout_view::item::get_input_field_cached() const +int layout_view_item::get_input_field_cached() const { assert(m_input_port); assert(m_input_field); @@ -4795,7 +5062,7 @@ int layout_view::item::get_input_field_cached() const // get_input_field_conditional - element state //------------------------------------------------- -int layout_view::item::get_input_field_conditional() const +int layout_view_item::get_input_field_conditional() const { assert(m_input_port); assert(!m_input_field); @@ -4808,7 +5075,7 @@ int layout_view::item::get_input_field_conditional() const // get_anim_output - get animation output //------------------------------------------------- -int layout_view::item::get_anim_output() const +int layout_view_item::get_anim_output() const { assert(m_have_animoutput); return int(unsigned((u32(s32(m_animoutput) & m_animmask) >> m_animshift))); @@ -4819,18 +5086,121 @@ int layout_view::item::get_anim_output() const // get_anim_input - get animation input //------------------------------------------------- -int layout_view::item::get_anim_input() const +int layout_view_item::get_anim_input() const { assert(m_animinput_port); return int(std::make_signed_t((m_animinput_port->read() & m_animmask) >> m_animshift)); } +//------------------------------------------------- +// get_scrollsizex - get horizontal scroll window +// size +//------------------------------------------------- + +float layout_view_item::get_scrollsizex() const +{ + return m_scrollsizex; +} + + +//------------------------------------------------- +// get_scrollsizey - get vertical scroll window +// size +//------------------------------------------------- + +float layout_view_item::get_scrollsizey() const +{ + return m_scrollsizey; +} + + +//------------------------------------------------- +// get_scrollposx - get horizontal scroll +// position +//------------------------------------------------- + +float layout_view_item::get_scrollposx() const +{ + return m_scrollposx; +} + + +//------------------------------------------------- +// get_scrollposy - get vertical scroll position +//------------------------------------------------- + +float layout_view_item::get_scrollposy() const +{ + return m_scrollposy; +} + + +//------------------------------------------------- +// get_scrollx_output - get scaled horizontal +// scroll output +//------------------------------------------------- + +template +float layout_view_item::get_scrollx_output() const +{ + assert(m_have_scrollxoutput); + u32 const unscaled(((u32(s32(m_scrollxoutput)) & m_scrollxmask) >> m_scrollxshift) - m_scrollxmin); + float const range(std::make_signed_t(m_scrollxmax - m_scrollxmin) + (!Wrap ? 0 : (m_scrollxmin < m_scrollxmax) ? 1 : -1)); + return float(s32(unscaled)) / range; +} + + +//------------------------------------------------- +// get_scrollx_input - get scaled horizontal +// scroll input +//------------------------------------------------- + +template +float layout_view_item::get_scrollx_input() const +{ + assert(m_scrollxinput_port); + ioport_value const unscaled(((m_scrollxinput_port->read() & m_scrollxmask) >> m_scrollxshift) - m_scrollxmin); + float const range(std::make_signed_t(m_scrollxmax - m_scrollxmin) + (!Wrap ? 0 : (m_scrollxmin < m_scrollxmax) ? 1 : -1)); + return float(std::make_signed_t(unscaled)) / range; +} + + +//------------------------------------------------- +// get_scrolly_output - get scaled vertical +// scroll output +//------------------------------------------------- + +template +float layout_view_item::get_scrolly_output() const +{ + assert(m_have_scrollyoutput); + u32 const unscaled(((u32(s32(m_scrollyoutput)) & m_scrollymask) >> m_scrollyshift) - m_scrollymin); + float const range(std::make_signed_t(m_scrollymax - m_scrollymin) + (!Wrap ? 0 : (m_scrollymin < m_scrollymax) ? 1 : -1)); + return float(s32(unscaled)) / range; +} + + +//------------------------------------------------- +// get_scrolly_input - get scaled vertical scroll +// input +//------------------------------------------------- + +template +float layout_view_item::get_scrolly_input() const +{ + assert(m_scrollyinput_port); + ioport_value const unscaled(((m_scrollyinput_port->read() & m_scrollymask) >> m_scrollyshift) - m_scrollymin); + float const range(std::make_signed_t(m_scrollymax - m_scrollymin) + (!Wrap ? 0 : (m_scrollymin < m_scrollymax) ? 1 : -1)); + return float(std::make_signed_t(unscaled)) / range; +} + + //------------------------------------------------- // get_interpolated_bounds - animated bounds //------------------------------------------------- -render_bounds layout_view::item::get_interpolated_bounds() const +render_bounds layout_view_item::get_interpolated_bounds() const { assert(m_bounds.size() > 1U); return interpolate_bounds(m_bounds, m_get_anim_state()); @@ -4841,7 +5211,7 @@ render_bounds layout_view::item::get_interpolated_bounds() const // get_interpolated_color - animated color //------------------------------------------------- -render_color layout_view::item::get_interpolated_color() const +render_color layout_view_item::get_interpolated_color() const { assert(m_color.size() > 1U); return interpolate_color(m_color, m_get_anim_state()); @@ -4852,7 +5222,7 @@ render_color layout_view::item::get_interpolated_color() const // find_element - find element definition //------------------------------------------------- -layout_element *layout_view::item::find_element(view_environment &env, util::xml::data_node const &itemnode, element_map &elemmap) +layout_element *layout_view_item::find_element(view_environment &env, util::xml::data_node const &itemnode, element_map &elemmap) { std::string const name(env.get_attribute_string(itemnode, !strcmp(itemnode.get_name(), "element") ? "ref" : "element")); if (name.empty()) @@ -4871,7 +5241,7 @@ layout_element *layout_view::item::find_element(view_environment &env, util::xml // make_bounds - get transformed bounds //------------------------------------------------- -layout_view::item::bounds_vector layout_view::item::make_bounds( +layout_view_item::bounds_vector layout_view_item::make_bounds( view_environment &env, util::xml::data_node const &itemnode, layout_group::transform const &trans) @@ -4904,7 +5274,7 @@ layout_view::item::bounds_vector layout_view::item::make_bounds( // make_color - get color inflection points //------------------------------------------------- -layout_view::item::color_vector layout_view::item::make_color( +layout_view_item::color_vector layout_view_item::make_color( view_environment &env, util::xml::data_node const &itemnode, render_color const &mult) @@ -4934,101 +5304,6 @@ layout_view::item::color_vector layout_view::item::make_color( } -//------------------------------------------------- -// make_animoutput_tag - get animation output tag -//------------------------------------------------- - -std::string layout_view::item::make_animoutput_tag(view_environment &env, util::xml::data_node const &itemnode) -{ - util::xml::data_node const *const animate(itemnode.get_child("animate")); - if (animate) - return std::string(env.get_attribute_string(*animate, "name")); - else - return std::string(); -} - - -//------------------------------------------------- -// make_animmask - get animation state mask -//------------------------------------------------- - -ioport_value layout_view::item::make_animmask(view_environment &env, util::xml::data_node const &itemnode) -{ - util::xml::data_node const *const animate(itemnode.get_child("animate")); - return animate ? env.get_attribute_int(*animate, "mask", ~ioport_value(0)) : ~ioport_value(0); -} - - -//------------------------------------------------- -// make_animinput_tag - get absolute tag for -// animation input -//------------------------------------------------- - -std::string layout_view::item::make_animinput_tag(view_environment &env, util::xml::data_node const &itemnode) -{ - util::xml::data_node const *const animate(itemnode.get_child("animate")); - return animate ? env.get_attribute_subtag(*animate, "inputtag") : std::string(); -} - - -//------------------------------------------------- -// make_input_tag - get absolute input tag -//------------------------------------------------- - -std::string layout_view::item::make_input_tag(view_environment &env, util::xml::data_node const &itemnode) -{ - return env.get_attribute_subtag(itemnode, "inputtag"); -} - - -//------------------------------------------------- -// get_blend_mode - explicit or implicit blend -//------------------------------------------------- - -int layout_view::item::get_blend_mode(view_environment &env, util::xml::data_node const &itemnode) -{ - // see if there's a blend mode attribute - std::string const *const mode(itemnode.get_attribute_string_ptr("blend")); - if (mode) - { - if (*mode == "none") - return BLENDMODE_NONE; - else if (*mode == "alpha") - return BLENDMODE_ALPHA; - else if (*mode == "multiply") - return BLENDMODE_RGB_MULTIPLY; - else if (*mode == "add") - return BLENDMODE_ADD; - else - throw layout_syntax_error(util::string_format("unknown blend mode %s", *mode)); - } - - // fall back to implicit blend mode based on element type - if (!strcmp(itemnode.get_name(), "screen")) - return -1; // magic number recognised by render.cpp to allow per-element blend mode - else if (!strcmp(itemnode.get_name(), "overlay")) - return BLENDMODE_RGB_MULTIPLY; - else - return BLENDMODE_ALPHA; -} - - -//------------------------------------------------- -// get_state_shift - shift to right-align LSB -//------------------------------------------------- - -unsigned layout_view::item::get_state_shift(ioport_value mask) -{ - unsigned result(0U); - while (mask && !BIT(mask, 0)) - { - ++result; - mask >>= 1; - } - return result; -} - - //************************************************************************** // LAYOUT VIEW VISIBILITY TOGGLE diff --git a/src/emu/rendlay.h b/src/emu/rendlay.h index 5b121d18880..befae560bcd 100644 --- a/src/emu/rendlay.h +++ b/src/emu/rendlay.h @@ -233,6 +233,178 @@ private: }; +/// \brief A single item in a view +/// +/// Each view has a list of item structures describing the visual +/// elements to draw, where they are located, additional blending modes, +/// and bindings for inputs and outputs. +class layout_view_item +{ + friend class layout_view; + +public: + using view_environment = emu::render::detail::view_environment; + using element_map = std::unordered_map; + using state_delegate = delegate; + using bounds_delegate = delegate; + using color_delegate = delegate; + using scroll_size_delegate = delegate; + using scroll_pos_delegate = delegate; + + // construction/destruction + layout_view_item( + view_environment &env, + util::xml::data_node const &itemnode, + element_map &elemmap, + int orientation, + layout_group::transform const &trans, + render_color const &color); + ~layout_view_item(); + + // getters + std::string const &id() const { return m_id; } + layout_element *element() const { return m_element; } + screen_device *screen() const { return m_screen; } + bool bounds_animated() const { return m_bounds.size() > 1U; } + bool color_animated() const { return m_color.size() > 1U; } + render_bounds bounds() const { return m_get_bounds(); } + render_color color() const { return m_get_color(); } + bool scroll_wrap_x() const { return m_scrollwrapx; } + bool scroll_wrap_y() const { return m_scrollwrapy; } + float scroll_size_x() const { return m_get_scroll_size_x(); } + float scroll_size_y() const { return m_get_scroll_size_y(); } + float scroll_pos_x() const { return m_get_scroll_pos_x(); } + float scroll_pos_y() const { return m_get_scroll_pos_y(); } + int blend_mode() const { return m_blend_mode; } + u32 visibility_mask() const { return m_visibility_mask; } + int orientation() const { return m_orientation; } + render_container *screen_container() const { return m_screen ? &m_screen->container() : nullptr; } + + // interactivity + bool has_input() const { return bool(m_input_port); } + std::pair input_tag_and_mask() const { return std::make_pair(m_input_port, m_input_mask); }; + bool clickthrough() const { return m_clickthrough; } + + // fetch state based on configured source + int element_state() const { return m_get_elem_state(); } + int animation_state() const { return m_get_anim_state(); } + + // set state + void set_state(int state) { m_elem_state = state; } + void set_scroll_size_x(float size) { m_scrollsizex = std::clamp(size, 0.01f, 1.0f); } + void set_scroll_size_y(float size) { m_scrollsizey = std::clamp(size, 0.01f, 1.0f); } + void set_scroll_pos_x(float pos) { m_scrollposx = pos; } + void set_scroll_pos_y(float pos) { m_scrollposy = pos; } + + // set handlers + void set_element_state_callback(state_delegate &&handler); + void set_animation_state_callback(state_delegate &&handler); + void set_bounds_callback(bounds_delegate &&handler); + void set_color_callback(color_delegate &&handler); + void set_scroll_size_x_callback(scroll_size_delegate &&handler); + void set_scroll_size_y_callback(scroll_size_delegate &&handler); + void set_scroll_pos_x_callback(scroll_pos_delegate &&handler); + void set_scroll_pos_y_callback(scroll_pos_delegate &&handler); + + // resolve tags, if any + void resolve_tags(); + +private: + using bounds_vector = emu::render::detail::bounds_vector; + using color_vector = emu::render::detail::color_vector; + + state_delegate default_get_elem_state(); + state_delegate default_get_anim_state(); + bounds_delegate default_get_bounds(); + color_delegate default_get_color(); + scroll_size_delegate default_get_scroll_size_x(); + scroll_size_delegate default_get_scroll_size_y(); + scroll_pos_delegate default_get_scroll_pos_x(); + scroll_pos_delegate default_get_scroll_pos_y(); + int get_state() const; + int get_output() const; + int get_input_raw() const; + int get_input_field_cached() const; + int get_input_field_conditional() const; + int get_anim_output() const; + int get_anim_input() const; + float get_scrollsizex() const; + float get_scrollsizey() const; + float get_scrollposx() const; + float get_scrollposy() const; + template float get_scrollx_output() const; + template float get_scrolly_output() const; + template float get_scrollx_input() const; + template float get_scrolly_input() const; + render_bounds get_interpolated_bounds() const; + render_color get_interpolated_color() const; + + static layout_element *find_element(view_environment &env, util::xml::data_node const &itemnode, element_map &elemmap); + static bounds_vector make_bounds(view_environment &env, util::xml::data_node const &itemnode, layout_group::transform const &trans); + static color_vector make_color(view_environment &env, util::xml::data_node const &itemnode, render_color const &mult); + + // internal state + layout_element *const m_element; // pointer to the associated element (non-screens only) + state_delegate m_get_elem_state; // resolved element state function + state_delegate m_get_anim_state; // resolved animation state function + bounds_delegate m_get_bounds; // resolved bounds function + color_delegate m_get_color; // resolved color function + scroll_size_delegate m_get_scroll_size_x; // resolved horizontal scroll window size function + scroll_size_delegate m_get_scroll_size_y; // resolved vertical scroll window size function + scroll_pos_delegate m_get_scroll_pos_x; // resolved horizontal scroll position function + scroll_pos_delegate m_get_scroll_pos_y; // resolved vertical scroll position function + output_finder<> m_output; // associated output + output_finder<> m_animoutput; // associated output for animation if different + output_finder<> m_scrollxoutput; // associated output for horizontal scroll position + output_finder<> m_scrollyoutput; // associated output for vertical scroll position + ioport_port * m_animinput_port; // input port used for animation + ioport_port * m_scrollxinput_port; // input port used for horizontal scrolling + ioport_port * m_scrollyinput_port; // input port used for vertical scrolling + bool const m_scrollwrapx; // whether horizontal scrolling works like a loop + bool const m_scrollwrapy; // whether vertical scrolling works like a loop + int m_elem_state; // element state used in absence of bindings + float m_scrollsizex; // horizontal scroll window size used in absence of bindings + float m_scrollsizey; // vertical scroll window size used in absence of bindings + float m_scrollposx; // horizontal scroll position used in absence of bindings + float m_scrollposy; // vertical scroll position used in absence of bindings + ioport_value const m_animmask; // mask for animation state + ioport_value const m_scrollxmask; // mask for horizontal scroll position + ioport_value const m_scrollymask; // mask for vertical scroll position + ioport_value const m_scrollxmin; // minimum value for horizontal scroll position + ioport_value const m_scrollymin; // minimum value for vertical scroll position + ioport_value const m_scrollxmax; // maximum value for horizontal scroll position + ioport_value const m_scrollymax; // maximum value for vertical scroll position + u8 const m_animshift; // shift for animation state + u8 const m_scrollxshift; // shift for horizontal scroll position + u8 const m_scrollyshift; // shift for vertical scroll position + ioport_port * m_input_port; // input port of this item + ioport_field const * m_input_field; // input port field of this item + ioport_value const m_input_mask; // input mask of this item + u8 const m_input_shift; // input mask rightshift for raw (trailing 0s) + bool m_clickthrough; // should click pass through to lower elements + screen_device * m_screen; // pointer to screen + int const m_orientation; // orientation of this item + bounds_vector m_bounds; // bounds of the item + color_vector const m_color; // color of the item + int m_blend_mode; // blending mode to use when drawing + u32 m_visibility_mask; // combined mask of parent visibility groups + + // cold items + std::string const m_id; // optional unique item identifier + std::string const m_input_tag; // input tag of this item + std::string const m_animinput_tag; // tag of input port for animation state + std::string const m_scrollxinput_tag; // tag of input port for horizontal scroll position + std::string const m_scrollyinput_tag; // tag of input port for vertical scroll position + bounds_vector const m_rawbounds; // raw (original) bounds of the item + bool const m_have_output; // whether we actually have an output + bool const m_input_raw; // get raw data from input port + bool const m_have_animoutput; // whether we actually have an output for animation + bool const m_have_scrollxoutput; // whether we actually have an output for horizontal scroll + bool const m_have_scrollyoutput; // whether we actually have an output for vertical scroll + bool const m_has_clickthrough; // whether clickthrough was explicitly configured +}; + + /// \brief A single view within a #layout_file /// /// The view is described using arbitrary coordinates that are scaled to @@ -243,133 +415,14 @@ class layout_view public: using layout_environment = emu::render::detail::layout_environment; using view_environment = emu::render::detail::view_environment; - using element_map = std::unordered_map; + using element_map = layout_view_item::element_map; using group_map = std::unordered_map; using screen_ref_vector = std::vector>; using prepare_items_delegate = delegate; using preload_delegate = delegate; using recomputed_delegate = delegate; - /// \brief A single item in a view - /// - /// Each view has a list of item structures describing the visual - /// elements to draw, where they are located, additional blending - /// modes, and bindings for inputs and outputs. - class item - { - friend class layout_view; - - public: - using state_delegate = delegate; - using bounds_delegate = delegate; - using color_delegate = delegate; - - // construction/destruction - item( - view_environment &env, - util::xml::data_node const &itemnode, - element_map &elemmap, - int orientation, - layout_group::transform const &trans, - render_color const &color); - ~item(); - - // getters - std::string const &id() const { return m_id; } - layout_element *element() const { return m_element; } - screen_device *screen() const { return m_screen; } - bool bounds_animated() const { return m_bounds.size() > 1U; } - bool color_animated() const { return m_color.size() > 1U; } - render_bounds bounds() const { return m_get_bounds(); } - render_color color() const { return m_get_color(); } - int blend_mode() const { return m_blend_mode; } - u32 visibility_mask() const { return m_visibility_mask; } - int orientation() const { return m_orientation; } - render_container *screen_container() const { return m_screen ? &m_screen->container() : nullptr; } - - // interactivity - bool has_input() const { return bool(m_input_port); } - std::pair input_tag_and_mask() const { return std::make_pair(m_input_port, m_input_mask); }; - bool clickthrough() const { return m_clickthrough; } - - // fetch state based on configured source - int element_state() const { return m_get_elem_state(); } - int animation_state() const { return m_get_anim_state(); } - - // set state - void set_state(int state) { m_elem_state = state; } - - // set handlers - void set_element_state_callback(state_delegate &&handler); - void set_animation_state_callback(state_delegate &&handler); - void set_bounds_callback(bounds_delegate &&handler); - void set_color_callback(color_delegate &&handler); - - // resolve tags, if any - void resolve_tags(); - - private: - using bounds_vector = emu::render::detail::bounds_vector; - using color_vector = emu::render::detail::color_vector; - - state_delegate default_get_elem_state(); - state_delegate default_get_anim_state(); - bounds_delegate default_get_bounds(); - color_delegate default_get_color(); - int get_state() const; - int get_output() const; - int get_input_raw() const; - int get_input_field_cached() const; - int get_input_field_conditional() const; - int get_anim_output() const; - int get_anim_input() const; - render_bounds get_interpolated_bounds() const; - render_color get_interpolated_color() const; - - static layout_element *find_element(view_environment &env, util::xml::data_node const &itemnode, element_map &elemmap); - static bounds_vector make_bounds(view_environment &env, util::xml::data_node const &itemnode, layout_group::transform const &trans); - static color_vector make_color(view_environment &env, util::xml::data_node const &itemnode, render_color const &mult); - static std::string make_animoutput_tag(view_environment &env, util::xml::data_node const &itemnode); - static std::string make_animinput_tag(view_environment &env, util::xml::data_node const &itemnode); - static ioport_value make_animmask(view_environment &env, util::xml::data_node const &itemnode); - static std::string make_input_tag(view_environment &env, util::xml::data_node const &itemnode); - static int get_blend_mode(view_environment &env, util::xml::data_node const &itemnode); - static unsigned get_state_shift(ioport_value mask); - - // internal state - layout_element *const m_element; // pointer to the associated element (non-screens only) - state_delegate m_get_elem_state; // resolved element state function - state_delegate m_get_anim_state; // resolved animation state function - bounds_delegate m_get_bounds; // resolved bounds function - color_delegate m_get_color; // resolved color function - output_finder<> m_output; // associated output - output_finder<> m_animoutput; // associated output for animation if different - ioport_port * m_animinput_port; // input port used for animation - int m_elem_state; // element state used in absence of bindings - ioport_value const m_animmask; // mask for animation state - u8 const m_animshift; // shift for animation state - ioport_port * m_input_port; // input port of this item - ioport_field const * m_input_field; // input port field of this item - ioport_value const m_input_mask; // input mask of this item - u8 const m_input_shift; // input mask rightshift for raw (trailing 0s) - bool m_clickthrough; // should click pass through to lower elements - screen_device * m_screen; // pointer to screen - int const m_orientation; // orientation of this item - bounds_vector m_bounds; // bounds of the item - color_vector const m_color; // color of the item - int m_blend_mode; // blending mode to use when drawing - u32 m_visibility_mask; // combined mask of parent visibility groups - - // cold items - std::string const m_id; // optional unique item identifier - std::string const m_input_tag; // input tag of this item - std::string const m_animinput_tag; // tag of input port for animation state - bounds_vector const m_rawbounds; // raw (original) bounds of the item - bool const m_have_output; // whether we actually have an output - bool const m_input_raw; // get raw data from input port - bool const m_have_animoutput; // whether we actually have an output for animation - bool const m_has_clickthrough; // whether clickthrough was explicitly configured - }; + using item = layout_view_item; using item_list = std::list; using item_ref_vector = std::vector >; diff --git a/src/frontend/mame/luaengine_render.cpp b/src/frontend/mame/luaengine_render.cpp index ea40a34c02a..21a68dc65cb 100644 --- a/src/frontend/mame/luaengine_render.cpp +++ b/src/frontend/mame/luaengine_render.cpp @@ -39,7 +39,7 @@ struct layout_view_items layout_view_items(layout_view &v) : view(v) { } layout_view::item_list &items() { return view.items(); } - static layout_view::item &unwrap(layout_view::item_list::iterator const &it) { return *it; } + static layout_view_item &unwrap(layout_view::item_list::iterator const &it) { return *it; } static int push_key(lua_State *L, layout_view::item_list::iterator const &it, std::size_t ix) { return sol::stack::push(L, ix + 1); } layout_view &view; @@ -97,7 +97,7 @@ public: { layout_view_items &self(get_self(L)); char const *const id(stack::unqualified_get(L)); - layout_view::item *const item(self.view.get_item(id)); + layout_view_item *const item(self.view.get_item(id)); if (item) return stack::push_reference(L, *item); else @@ -279,48 +279,86 @@ void lua_engine::initialize_render(sol::table &emu) layout_view_type["has_art"] = sol::property(&layout_view::has_art); - auto layout_view_item_type = sol().registry().new_usertype("layout_item", sol::no_constructor); - layout_view_item_type["set_state"] = &layout_view::item::set_state; + auto layout_view_item_type = sol().registry().new_usertype("layout_item", sol::no_constructor); + layout_view_item_type["set_state"] = &layout_view_item::set_state; layout_view_item_type["set_element_state_callback"] = make_simple_callback_setter( - &layout_view::item::set_element_state_callback, + &layout_view_item::set_element_state_callback, [] () { return 0; }, "set_element_state_callback", "element state"); layout_view_item_type["set_animation_state_callback"] = make_simple_callback_setter( - &layout_view::item::set_animation_state_callback, + &layout_view_item::set_animation_state_callback, [] () { return 0; }, "set_animation_state_callback", "animation state"); layout_view_item_type["set_bounds_callback"] = make_simple_callback_setter( - &layout_view::item::set_bounds_callback, + &layout_view_item::set_bounds_callback, [] () { return render_bounds{ 0.0f, 0.0f, 1.0f, 1.0f }; }, "set_bounds_callback", "bounds"); layout_view_item_type["set_color_callback"] = make_simple_callback_setter( - &layout_view::item::set_color_callback, + &layout_view_item::set_color_callback, [] () { return render_color{ 1.0f, 1.0f, 1.0f, 1.0f }; }, "set_color_callback", "color"); + layout_view_item_type["set_scroll_size_x_callback"] = + make_simple_callback_setter( + &layout_view_item::set_scroll_size_x_callback, + [] () { return 1.0f; }, + "set_scroll_size_x_callback", + "horizontal scroll window size"); + layout_view_item_type["set_scroll_size_y_callback"] = + make_simple_callback_setter( + &layout_view_item::set_scroll_size_y_callback, + [] () { return 1.0f; }, + "set_scroll_size_y_callback", + "vertical scroll window size"); + layout_view_item_type["set_scroll_pos_x_callback"] = + make_simple_callback_setter( + &layout_view_item::set_scroll_pos_x_callback, + [] () { return 1.0f; }, + "set_scroll_pos_x_callback", + "horizontal scroll position"); + layout_view_item_type["set_scroll_pos_y_callback"] = + make_simple_callback_setter( + &layout_view_item::set_scroll_pos_y_callback, + [] () { return 1.0f; }, + "set_scroll_pos_y_callback", + "vertical scroll position"); layout_view_item_type["id"] = sol::property( - [] (layout_view::item &i, sol::this_state s) -> sol::object + [] (layout_view_item &i, sol::this_state s) -> sol::object { if (i.id().empty()) return sol::lua_nil; else return sol::make_object(s, i.id()); }); - layout_view_item_type["bounds_animated"] = sol::property(&layout_view::item::bounds_animated); - layout_view_item_type["color_animated"] = sol::property(&layout_view::item::color_animated); - layout_view_item_type["bounds"] = sol::property(&layout_view::item::bounds); - layout_view_item_type["color"] = sol::property(&layout_view::item::color); - layout_view_item_type["blend_mode"] = sol::property(&layout_view::item::blend_mode); - layout_view_item_type["orientation"] = sol::property(&layout_view::item::orientation); - layout_view_item_type["element_state"] = sol::property(&layout_view::item::element_state); - layout_view_item_type["animation_state"] = sol::property(&layout_view::item::animation_state); + layout_view_item_type["bounds_animated"] = sol::property(&layout_view_item::bounds_animated); + layout_view_item_type["color_animated"] = sol::property(&layout_view_item::color_animated); + layout_view_item_type["bounds"] = sol::property(&layout_view_item::bounds); + layout_view_item_type["color"] = sol::property(&layout_view_item::color); + layout_view_item_type["scroll_wrap_x"] = sol::property(&layout_view_item::scroll_wrap_x); + layout_view_item_type["scroll_wrap_y"] = sol::property(&layout_view_item::scroll_wrap_y); + layout_view_item_type["scroll_size_x"] = sol::property( + &layout_view_item::scroll_size_x, + &layout_view_item::set_scroll_size_x); + layout_view_item_type["scroll_size_y"] = sol::property( + &layout_view_item::scroll_size_y, + &layout_view_item::set_scroll_size_y); + layout_view_item_type["scroll_pos_x"] = sol::property( + &layout_view_item::scroll_pos_x, + &layout_view_item::set_scroll_pos_y); + layout_view_item_type["scroll_pos_y"] = sol::property( + &layout_view_item::scroll_pos_y, + &layout_view_item::set_scroll_pos_y); + layout_view_item_type["blend_mode"] = sol::property(&layout_view_item::blend_mode); + layout_view_item_type["orientation"] = sol::property(&layout_view_item::orientation); + layout_view_item_type["element_state"] = sol::property(&layout_view_item::element_state); + layout_view_item_type["animation_state"] = sol::property(&layout_view_item::animation_state); auto layout_file_type = sol().registry().new_usertype("layout_file", sol::no_constructor); -- cgit v1.2.3