325 lines
11 KiB
Python
325 lines
11 KiB
Python
|
'''
|
||
|
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 behavior/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
|
||
|
|
||
|
|
||
|
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)
|
||
|
|
||
|
def do_layout(self, *args):
|
||
|
super(RelativeLayout, self).do_layout(pos=(0, 0))
|
||
|
|
||
|
def to_parent(self, x, y, **k):
|
||
|
return (x + self.x, y + self.y)
|
||
|
|
||
|
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))
|
||
|
|
||
|
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)
|
||
|
|
||
|
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
|
||
|
|
||
|
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
|
||
|
|
||
|
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
|