(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
This commit is contained in:
Vas Crabb 2018-08-02 23:11:17 +10:00
parent 153fa4c4f8
commit 6ea9ff9042
4 changed files with 401 additions and 98 deletions

View File

@ -601,6 +601,181 @@ An example element for a button that gives visual feedback when clicked::
</element> </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: .. _layout-parts-repeats:
Repeating blocks 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. 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: .. _layout-autogen:
Automatically-generated views Automatically-generated views

View File

@ -98,6 +98,8 @@ class LayoutChecker(Minifyer):
FLOATCHARS = re.compile('^.*[.eE].*$') FLOATCHARS = re.compile('^.*[.eE].*$')
SHAPES = frozenset(('disk', 'dotmatrix', 'dotmatrix5dot', 'dotmatrixdot', 'led14seg', 'led14segsc', 'led16seg', 'led16segsc', 'led7seg', 'led8seg_gts1', 'rect')) SHAPES = frozenset(('disk', 'dotmatrix', 'dotmatrix5dot', 'dotmatrixdot', 'led14seg', 'led14segsc', 'led16seg', 'led16segsc', 'led7seg', 'led8seg_gts1', 'rect'))
OBJECTS = frozenset(('backdrop', 'bezel', 'cpanel', 'marquee', 'overlay')) OBJECTS = frozenset(('backdrop', 'bezel', 'cpanel', 'marquee', 'overlay'))
ORIENTATIONS = frozenset((0, 90, 180, 270))
YESNO = frozenset(("yes", "no"))
def __init__(self, output, **kwargs): def __init__(self, output, **kwargs):
super(LayoutChecker, self).__init__(output=output, **kwargs) super(LayoutChecker, self).__init__(output=output, **kwargs)
@ -246,6 +248,17 @@ class LayoutChecker(Minifyer):
if has_ltrb and has_origin_size: if has_ltrb and has_origin_size:
self.handleError('Element bounds has both left/top/right/bottom and origin/size attributes') 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): def checkColorChannel(self, attrs, name):
channel = self.checkFloatAttribute('color', attrs, name, None) channel = self.checkFloatAttribute('color', attrs, name, None)
if (channel is not None) and ((0.0 > channel) or (1.0 < channel)): 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: for group in self.referenced_groups:
if (group not in self.groups) and (not self.VARPATTERN.match(group)): 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])) 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() self.handlers.pop()
def elementStartHandler(self, name, attrs): def elementStartHandler(self, name, attrs):
@ -437,11 +452,16 @@ class LayoutChecker(Minifyer):
self.referenced_elements[attrs['element']] = self.formatLocation() self.referenced_elements[attrs['element']] = self.formatLocation()
if 'inputtag' in attrs: if 'inputtag' in attrs:
if 'inputmask' not 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.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.handlers.append((self.objectStartHandler, self.objectEndHandler))
self.have_bounds.append(False) self.have_bounds.append(False)
self.have_orientation.append(False)
elif 'screen' == name: elif 'screen' == name:
if 'index' in attrs: if 'index' in attrs:
index = self.checkIntAttribute(name, attrs, 'index', None) 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.handleError('Element screen attribute tag "%s" contains invalid characters' % (tag, ))
self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.handlers.append((self.objectStartHandler, self.objectEndHandler))
self.have_bounds.append(False) self.have_bounds.append(False)
self.have_orientation.append(False)
elif 'group' == name: elif 'group' == name:
if 'ref' not in attrs: if 'ref' not in attrs:
self.handleError('Element group missing attribute ref') self.handleError('Element group missing attribute ref')
@ -463,6 +484,7 @@ class LayoutChecker(Minifyer):
self.referenced_groups[attrs['ref']] = self.formatLocation() self.referenced_groups[attrs['ref']] = self.formatLocation()
self.handlers.append((self.objectStartHandler, self.objectEndHandler)) self.handlers.append((self.objectStartHandler, self.objectEndHandler))
self.have_bounds.append(False) self.have_bounds.append(False)
self.have_orientation.append(False)
elif 'repeat' == name: elif 'repeat' == name:
if 'count' not in attrs: if 'count' not in attrs:
self.handleError('Element repeat missing attribute count') self.handleError('Element repeat missing attribute count')
@ -496,10 +518,13 @@ class LayoutChecker(Minifyer):
def objectStartHandler(self, name, attrs): def objectStartHandler(self, name, attrs):
if 'bounds' == name: if 'bounds' == name:
self.checkBounds(attrs) self.checkBounds(attrs)
elif 'orientation' == name:
self.checkOrientation(attrs)
self.ignored_depth = 1 self.ignored_depth = 1
def objectEndHandler(self, name): def objectEndHandler(self, name):
self.have_bounds.pop() self.have_bounds.pop()
self.have_orientation.pop()
self.handlers.pop() self.handlers.pop()
def setDocumentLocator(self, locator): def setDocumentLocator(self, locator):
@ -512,6 +537,7 @@ class LayoutChecker(Minifyer):
self.variable_scopes = [ ] self.variable_scopes = [ ]
self.repeat_depth = [ ] self.repeat_depth = [ ]
self.have_bounds = [ ] self.have_bounds = [ ]
self.have_orientation = [ ]
self.have_color = [ ] self.have_color = [ ]
self.generated_element_names = False self.generated_element_names = False
self.generated_group_names = False self.generated_group_names = False
@ -529,6 +555,7 @@ class LayoutChecker(Minifyer):
del self.variable_scopes del self.variable_scopes
del self.repeat_depth del self.repeat_depth
del self.have_bounds del self.have_bounds
del self.have_orientation
del self.have_color del self.have_color
del self.generated_element_names del self.generated_element_names
del self.generated_group_names del self.generated_group_names

