'''
Relative Layout
===============
.. versionadded:: 1.4.0
This layout allows you to set relative coordinates for children. If you want
absolute positioning, use the :class:`~kivy.uix.floatlayout.FloatLayout`.
The :class:`RelativeLayout` class behaves just like the regular
:class:`FloatLayout` except that its child widgets are positioned relative to
the layout.
When a widget with position = (0,0) is added to a RelativeLayout,
the child widget will also move when the position of the RelativeLayout
is changed. The child widgets coordinates remain (0,0) as they are
always relative to the parent layout.
Coordinate Systems
------------------
Window coordinates
~~~~~~~~~~~~~~~~~~
By default, there's only one coordinate system that defines the position of
widgets and touch events dispatched to them: the window coordinate system,
which places (0, 0) at the bottom left corner of the window.
Although there are other coordinate systems defined, e.g. local
and parent coordinates, these coordinate systems are identical to the window
coordinate system as long as a relative layout type widget is not in the
widget's parent stack. When widget.pos is read or a touch is received,
the coordinate values are in parent coordinates. But as mentioned, these are
identical to window coordinates, even in complex widget stacks as long as
there's no relative layout type widget in the widget's parent stack.
For example:
.. code-block:: kv
BoxLayout:
Label:
text: 'Left'
Button:
text: 'Middle'
on_touch_down: print('Middle: {}'.format(args[1].pos))
BoxLayout:
on_touch_down: print('Box: {}'.format(args[1].pos))
Button:
text: 'Right'
on_touch_down: print('Right: {}'.format(args[1].pos))
When the middle button is clicked and the touch propagates through the
different parent coordinate systems, it prints the following::
>>> Box: (430.0, 282.0)
>>> Right: (430.0, 282.0)
>>> Middle: (430.0, 282.0)
As claimed, the touch has identical coordinates to the window coordinates
in every coordinate system. :meth:`~kivy.uix.widget.Widget.collide_point`
for example, takes the point in window coordinates.
Parent coordinates
~~~~~~~~~~~~~~~~~~
Other :class:`RelativeLayout` type widgets are
:class:`~kivy.uix.scatter.Scatter`,
:class:`~kivy.uix.scatterlayout.ScatterLayout`,
and :class:`~kivy.uix.scrollview.ScrollView`. If such a special widget is in
the parent stack, only then does the parent and local coordinate system
diverge from the window coordinate system. For each such widget in the stack,
a coordinate system with (0, 0) of that coordinate system being at the bottom
left corner of that widget is created. **Position and touch coordinates
received and read by a widget are in the coordinate system of the most
recent special widget in its parent stack (not including itself) or in window
coordinates if there are none** (as in the first example). We call these
coordinates parent coordinates.
For example:
.. code-block:: kv
BoxLayout:
Label:
text: 'Left'
Button:
text: 'Middle'
on_touch_down: print('Middle: {}'.format(args[1].pos))
RelativeLayout:
on_touch_down: print('Relative: {}'.format(args[1].pos))
Button:
text: 'Right'
on_touch_down: print('Right: {}'.format(args[1].pos))
Clicking on the middle button prints::
>>> Relative: (396.0, 298.0)
>>> Right: (-137.33, 298.0)
>>> Middle: (396.0, 298.0)
As the touch propagates through the widgets, for each widget, the
touch is received in parent coordinates. Because both the relative and middle
widgets don't have these special widgets in their parent stack, the touch is
the same as window coordinates. Only the right widget, which has a
RelativeLayout in its parent stack, receives the touch in coordinates relative
to that RelativeLayout which is different than window coordinates.
Local and Widget coordinates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When expressed in parent coordinates, the position is expressed in the
coordinates of the most recent special widget in its parent stack, not
including itself. When expressed in local or widget coordinates, the widgets
themselves are also included.
Changing the above example to transform the parent coordinates into local
coordinates:
.. code-block:: kv
BoxLayout:
Label:
text: 'Left'
Button:
text: 'Middle'
on_touch_down: print('Middle: {}'.format(\
self.to_local(*args[1].pos)))
RelativeLayout:
on_touch_down: print('Relative: {}'.format(\
self.to_local(*args[1].pos)))
Button:
text: 'Right'
on_touch_down: print('Right: {}'.format(\
self.to_local(*args[1].pos)))
Now, clicking on the middle button prints::
>>> Relative: (-135.33, 301.0)
>>> Right: (-135.33, 301.0)
>>> Middle: (398.0, 301.0)
This is because now the relative widget also expresses the coordinates
relative to itself.
.. note::
Although all widgets including :class:`RelativeLayout` receive their touch
events in ``on_touch_xxx`` in parent coordinates, these special widgets
will transform the touch position to be in local coordinates before it
calls ``super``. This may only be noticeable in a complex inheritance
class.
Coordinate transformations
~~~~~~~~~~~~~~~~~~~~~~~~~~
:class:`~kivy.uix.widget.Widget` provides 4 functions to transform coordinates
between the various coordinate systems. For now, we assume that the `relative`
keyword of these functions is `False`.
:meth:`~kivy.uix.widget.Widget.to_widget` takes the coordinates expressed in
window coordinates and returns them in local (widget) coordinates.
:meth:`~kivy.uix.widget.Widget.to_window` takes the coordinates expressed in
local coordinates and returns them in window coordinates.
:meth:`~kivy.uix.widget.Widget.to_parent` takes the coordinates expressed in
local coordinates and returns them in parent coordinates.
:meth:`~kivy.uix.widget.Widget.to_local` takes the coordinates expressed in
parent coordinates and returns them in local coordinates.
Each of the 4 transformation functions take a `relative` parameter. When the
relative parameter is True, the coordinates are returned or originate in
true relative coordinates - relative to a coordinate system with its (0, 0) at
the bottom left corner of the widget in question.
.. _kivy-uix-relativelayout-common-pitfalls:
Common Pitfalls
---------------
As all positions within a :class:`RelativeLayout` are relative to the position
of the layout itself, the position of the layout should never be used in
determining the position of sub-widgets or the layout's :attr:`canvas`.
Take the following kv code for example:
.. container:: align-right
.. figure:: images/relativelayout-fixedposition.png
:scale: 50%
expected result
.. figure:: images/relativelayout-doubleposition.png
:scale: 50%
actual result
.. code-block:: kv
FloatLayout:
Widget:
size_hint: None, None
size: 200, 200
pos: 200, 200
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
RelativeLayout:
size_hint: None, None
size: 200, 200
pos: 200, 200
canvas:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
pos: self.pos # incorrect
size: self.size
You might expect this to render a single pink rectangle; however, the content
of the :class:`RelativeLayout` is already transformed, so the use of
`pos: self.pos` will double that transformation. In this case, using
`pos: 0, 0` or omitting `pos` completely will provide the expected result.
This also applies to the position of sub-widgets. Instead of positioning a
:class:`~kivy.uix.widget.Widget` based on the layout's own position:
.. code-block:: kv
RelativeLayout:
Widget:
pos: self.parent.pos
Widget:
center: self.parent.center
use the :attr:`pos_hint` property:
.. code-block:: kv
RelativeLayout:
Widget:
pos_hint: {'x': 0, 'y': 0}
Widget:
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
.. versionchanged:: 1.7.0
Prior to version 1.7.0, the :class:`RelativeLayout` was implemented as a
:class:`~kivy.uix.floatlayout.FloatLayout` inside a
:class:`~kivy.uix.scatter.Scatter`. This behaviour/widget has
been renamed to `ScatterLayout`. The :class:`RelativeLayout` now only
supports relative positions (and can't be rotated, scaled or translated on
a multitouch system using two or more fingers). This was done so that the
implementation could be optimized and avoid the heavier calculations of
:class:`Scatter` (e.g. inverse matrix, recalculating multiple properties
etc.)
'''
__all__ = ('RelativeLayout', )
from kivy.uix.floatlayout import FloatLayout
[docs]class RelativeLayout(FloatLayout):
'''RelativeLayout class, see module documentation for more information.
'''
def __init__(self, **kw):
super(RelativeLayout, self).__init__(**kw)
funbind = self.funbind
trigger = self._trigger_layout
funbind('pos', trigger)
funbind('pos_hint', trigger)
[docs] def do_layout(self, *args):
super(RelativeLayout, self).do_layout(pos=(0, 0))
[docs] def to_parent(self, x, y, **k):
return (x + self.x, y + self.y)
[docs] def to_local(self, x, y, **k):
return (x - self.x, y - self.y)
def _apply_transform(self, m, pos=None):
m.translate(self.x, self.y, 0)
return super(RelativeLayout, self)._apply_transform(m, (0, 0))
[docs] def on_motion(self, etype, me):
if me.type_id in self.motion_filter and 'pos' in me.profile:
me.push()
me.apply_transform_2d(self.to_local)
ret = super().on_motion(etype, me)
me.pop()
return ret
return super().on_motion(etype, me)
[docs] def on_touch_down(self, touch):
x, y = touch.x, touch.y
touch.push()
touch.apply_transform_2d(self.to_local)
ret = super(RelativeLayout, self).on_touch_down(touch)
touch.pop()
return ret
[docs] def on_touch_move(self, touch):
x, y = touch.x, touch.y
touch.push()
touch.apply_transform_2d(self.to_local)
ret = super(RelativeLayout, self).on_touch_move(touch)
touch.pop()
return ret
[docs] def on_touch_up(self, touch):
x, y = touch.x, touch.y
touch.push()
touch.apply_transform_2d(self.to_local)
ret = super(RelativeLayout, self).on_touch_up(touch)
touch.pop()
return ret