summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2018-08-02 23:11:17 +1000
committer Vas Crabb <vas@vastheman.com>2018-08-02 23:11:17 +1000
commit6ea9ff90423416d630472298a14afc5526104e75 (patch)
treee9e8e7d7b86f96d3d20d3b635a5cf99bc75f04a8
parent153fa4c4f88bfc3bc6abd48700d9481b1d90321c (diff)
(nw) Further layout work:
* Allow <orientation> and <color> 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
-rw-r--r--docs/source/techspecs/layout_files.rst191
-rw-r--r--scripts/build/complay.py31
-rw-r--r--src/emu/render.h15
-rw-r--r--src/emu/rendlay.cpp268
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::
</element>
+.. _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::
+
+ <view name="Control panel">
+
+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::
+
+ <view name="LED Displays, Terminal and Keypad">
+ <cpanel element="beige"><bounds x="320" y="0" width="172" height="372" /></cpanel>
+ <group ref="displays"><bounds x="0" y="0" width="320" height="132" /></group>
+ <group ref="keypad"><bounds x="336" y="16" width="140" height="260" /></group>
+ <screen index="0"><bounds x="0" y="132" width="320" height="240" /></screen>
+ </view>
+
+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::
+
+ <cpanel element="btn_3" inputtag="X2" inputmask="0x10">
+ <bounds x="2.30" y="4.325" width="1.0" height="1.0" />
+ </cpanel>
+ <cpanel element="btn_0" inputtag="X0" inputmask="0x20">
+ <bounds x="0.725" y="5.375" width="1.0" height="1.0" /></cpanel>
+ <cpanel element="btn_rst" inputtag="RESET" inputmask="0x01">
+ <bounds x="1.775" y="5.375" width="1.0" height="1.0" />
+ </cpanel>
+
+
+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::
+
+ <cpanel name="digit6" element="digit"><bounds x="16" y="16" width="48" height="80" /></cpanel>
+ <cpanel name="digit5" element="digit"><bounds x="64" y="16" width="48" height="80" /></cpanel>
+ <cpanel name="digit4" element="digit"><bounds x="112" y="16" width="48" height="80" /></cpanel>
+ <cpanel name="digit3" element="digit"><bounds x="160" y="16" width="48" height="80" /></cpanel>
+ <cpanel name="digit2" element="digit"><bounds x="208" y="16" width="48" height="80" /></cpanel>
+ <cpanel name="digit1" element="digit"><bounds x="256" y="16" width="48" height="80" /></cpanel>
+
+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 <math.h>
+#include <array>
#include <map>
#include <memory>
#include <mutex>
@@ -759,14 +760,16 @@ class layout_group
public:
using environment = emu::render::detail::layout_environment;
using group_map = std::unordered_map<std::string, layout_group>;
+ using transform = std::array<std::array<float, 3>, 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)