mirror of
https://github.com/holub/mame
synced 2025-10-04 08:28:39 +03:00
(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:
parent
153fa4c4f8
commit
6ea9ff9042
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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, render_bounds const &transform)
|
||||
inline void render_bounds_transform(render_bounds &bounds, layout_group::transform const &trans)
|
||||
{
|
||||
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;
|
||||
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] };
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
return ROT0;
|
||||
|
||||
// parse attributes
|
||||
int result;
|
||||
int const rotate(get_attribute_int(*node, "rotate", 0));
|
||||
switch (rotate)
|
||||
{
|
||||
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;
|
||||
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");
|
||||
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));
|
||||
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);
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user