View File

@ -49,6 +49,7 @@
#include "screen.h" #include "screen.h"
#include <math.h> #include <math.h>
#include <array>
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -759,14 +760,16 @@ class layout_group
public: public:
using environment = emu::render::detail::layout_environment; using environment = emu::render::detail::layout_environment;
using group_map = std::unordered_map<std::string, layout_group>; 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 &groupnode);
~layout_group(); ~layout_group();
util::xml::data_node const &get_groupnode() const { return m_groupnode; } util::xml::data_node const &get_groupnode() const { return m_groupnode; }
render_bounds make_transform(render_bounds const &dest) const; transform make_transform(int orientation, render_bounds const &dest) const;
render_bounds make_transform(render_bounds const &dest, render_bounds const &transform) 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 set_bounds_unresolved();
void resolve_bounds(environment &env, group_map &groupmap); void resolve_bounds(environment &env, group_map &groupmap);
@ -818,7 +821,9 @@ public:
environment &env, environment &env,
util::xml::data_node const &itemnode, util::xml::data_node const &itemnode,
element_map &elemmap, element_map &elemmap,
render_bounds const &transform); int orientation,
layout_group::transform const &trans,
render_color const &color);
~item(); ~item();
// getters // getters
@ -886,7 +891,9 @@ private:
util::xml::data_node const &parentnode, util::xml::data_node const &parentnode,
element_map &elemmap, element_map &elemmap,
group_map &groupmap, group_map &groupmap,
render_bounds const &transform, int orientation,
layout_group::transform const &trans,
render_color const &color,
bool root, bool root,
bool repeat, bool repeat,
bool init); bool init);

View File

