From 6ea9ff90423416d630472298a14afc5526104e75 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Thu, 2 Aug 2018 23:11:17 +1000 Subject: (nw) Further layout work: * Allow and to work on group references * Fix some corner cases where group bounds could be miscalculated * Fix a corner case where MAME could incorrectly refuse to instantiate groups * Add more checks to complay.py * Document more of the layout format --- docs/source/techspecs/layout_files.rst | 191 +++++++++++++++++++++++ scripts/build/complay.py | 31 +++- src/emu/render.h | 15 +- src/emu/rendlay.cpp | 268 +++++++++++++++++++++------------ 4 files changed, 404 insertions(+), 101 deletions(-) diff --git a/docs/source/techspecs/layout_files.rst b/docs/source/techspecs/layout_files.rst index 89ce3e54d58..31098111b30 100644 --- a/docs/source/techspecs/layout_files.rst +++ b/docs/source/techspecs/layout_files.rst @@ -601,6 +601,181 @@ An example element for a button that gives visual feedback when clicked:: +.. _layout-parts-views: + +Views +~~~~~ + +A view defines an arrangement of elements and/or emulated screen images that can +be displayed in a window or on a screen. Views also connect elements to +emulated I/O ports and/or outputs. A layout file may contain multiple views. +If a view references a non-existent screen, it will be considered *unviable*. +MAME will print a warning message, skip over the unviable view, and continue to +load views from the layout file. This is particularly useful for systems where +a screen is optional, for example computer systems with front panel controls and +an optional serial terminal. + +Views are identified by name in MAME's user interface and in command-line +options. For layouts files associated with devices other than the root driver +device, view names are prefixed with the device's tag (with the initial colon +omitted) -- for example a view called "Keyboard LEDs" loaded for the device +``:tty:ie15`` will be called "tty:ie15 Keyboard LEDs" in MAME's user interface. +Views are listed in the order they are loaded. Within a layout file, views are +loaded in the order they appear, from top to bottom. + +Views are created with ``view`` elements inside the top-level ``mamelayout`` +element. Each ``view`` element must have a ``name`` attribute, supplying its +human-readable name for use in the user interface and command-line options. +This is an example of a valid opening tag for a ``view`` element:: + + + +A view creates a nested parameter scope inside the parameter scope of the +top-level ``mamelayout`` element. For historical reasons, ``view`` elements are +processed *after* all other child elements of the top-level ``mamelayout`` +element. This means a view can reference elements and groups that appear after +it in the file, and parameters from the enclosing scope will have their final +values from the end of the ``mamelayout`` element. + +The following child elements are allowed inside a ``view`` element: + +bounds + Sets the origin and size of the view if present. If absent, the bounds of + the view are computed as the union of the bounds of all screens and elements + within the view. See :ref:`layout-concepts-coordinates` for details. It + only makes sense to have one ``bounds`` as a direct child of a view element. +param + Defines or reassigns a value parameter in the view's scope. See + :ref:`layout-concepts-params` for details. +backdrop overlay bezel cpanel marquee + Adds an element to the relevant layer (see :ref:`layout-parts-elements` and + :ref:`layout-concepts-layers`). The name of the element to add is specified + using the required ``element`` attribute. It is an error if no element with + this name is defined in the layout file. May optionally be connected to an + emulated I/O port using ``inputtag`` and ``inputmask`` attributes, and/or an + emulated output using a ``name`` attribute. Within a layer, elements are + drawn in the order they appear in the layout file, from front to back. See + below for more details. +screen + Adds an emulated screen image to the view. The screen must be identified + using either an ``index`` attribute or a ``tag`` attribute (it is an error + for a ``screen`` element to have both ``index`` and ``tag`` attributes). + If present, the ``index`` attribute must be a non-negative integer. Screens + are numbered by the order they appear in machine configuration, starting at + zero (0). If present, the ``tag`` attribute must be the tag path to the + screen relative to the device that causes the layout to be loaded. Screens + are drawn in the order they appear in the layout file, from front to back. +group + Adds the content of the group to the view (see :ref:`layout-parts-groups`). + The name of the group to add is specified using the required ``ref`` + attribute. It is an error if no group with this name is defined in the + layout file. See below for more details on positioning. +repeat + Repeats its contents the number of times specified by the required ``count`` + attribute. The ``count`` attribute must be a positive integer. A + ``repeat`` element in a view may contain ``backdrop``, ``screen``, + ``overlay``, ``bezel``, ``cpanel``, ``marquee``, ``group``, and further + ``repeat`` elements, which function the same way they do when placed in a + view directly. See :ref:`layout-parts-repeats` for discussion on using + ``repeat`` elements. + +Screens (``screen`` elements), layout elements (``backdrop``, ``overlay``, +``bezel``, ``cpanel`` or ``marquee`` elements) and groups (``group`` elements) +may be have their orientation altered using an ``orientation`` child element. +For screens, the orientation modifiers are applied in addition to the +orientation modifiers specified on the screen device and on the machine. The +``orientation`` element supports the following attributes, all of which are +optional: + +rotate + If present, applies clockwise rotation in ninety degree implements. Must be + an integer equal to 0, 90, 180 or 270. +swapxy + Allows the screen, element or group to be mirrored along a line at + forty-five degrees to vertical from upper left to lower right. Must be + either ``yes`` or ``no`` if present. Mirroring applies logically after + rotation. +flipx + Allows the screen, element or group to be mirrored around its vertical axis, + from left to right. Must be either ``yes`` or ``no`` if present. Mirroring + applies logically after rotation. +flipy + Allows the screen, element or group to be mirrored around its horizontal + axis, from top to bottom. Must be either ``yes`` or ``no`` if present. + Mirroring applies logically after rotation. + +Screens (``screen`` elements), layout elements (``backdrop``, ``overlay``, +``bezel``, ``cpanel`` or ``marquee`` elements) and groups (``group`` elements) +may be positioned and sized using a ``bounds`` child element (see +:ref:`layout-concepts-coordinates` for details). In the absence of a ``bounds`` +child element, screens' and layout elements' bounds default to a unit square +(origin at 0,0 and height and width both equal to 1). In the absence of a +``bounds`` child element, groups are expanded with no translation/scaling (note +that groups may position screens/elements outside their bounds). This example +shows a view instantiating and positioning a screen, an individual layout +element, and two element groups:: + + + + + + + + +Screens (``screen`` elements), layout elements (``backdrop``, ``overlay``, +``bezel``, ``cpanel`` or ``marquee`` elements) and groups (``group`` elements) +may have a ``color`` child element (see :ref:`layout-concepts-colours`) +specifying a modifier colour. The components colours of the screen or layout +element(s) are multiplied by this colour. + +If an element instantiating a layout element (``backdrop``, ``overlay``, +``bezel``, ``cpanel`` or ``marquee``) has ``inputtag`` and ``inputmask`` +attributes, clicking it is equivalent to pressing a key/button mapped to the +corresponding input(s). The ``inputtag`` specifies the tag path of an I/O port +relative to the device that caused the layout file to be loaded. The +``inputmask`` attribute must be an integer specifying the bits of the I/O port +that the element should activate. This sample is shows instantiation of +clickable buttons:: + + + + + + + + + + + +If an element instantiating a layout element (``backdrop``, ``overlay``, +``bezel``, ``cpanel`` or ``marquee``) has a ``name`` attribute, it will take its +state from the value of the correspondingly named emulated output. Note that +output names are global, which can become an issue when a machine uses multiple +instances of the same type of device. See :ref:`layout-parts-elements` for +details on how an element's state affects its appearance. This example shows +how digital displays may be connected to emulated outputs:: + + + + + + + + +If an element instantiating a layout element has ``inputtag`` and ``inputmask`` +attributes but lacks a ``name`` attribute, it will take its state from the value +of the corresponding I/O port, masked with the ``inputmask`` value, and shifted +to the right so that the least significant one bit of the mask aligns with the +least significant bit of the value (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). This is often used to allow clickable buttons and toggle switches +to provide visible feedback. + +When handling mouse input, MAME treats all layout elements as being rectangular, +and only activates the frontmost element whose area includes the location of the +mouse pointer. + + .. _layout-parts-repeats: Repeating blocks @@ -736,6 +911,22 @@ tiles on each iteration. Rows are connected to I/O ports ``board:IN.7`` at the top to ``board.IN.0`` at the bottom. +.. _layout-errors: + +Error handling +-------------- + +* For internal (developer-supplied) layout files, errors detected by the + ``complay.py`` script result in a build failure. +* MAME will stop loading a layout file if a syntax error is encountered. No + views from the layout will be available. Examples of syntax errors include + undefined element or group references, invalid bounds, invalid colours, + recursively nested groups, and redefined generator parameters. +* When loading a layout file, if a view references a non-existent screen, MAME + will print a warning message and continue. Views referencing non-existent + screens are considered unviable and not available to the user. + + .. _layout-autogen: Automatically-generated views diff --git a/scripts/build/complay.py b/scripts/build/complay.py index c6cea3be251..dff981e65c9 100644 --- a/scripts/build/complay.py +++ b/scripts/build/complay.py @@ -98,6 +98,8 @@ class LayoutChecker(Minifyer): FLOATCHARS = re.compile('^.*[.eE].*$') SHAPES = frozenset(('disk', 'dotmatrix', 'dotmatrix5dot', 'dotmatrixdot', 'led14seg', 'led14segsc', 'led16seg', 'led16segsc', 'led7seg', 'led8seg_gts1', 'rect')) OBJECTS = frozenset(('backdrop', 'bezel', 'cpanel', 'marquee', 'overlay')) + ORIENTATIONS = frozenset((0, 90, 180, 270)) + YESNO = frozenset(("yes", "no")) def __init__(self, output, **kwargs): super(LayoutChecker, self).__init__(output=output, **kwargs) @@ -246,6 +248,17 @@ class LayoutChecker(Minifyer): if has_ltrb and has_origin_size: self.handleError('Element bounds has both left/top/right/bottom and origin/size attributes') + def checkOrientation(self, attrs): + if self.have_orientation[-1]: + self.handleError('Duplicate element orientation') + else: + self.have_orientation[-1] = True + 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: + self.handleError('Element orientation attribute %s "%s" is not "yes" or "no"' % (name, attrs[name])) + def checkColorChannel(self, attrs, name): channel = self.checkFloatAttribute('color', attrs, name, None) if (channel is not None) and ((0.0 > channel) or (1.0 < channel)): @@ -363,6 +376,8 @@ class LayoutChecker(Minifyer): for group in self.referenced_groups: if (group not in self.groups) and (not self.VARPATTERN.match(group)): self.handleError('Group "%s" not found (first referenced at %s)' % (group, self.referenced_groups[group])) + if not self.views: + self.handleError('No view elements found') self.handlers.pop() def elementStartHandler(self, name, attrs): @@ -437,11 +452,16 @@ class LayoutChecker(Minifyer): self.referenced_elements[attrs['element']] = self.formatLocation() if 'inputtag' in attrs: if 'inputmask' not in attrs: - self.handleError('Element %s has inputtag without inputmask attribute' % (name, )) + self.handleError('Element %s has inputtag attribute without inputmask attribute' % (name, )) self.checkTag(attrs['inputtag'], name, 'inputtag') - self.checkIntAttribute(name, attrs, 'inputmask', None) + elif 'inputmask' in attrs: + self.handleError('Element %s has inputmask attribute without inputtag attirbute' % (name, )) + inputmask = self.checkIntAttribute(name, attrs, 'inputmask', None) + if (inputmask is not None) and (0 == inputmask): + self.handleError('Element %s has attribute inputmask "%s" is zero' % (name, attrs['inputmask'])) self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.have_bounds.append(False) + self.have_orientation.append(False) elif 'screen' == name: if 'index' in attrs: index = self.checkIntAttribute(name, attrs, 'index', None) @@ -456,6 +476,7 @@ class LayoutChecker(Minifyer): self.handleError('Element screen attribute tag "%s" contains invalid characters' % (tag, )) self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.have_bounds.append(False) + self.have_orientation.append(False) elif 'group' == name: if 'ref' not in attrs: self.handleError('Element group missing attribute ref') @@ -463,6 +484,7 @@ class LayoutChecker(Minifyer): self.referenced_groups[attrs['ref']] = self.formatLocation() self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.have_bounds.append(False) + self.have_orientation.append(False) elif 'repeat' == name: if 'count' not in attrs: self.handleError('Element repeat missing attribute count') @@ -496,10 +518,13 @@ class LayoutChecker(Minifyer): def objectStartHandler(self, name, attrs): if 'bounds' == name: self.checkBounds(attrs) + elif 'orientation' == name: + self.checkOrientation(attrs) self.ignored_depth = 1 def objectEndHandler(self, name): self.have_bounds.pop() + self.have_orientation.pop() self.handlers.pop() def setDocumentLocator(self, locator): @@ -512,6 +537,7 @@ class LayoutChecker(Minifyer): self.variable_scopes = [ ] self.repeat_depth = [ ] self.have_bounds = [ ] + self.have_orientation = [ ] self.have_color = [ ] self.generated_element_names = False self.generated_group_names = False @@ -529,6 +555,7 @@ class LayoutChecker(Minifyer): del self.variable_scopes del self.repeat_depth del self.have_bounds + del self.have_orientation del self.have_color del self.generated_element_names del self.generated_group_names diff --git a/src/emu/render.h b/src/emu/render.h index 387b4fc141d..d65a1111851 100644 --- a/src/emu/render.h +++ b/src/emu/render.h @@ -49,6 +49,7 @@ #include "screen.h" #include +#include #include #include #include @@ -759,14 +760,16 @@ class layout_group public: using environment = emu::render::detail::layout_environment; using group_map = std::unordered_map; + using transform = std::array, 3>; layout_group(util::xml::data_node const &groupnode); ~layout_group(); util::xml::data_node const &get_groupnode() const { return m_groupnode; } - render_bounds make_transform(render_bounds const &dest) const; - render_bounds make_transform(render_bounds const &dest, render_bounds const &transform) const; + transform make_transform(int orientation, render_bounds const &dest) const; + transform make_transform(int orientation, transform const &trans) const; + transform make_transform(int orientation, render_bounds const &dest, transform const &trans) const; void set_bounds_unresolved(); void resolve_bounds(environment &env, group_map &groupmap); @@ -818,7 +821,9 @@ public: environment &env, util::xml::data_node const &itemnode, element_map &elemmap, - render_bounds const &transform); + int orientation, + layout_group::transform const &trans, + render_color const &color); ~item(); // getters @@ -886,7 +891,9 @@ private: util::xml::data_node const &parentnode, element_map &elemmap, group_map &groupmap, - render_bounds const &transform, + int orientation, + layout_group::transform const &trans, + render_color const &color, bool root, bool repeat, bool init); diff --git a/src/emu/rendlay.cpp b/src/emu/rendlay.cpp index 7a926ba8963..bc9e708bb5b 100644 --- a/src/emu/rendlay.cpp +++ b/src/emu/rendlay.cpp @@ -69,23 +69,26 @@ enum std::locale const f_portable_locale("C"); +constexpr layout_group::transform identity_transform{{ {{ 1.0F, 0.0F, 0.0F }}, {{ 0.0F, 1.0F, 0.0F }}, {{ 0.0F, 0.0F, 1.0F }} }}; + //************************************************************************** // INLINE HELPERS //************************************************************************** -//------------------------------------------------- -// render_bounds_transform - apply translation/ -// scaling -//------------------------------------------------- +inline void render_bounds_transform(render_bounds &bounds, layout_group::transform const &trans) +{ + bounds = render_bounds{ + (bounds.x0 * trans[0][0]) + (bounds.y0 * trans[0][1]) + trans[0][2], + (bounds.x0 * trans[1][0]) + (bounds.y0 * trans[1][1]) + trans[1][2], + (bounds.x1 * trans[0][0]) + (bounds.y1 * trans[0][1]) + trans[0][2], + (bounds.x1 * trans[1][0]) + (bounds.y1 * trans[1][1]) + trans[1][2] }; +} -inline void render_bounds_transform(render_bounds &bounds, render_bounds const &transform) +constexpr render_color render_color_multiply(render_color const &x, render_color const &y) { - bounds.x0 = (bounds.x0 * transform.x1) + transform.x0; - bounds.y0 = (bounds.y0 * transform.y1) + transform.y0; - bounds.x1 = (bounds.x1 * transform.x1) + transform.x0; - bounds.y1 = (bounds.y1 * transform.y1) + transform.y0; + return render_color{ x.a * y.a, x.r * y.r, x.g * y.g, x.b * y.b }; } @@ -788,53 +791,50 @@ public: } } - void parse_color(util::xml::data_node const *node, render_color &result) + render_color parse_color(util::xml::data_node const *node) { - // default to white + // default to opaque white if (!node) - { - result.r = result.g = result.b = result.a = 1.0F; - } - else - { - // parse attributes - result.r = get_attribute_float(*node, "red", 1.0F); - result.g = get_attribute_float(*node, "green", 1.0F); - result.b = get_attribute_float(*node, "blue", 1.0F); - result.a = get_attribute_float(*node, "alpha", 1.0F); + return render_color{ 1.0F, 1.0F, 1.0F, 1.0F }; - // check for errors - if ((0.0F > (std::min)({ result.r, result.g, result.b, result.a })) || (1.0F < (std::max)({ result.r, result.g, result.b, result.a }))) - throw layout_syntax_error(util::string_format("illegal RGBA color %f,%f,%f,%f", result.r, result.g, result.b, result.a)); - } + // parse attributes + render_color const result{ + get_attribute_float(*node, "alpha", 1.0F), + get_attribute_float(*node, "red", 1.0F), + get_attribute_float(*node, "green", 1.0F), + get_attribute_float(*node, "blue", 1.0F) }; + + // check for errors + if ((0.0F > (std::min)({ result.r, result.g, result.b, result.a })) || (1.0F < (std::max)({ result.r, result.g, result.b, result.a }))) + throw layout_syntax_error(util::string_format("illegal RGBA color %f,%f,%f,%f", result.r, result.g, result.b, result.a)); + + return result; } - void parse_orientation(util::xml::data_node const *node, int &result) + int parse_orientation(util::xml::data_node const *node) { // default to no transform if (!node) - { - result = ROT0; - } - else - { - // parse attributes - int const rotate(get_attribute_int(*node, "rotate", 0)); - switch (rotate) - { - case 0: result = ROT0; break; - case 90: result = ROT90; break; - case 180: result = ROT180; break; - case 270: result = ROT270; break; - default: throw layout_syntax_error(util::string_format("invalid rotate attribute %d", rotate)); - } - if (!std::strcmp("yes", get_attribute_string(*node, "swapxy", "no"))) - result ^= ORIENTATION_SWAP_XY; - if (!std::strcmp("yes", get_attribute_string(*node, "flipx", "no"))) - result ^= ORIENTATION_FLIP_X; - if (!std::strcmp("yes", get_attribute_string(*node, "flipy", "no"))) - result ^= ORIENTATION_FLIP_Y; - } + return ROT0; + + // parse attributes + int result; + int const rotate(get_attribute_int(*node, "rotate", 0)); + switch (rotate) + { + case 0: result = ROT0; break; + case 90: result = ROT90; break; + case 180: result = ROT180; break; + case 270: result = ROT270; break; + default: throw layout_syntax_error(util::string_format("invalid rotate attribute %d", rotate)); + } + if (!std::strcmp("yes", get_attribute_string(*node, "swapxy", "no"))) + result ^= ORIENTATION_SWAP_XY; + if (!std::strcmp("yes", get_attribute_string(*node, "flipx", "no"))) + result ^= ORIENTATION_FLIP_X; + if (!std::strcmp("yes", get_attribute_string(*node, "flipy", "no"))) + result ^= ORIENTATION_FLIP_Y; + return result; } }; @@ -965,25 +965,65 @@ layout_group::~layout_group() // matrix for given destination bounds //------------------------------------------------- -render_bounds layout_group::make_transform(render_bounds const &dest) const +layout_group::transform layout_group::make_transform(int orientation, render_bounds const &dest) const { assert(m_bounds_resolved); - return render_bounds{ - dest.x0 - (m_bounds.x0 * (dest.x1 - dest.x0) / (m_bounds.x1 - m_bounds.x0)), - dest.y0 - (m_bounds.y0 * (dest.y1 - dest.y0) / (m_bounds.y1 - m_bounds.y0)), - (dest.x1 - dest.x0) / (m_bounds.x1 - m_bounds.x0), - (dest.y1 - dest.y0) / (m_bounds.y1 - m_bounds.y0) }; + // make orientation matrix + transform result{{ {{ 1.0F, 0.0F, 0.0F }}, {{ 0.0F, 1.0F, 0.0F }}, {{ 0.0F, 0.0F, 1.0F }} }}; + if (orientation & ORIENTATION_SWAP_XY) + { + std::swap(result[0][0], result[0][1]); + std::swap(result[1][0], result[1][1]); + } + if (orientation & ORIENTATION_FLIP_X) + { + result[0][0] = -result[0][0]; + result[0][1] = -result[0][1]; + } + if (orientation & ORIENTATION_FLIP_Y) + { + result[1][0] = -result[1][0]; + result[1][1] = -result[1][1]; + } + + // apply to bounds and force into destination rectangle + render_bounds bounds(m_bounds); + render_bounds_transform(bounds, result); + result[0][0] *= (dest.x1 - dest.x0) / std::abs(bounds.x1 - bounds.x0); + result[0][1] *= (dest.x1 - dest.x0) / std::abs(bounds.x1 - bounds.x0); + result[0][2] = dest.x0 - ((std::min)(bounds.x0, bounds.x1) * (dest.x1 - dest.x0) / std::abs(bounds.x1 - bounds.x0)); + result[1][0] *= (dest.y1 - dest.y0) / std::abs(bounds.y1 - bounds.y0); + result[1][1] *= (dest.y1 - dest.y0) / std::abs(bounds.y1 - bounds.y0); + result[1][2] = dest.y0 - ((std::min)(bounds.y0, bounds.y1) * (dest.y1 - dest.y0) / std::abs(bounds.y1 - bounds.y0)); + return result; } -render_bounds layout_group::make_transform(render_bounds const &dest, render_bounds const &transform) const +layout_group::transform layout_group::make_transform(int orientation, transform const &trans) const { - render_bounds const next(make_transform(dest)); - return render_bounds{ - (transform.x0 * next.x1) + next.x0, - (transform.y0 * next.y1) + next.y0, - transform.x1 * next.x1, - transform.y1 * next.y1 }; + assert(m_bounds_resolved); + + render_bounds const dest{ + m_bounds.x0, + m_bounds.y0, + (orientation & ORIENTATION_SWAP_XY) ? (m_bounds.x0 + m_bounds.y1 - m_bounds.y0) : m_bounds.x1, + (orientation & ORIENTATION_SWAP_XY) ? (m_bounds.y0 + m_bounds.x1 - m_bounds.x0) : m_bounds.y1 }; + return make_transform(orientation, dest, trans); +} + +layout_group::transform layout_group::make_transform(int orientation, render_bounds const &dest, transform const &trans) const +{ + transform const next(make_transform(orientation, dest)); + transform result{{ {{ 0.0F, 0.0F, 0.0F }}, {{ 0.0F, 0.0F, 0.0F }}, {{ 0.0F, 0.0F, 0.0F }} }}; + for (unsigned y = 0; 3U > y; ++y) + { + for (unsigned x = 0; 3U > x; ++x) + { + for (unsigned i = 0; 3U > i; ++i) + result[y][x] += trans[y][i] * next[i][x]; + } + } + return result; } @@ -1013,8 +1053,8 @@ void layout_group::resolve_bounds(environment &env, group_map &groupmap, std::ve // a wild loop appears! std::ostringstream path; for (layout_group const *const group : seen) - path << ' ' << group->m_groupnode.get_name(); - path << ' ' << m_groupnode.get_name(); + path << ' ' << group->m_groupnode.get_attribute_string("name", nullptr); + path << ' ' << m_groupnode.get_attribute_string("name", nullptr); throw layout_syntax_error(util::string_format("recursively nested groups %s", path.str())); } @@ -1024,6 +1064,7 @@ void layout_group::resolve_bounds(environment &env, group_map &groupmap, std::ve environment local(env); resolve_bounds(local, m_groupnode, groupmap, seen, false, true); } + seen.pop_back(); } void layout_group::resolve_bounds( @@ -1071,17 +1112,33 @@ void layout_group::resolve_bounds( } else if (!strcmp(itemnode->get_name(), "group")) { - char const *ref(env.get_attribute_string(*itemnode, "ref", nullptr)); - if (!ref) - throw layout_syntax_error("nested group must have ref attribute"); - - group_map::iterator const found(groupmap.find(ref)); - if (groupmap.end() == found) - throw layout_syntax_error(util::string_format("unable to find group %s", ref)); - - environment local(env); - found->second.resolve_bounds(local, groupmap, seen); - union_render_bounds(m_bounds, found->second.m_bounds); + util::xml::data_node const *const itemboundsnode(itemnode->get_child("bounds")); + if (itemboundsnode) + { + render_bounds itembounds; + env.parse_bounds(itemboundsnode, itembounds); + union_render_bounds(m_bounds, itembounds); + } + else + { + char const *ref(env.get_attribute_string(*itemnode, "ref", nullptr)); + if (!ref) + throw layout_syntax_error("nested group must have ref attribute"); + + group_map::iterator const found(groupmap.find(ref)); + if (groupmap.end() == found) + throw layout_syntax_error(util::string_format("unable to find group %s", ref)); + + int const orientation(env.parse_orientation(itemnode->get_child("orientation"))); + environment local(env); + found->second.resolve_bounds(local, groupmap, seen); + render_bounds const itembounds{ + found->second.m_bounds.x0, + found->second.m_bounds.y0, + (orientation & ORIENTATION_SWAP_XY) ? (found->second.m_bounds.x0 + found->second.m_bounds.y1 - found->second.m_bounds.y0) : found->second.m_bounds.x1, + (orientation & ORIENTATION_SWAP_XY) ? (found->second.m_bounds.y0 + found->second.m_bounds.x1 - found->second.m_bounds.x0) : found->second.m_bounds.y1 }; + union_render_bounds(m_bounds, itembounds); + } } else if (!strcmp(itemnode->get_name(), "repeat")) { @@ -2576,12 +2633,10 @@ layout_element::texture &layout_element::texture::operator=(texture &&that) //------------------------------------------------- layout_element::component::component(environment &env, util::xml::data_node const &compnode, const char *dirname) - : m_state(0) + : m_state(env.get_attribute_int(compnode, "state", -1)) + , m_color(env.parse_color(compnode.get_child("color"))) { - // fetch common data - m_state = env.get_attribute_int(compnode, "state", -1); env.parse_bounds(compnode.get_child("bounds"), m_bounds); - env.parse_color(compnode.get_child("color"), m_color); } @@ -2904,7 +2959,7 @@ layout_view::layout_view( m_expbounds.x0 = m_expbounds.y0 = m_expbounds.x1 = m_expbounds.y1 = 0; environment local(env); local.set_parameter("viewname", std::string(m_name)); - add_items(local, viewnode, elemmap, groupmap, render_bounds{ 0.0f, 0.0f, 1.0f, 1.0f }, true, false, true); + add_items(local, viewnode, elemmap, groupmap, ROT0, identity_transform, render_color{ 1.0F, 1.0F, 1.0F, 1.0F }, true, false, true); recompute(render_layer_config()); for (group_map::value_type &group : groupmap) group.second.set_bounds_unresolved(); @@ -3065,7 +3120,9 @@ void layout_view::add_items( util::xml::data_node const &parentnode, element_map &elemmap, group_map &groupmap, - render_bounds const &transform, + int orientation, + layout_group::transform const &trans, + render_color const &color, bool root, bool repeat, bool init) @@ -3096,27 +3153,27 @@ void layout_view::add_items( } else if (!strcmp(itemnode->get_name(), "backdrop")) { - m_backdrop_list.emplace_back(env, *itemnode, elemmap, transform); + m_backdrop_list.emplace_back(env, *itemnode, elemmap, orientation, trans, color); } else if (!strcmp(itemnode->get_name(), "screen")) { - m_screen_list.emplace_back(env, *itemnode, elemmap, transform); + m_screen_list.emplace_back(env, *itemnode, elemmap, orientation, trans, color); } else if (!strcmp(itemnode->get_name(), "overlay")) { - m_overlay_list.emplace_back(env, *itemnode, elemmap, transform); + m_overlay_list.emplace_back(env, *itemnode, elemmap, orientation, trans, color); } else if (!strcmp(itemnode->get_name(), "bezel")) { - m_bezel_list.emplace_back(env, *itemnode, elemmap, transform); + m_bezel_list.emplace_back(env, *itemnode, elemmap, orientation, trans, color); } else if (!strcmp(itemnode->get_name(), "cpanel")) { - m_cpanel_list.emplace_back(env, *itemnode, elemmap, transform); + m_cpanel_list.emplace_back(env, *itemnode, elemmap, orientation, trans, color); } else if (!strcmp(itemnode->get_name(), "marquee")) { - m_marquee_list.emplace_back(env, *itemnode, elemmap, transform); + m_marquee_list.emplace_back(env, *itemnode, elemmap, orientation, trans, color); } else if (!strcmp(itemnode->get_name(), "group")) { @@ -3130,17 +3187,33 @@ void layout_view::add_items( unresolved = false; found->second.resolve_bounds(env, groupmap); - render_bounds grouptrans(transform); + layout_group::transform grouptrans(trans); util::xml::data_node const *const itemboundsnode(itemnode->get_child("bounds")); + util::xml::data_node const *const itemorientnode(itemnode->get_child("orientation")); + int const grouporient(env.parse_orientation(itemorientnode)); if (itemboundsnode) { render_bounds itembounds; env.parse_bounds(itemboundsnode, itembounds); - grouptrans = found->second.make_transform(itembounds, transform); + grouptrans = found->second.make_transform(grouporient, itembounds, trans); + } + else if (itemorientnode) + { + grouptrans = found->second.make_transform(grouporient, trans); } environment local(env); - add_items(local, found->second.get_groupnode(), elemmap, groupmap, grouptrans, false, false, true); + add_items( + local, + found->second.get_groupnode(), + elemmap, + groupmap, + orientation_add(grouporient, orientation), + grouptrans, + render_color_multiply(env.parse_color(itemnode->get_child("color")), color), + false, + false, + true); } else if (!strcmp(itemnode->get_name(), "repeat")) { @@ -3150,7 +3223,7 @@ void layout_view::add_items( environment local(env); for (int i = 0; count > i; ++i) { - add_items(local, *itemnode, elemmap, groupmap, transform, false, true, !i); + add_items(local, *itemnode, elemmap, groupmap, orientation, trans, color, false, true, !i); local.increment_parameters(); } } @@ -3200,7 +3273,9 @@ layout_view::item::item( environment &env, util::xml::data_node const &itemnode, element_map &elemmap, - render_bounds const &transform) + int orientation, + layout_group::transform const &trans, + render_color const &color) : m_element(nullptr) , m_output(env.device(), env.get_attribute_string(itemnode, "name", "")) , m_have_output(env.get_attribute_string(itemnode, "name", "")[0]) @@ -3208,7 +3283,8 @@ layout_view::item::item( , m_input_port(nullptr) , m_input_mask(0) , m_screen(nullptr) - , m_orientation(ROT0) + , m_orientation(orientation_add(env.parse_orientation(itemnode.get_child("orientation")), orientation)) + , m_color(render_color_multiply(env.parse_color(itemnode.get_child("color")), color)) { // find the associated element char const *const name(env.get_attribute_string(itemnode, "element", nullptr)); @@ -3234,9 +3310,11 @@ layout_view::item::item( if (m_have_output && m_element) m_output = m_element->default_state(); env.parse_bounds(itemnode.get_child("bounds"), m_rawbounds); - render_bounds_transform(m_rawbounds, transform); - env.parse_color(itemnode.get_child("color"), m_color); - env.parse_orientation(itemnode.get_child("orientation"), m_orientation); + render_bounds_transform(m_rawbounds, trans); + if (m_rawbounds.x0 > m_rawbounds.x1) + std::swap(m_rawbounds.x0, m_rawbounds.x1); + if (m_rawbounds.y0 > m_rawbounds.y1) + std::swap(m_rawbounds.y0, m_rawbounds.y1); // sanity checks if (strcmp(itemnode.get_name(), "screen") == 0) -- cgit v1.2.3