@ -69,23 +69,26 @@ enum
std::locale const f_portable_locale("C"); 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 // INLINE HELPERS
//************************************************************************** //**************************************************************************
//------------------------------------------------- inline void render_bounds_transform(render_bounds &bounds, layout_group::transform const &trans)
// render_bounds_transform - apply translation/
// scaling
//-------------------------------------------------
inline void render_bounds_transform(render_bounds &bounds, render_bounds const &transform)
{ {
bounds.x0 = (bounds.x0 * transform.x1) + transform.x0; bounds = render_bounds{
bounds.y0 = (bounds.y0 * transform.y1) + transform.y0; (bounds.x0 * trans[0][0]) + (bounds.y0 * trans[0][1]) + trans[0][2],
bounds.x1 = (bounds.x1 * transform.x1) + transform.x0; (bounds.x0 * trans[1][0]) + (bounds.y0 * trans[1][1]) + trans[1][2],
bounds.y1 = (bounds.y1 * transform.y1) + transform.y0; (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] };
}
constexpr render_color render_color_multiply(render_color const &x, render_color const &y)
{
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) if (!node)
{ return render_color{ 1.0F, 1.0F, 1.0F, 1.0F };
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);
// check for errors // parse attributes
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 }))) render_color const result{
throw layout_syntax_error(util::string_format("illegal RGBA color %f,%f,%f,%f", result.r, result.g, result.b, result.a)); 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 // default to no transform
if (!node) if (!node)
return ROT0;
// parse attributes
int result;
int const rotate(get_attribute_int(*node, "rotate", 0));
switch (rotate)
{ {
result = ROT0; case 0: result = ROT0; break;
} case 90: result = ROT90; break;
else case 180: result = ROT180; break;
{ case 270: result = ROT270; break;
// parse attributes default: throw layout_syntax_error(util::string_format("invalid rotate attribute %d", rotate));
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;
} }
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 // 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); assert(m_bounds_resolved);
return render_bounds{ // make orientation matrix
dest.x0 - (m_bounds.x0 * (dest.x1 - dest.x0) / (m_bounds.x1 - m_bounds.x0)), transform result{{ {{ 1.0F, 0.0F, 0.0F }}, {{ 0.0F, 1.0F, 0.0F }}, {{ 0.0F, 0.0F, 1.0F }} }};
dest.y0 - (m_bounds.y0 * (dest.y1 - dest.y0) / (m_bounds.y1 - m_bounds.y0)), if (orientation & ORIENTATION_SWAP_XY)
(dest.x1 - dest.x0) / (m_bounds.x1 - m_bounds.x0), {
(dest.y1 - dest.y0) / (m_bounds.y1 - m_bounds.y0) }; 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)); assert(m_bounds_resolved);
return render_bounds{
(transform.x0 * next.x1) + next.x0, render_bounds const dest{
(transform.y0 * next.y1) + next.y0, m_bounds.x0,
transform.x1 * next.x1, m_bounds.y0,
transform.y1 * next.y1 }; (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! // a wild loop appears!
std::ostringstream path; std::ostringstream path;
for (layout_group const *const group : seen) for (layout_group const *const group : seen)
path << ' ' << group->m_groupnode.get_name(); path << ' ' << group->m_groupnode.get_attribute_string("name", nullptr);
path << ' ' << m_groupnode.get_name(); path << ' ' << m_groupnode.get_attribute_string("name", nullptr);
throw layout_syntax_error(util::string_format("recursively nested groups %s", path.str())); 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); environment local(env);
resolve_bounds(local, m_groupnode, groupmap, seen, false, true); resolve_bounds(local, m_groupnode, groupmap, seen, false, true);
} }
seen.pop_back();
} }
void layout_group::resolve_bounds( void layout_group::resolve_bounds(
@ -1071,17 +1112,33 @@ void layout_group::resolve_bounds(
} }
else if (!strcmp(itemnode->get_name(), "group")) else if (!strcmp(itemnode->get_name(), "group"))
{ {
char const *ref(env.get_attribute_string(*itemnode, "ref", nullptr)); util::xml::data_node const *const itemboundsnode(itemnode->get_child("bounds"));
if (!ref) if (itemboundsnode)
throw layout_syntax_error("nested group must have ref attribute"); {
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)); group_map::iterator const found(groupmap.find(ref));
if (groupmap.end() == found) if (groupmap.end() == found)
throw layout_syntax_error(util::string_format("unable to find group %s", ref)); throw layout_syntax_error(util::string_format("unable to find group %s", ref));
environment local(env); int const orientation(env.parse_orientation(itemnode->get_child("orientation")));
found->second.resolve_bounds(local, groupmap, seen); environment local(env);
union_render_bounds(m_bounds, found->second.m_bounds); 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")) 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) 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_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; m_expbounds.x0 = m_expbounds.y0 = m_expbounds.x1 = m_expbounds.y1 = 0;
environment local(env); environment local(env);
local.set_parameter("viewname", std::string(m_name)); 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()); recompute(render_layer_config());
for (group_map::value_type &group : groupmap) for (group_map::value_type &group : groupmap)
group.second.set_bounds_unresolved(); group.second.set_bounds_unresolved();
@ -3065,7 +3120,9 @@ void layout_view::add_items(
util::xml::data_node const &parentnode, util::xml::data_node const &parentnode,
element_map &elemmap, element_map &elemmap,
group_map &groupmap, group_map &groupmap,
render_bounds const &transform, int orientation,
layout_group::transform const &trans,
render_color const &color,
bool root, bool root,
bool repeat, bool repeat,
bool init) bool init)
@ -3096,27 +3153,27 @@ void layout_view::add_items(
} }
else if (!strcmp(itemnode->get_name(), "backdrop")) 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")) 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")) 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")) 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")) 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")) 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")) else if (!strcmp(itemnode->get_name(), "group"))
{ {
@ -3130,17 +3187,33 @@ void layout_view::add_items(
unresolved = false; unresolved = false;
found->second.resolve_bounds(env, groupmap); 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 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) if (itemboundsnode)
{ {
render_bounds itembounds; render_bounds itembounds;
env.parse_bounds(itemboundsnode, 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); 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")) else if (!strcmp(itemnode->get_name(), "repeat"))
{ {
@ -3150,7 +3223,7 @@ void layout_view::add_items(
environment local(env); environment local(env);
for (int i = 0; count > i; ++i) 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(); local.increment_parameters();
} }
} }
@ -3200,7 +3273,9 @@ layout_view::item::item(
environment &env, environment &env,
util::xml::data_node const &itemnode, util::xml::data_node const &itemnode,
element_map &elemmap, element_map &elemmap,
render_bounds const &transform) int orientation,
layout_group::transform const &trans,
render_color const &color)
: m_element(nullptr) : m_element(nullptr)
, m_output(env.device(), env.get_attribute_string(itemnode, "name", "")) , m_output(env.device(), env.get_attribute_string(itemnode, "name", ""))
, m_have_output(env.get_attribute_string(itemnode, "name", "")[0]) , m_have_output(env.get_attribute_string(itemnode, "name", "")[0])
@ -3208,7 +3283,8 @@ layout_view::item::item(
, m_input_port(nullptr) , m_input_port(nullptr)
, m_input_mask(0) , m_input_mask(0)
, m_screen(nullptr) , 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 // find the associated element
char const *const name(env.get_attribute_string(itemnode, "element", nullptr)); 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) if (m_have_output && m_element)
m_output = m_element->default_state(); m_output = m_element->default_state();
env.parse_bounds(itemnode.get_child("bounds"), m_rawbounds); env.parse_bounds(itemnode.get_child("bounds"), m_rawbounds);
render_bounds_transform(m_rawbounds, transform); render_bounds_transform(m_rawbounds, trans);
env.parse_color(itemnode.get_child("color"), m_color); if (m_rawbounds.x0 > m_rawbounds.x1)
env.parse_orientation(itemnode.get_child("orientation"), m_orientation); 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 // sanity checks
if (strcmp(itemnode.get_name(), "screen") == 0) if (strcmp(itemnode.get_name(), "screen") == 0)