Created
July 8, 2025 21:10
-
-
Save kengoon/4df13ce22715c465a676abffd3042a48 to your computer and use it in GitHub Desktop.
Patch For Kivent cwidget.pyx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from kivy._event cimport EventDispatcher | |
| from kivy.factory import Factory | |
| from kivy.properties import (NumericProperty, StringProperty, AliasProperty, | |
| ReferenceListProperty, ObjectProperty, | |
| ListProperty, DictProperty, BooleanProperty) | |
| from kivy.graphics import (Canvas, Translate, Fbo, ClearColor, ClearBuffers, | |
| Scale) | |
| from kivy.base import EventLoop | |
| from kivy.lang import Builder | |
| from kivy.context import get_current_context | |
| from weakref import proxy | |
| from functools import partial | |
| from kivy.graphics.transformation import Matrix | |
| from kivy.eventmanager import ( | |
| MODE_DONT_DISPATCH, | |
| MODE_FILTERED_DISPATCH, | |
| MODE_DEFAULT_DISPATCH | |
| ) | |
| from kivy.weakproxy import WeakProxy | |
| from itertools import islice | |
| from kivy.uix.widget import (_widget_destructors, WidgetException, | |
| _widget_destructor, Widget, WidgetMetaclass) | |
| cdef class CWidget(EventDispatcher): | |
| '''Widget class. See module documentation for more information. | |
| :Events: | |
| `on_touch_down`: `(touch, )` | |
| Fired when a new touch event occurs. `touch` is the touch object. | |
| `on_touch_move`: `(touch, )` | |
| Fired when an existing touch moves. `touch` is the touch object. | |
| `on_touch_up`: `(touch, )` | |
| Fired when an existing touch disappears. `touch` is the touch | |
| object. | |
| `on_kv_post`: `(base_widget, )` | |
| Fired after all the kv rules associated with the widget | |
| and all other widgets that are in any of those rules have had | |
| all their kv rules applied. `base_widget` is the base-most widget | |
| whose instantiation triggered the kv rules (i.e. the widget | |
| instantiated from Python, e.g. ``MyWidget()``). | |
| .. versionchanged:: 3.0 | |
| .. warning:: | |
| Adding a `__del__` method to a class derived from Widget with Python | |
| prior to 3.4 will disable automatic garbage collection for instances | |
| of that class. This is because the Widget class creates reference | |
| cycles, thereby `preventing garbage collection | |
| <https://docs.python.org/2/library/gc.html#gc.garbage>`_. | |
| .. versionchanged:: 1.0.9 | |
| Everything related to event properties has been moved to the | |
| :class:`~kivy.event.EventDispatcher`. Event properties can now be used | |
| when contructing a simple class without subclassing :class:`Widget`. | |
| .. versionchanged:: 1.5.0 | |
| The constructor now accepts on_* arguments to automatically bind | |
| callbacks to properties or events, as in the Kv language. | |
| ''' | |
| '''Canvas of the widget. | |
| The canvas is a graphics object that contains all the drawing instructions | |
| for the graphical representation of the widget. | |
| There are no general properties for the Widget class, such as background | |
| color, to keep the design simple and lean. Some derived classes, such as | |
| Button, do add such convenience properties but generally the developer is | |
| responsible for implementing the graphics representation for a custom | |
| widget from the ground up. See the derived widget classes for patterns to | |
| follow and extend. | |
| See :class:`~kivy.graphics.Canvas` for more information about the usage. | |
| ''' | |
| __metaclass__ = WidgetMetaclass | |
| __events__ = ('on_touch_down', 'on_touch_move', 'on_touch_up', 'on_kv_post') | |
| property canvas: | |
| def __get__(self): | |
| return self.canvas | |
| def __set__(self, new_canvas): | |
| self.canvas = new_canvas | |
| property _proxy_ref: | |
| def __get__(self): | |
| return self._proxy_ref | |
| def __set__(self, value): | |
| self._proxy_ref = value | |
| property _context: | |
| def __get__(self): | |
| return self._context | |
| def __set__(self, value): | |
| self._context = value | |
| property _disabled_value: | |
| def __get__(self): | |
| return self._disabled_value | |
| def __set__(self, value): | |
| self._disabled_value = value | |
| property _disabled_count: | |
| def __get__(self): | |
| return self._disabled_count | |
| def __set__(self, value): | |
| self._disabled_count = value | |
| def __init__(self, **kwargs): | |
| # Before doing anything, ensure the windows exist. | |
| EventLoop.ensure_window() | |
| # Assign the default context of the widget creation. | |
| if self._context is None: | |
| self._context = get_current_context() | |
| no_builder = '__no_builder' in kwargs | |
| self._disabled_value = False | |
| if no_builder: | |
| del kwargs['__no_builder'] | |
| on_args = {k: v for k, v in kwargs.items() if k[:3] == 'on_'} | |
| for key in on_args: | |
| del kwargs[key] | |
| self._disabled_count = 0 | |
| super(CWidget, self).__init__(**kwargs) | |
| # Create the default canvas if it does not exist. | |
| if self.canvas is None: | |
| self.canvas = Canvas(opacity=self.opacity) | |
| # Apply all the styles. | |
| if not no_builder: | |
| rule_children = [] | |
| self.apply_class_lang_rules( | |
| ignored_consts=self._kwargs_applied_init, | |
| rule_children=rule_children) | |
| for widget in rule_children: | |
| widget.dispatch('on_kv_post', self) | |
| self.dispatch('on_kv_post', self) | |
| # Bind all the events. | |
| if on_args: | |
| self.bind(**on_args) | |
| @property | |
| def proxy_ref(self): | |
| '''Return a proxy reference to the widget, i.e. without creating a | |
| reference to the widget. See `weakref.proxy | |
| <http://docs.python.org/2/library/weakref.html?highlight\ | |
| =proxy#weakref.proxy>`_ for more information. | |
| .. versionadded:: 1.7.2 | |
| ''' | |
| _proxy_ref = self._proxy_ref | |
| if _proxy_ref is not None: | |
| return _proxy_ref | |
| f = partial(_widget_destructor, self.uid) | |
| self._proxy_ref = _proxy_ref = WeakProxy(self, f) | |
| # Only f should be enough here, but it appears that is a very | |
| # specific case, the proxy destructor is not called if both f and | |
| # _proxy_ref are not together in a tuple. | |
| _widget_destructors[self.uid] = (f, _proxy_ref) | |
| return _proxy_ref | |
| def __richcmp__(self, other, op): | |
| if not isinstance(other, Widget): | |
| return False | |
| return self.proxy_ref is other.proxy_ref | |
| def __hash__(self): | |
| return id(self) | |
| def apply_class_lang_rules( | |
| self, root=None, ignored_consts=set(), rule_children=None): | |
| ''' | |
| Method that is called by kivy to apply the kv rules of this widget's | |
| class. | |
| :Parameters: | |
| `root`: :class:`Widget` | |
| The root widget that instantiated this widget in kv, if the | |
| widget was instantiated in kv, otherwise ``None``. | |
| `ignored_consts`: set | |
| (internal) See :meth:`~kivy.lang.builder.BuilderBase.apply`. | |
| `rule_children`: list | |
| (internal) See :meth:`~kivy.lang.builder.BuilderBase.apply`. | |
| This is useful to be able to execute code before/after the class kv | |
| rules are applied to the widget. E.g. if the kv code requires some | |
| properties to be initialized before it is used in a binding rule. | |
| If overwriting remember to call ``super``, otherwise the kv rules will | |
| not be applied. | |
| In the following example, | |
| .. code-block:: python | |
| class MyWidget(Widget): | |
| pass | |
| class OtherWidget(MyWidget): | |
| pass | |
| .. code-block:: kv | |
| <MyWidget>: | |
| my_prop: some_value | |
| <OtherWidget>: | |
| other_prop: some_value | |
| When ``OtherWidget`` is instantiated with ``OtherWidget()``, the | |
| widget's :meth:`apply_class_lang_rules` is called and it applies the | |
| kv rules of this class - ``<MyWidget>`` and ``<OtherWidget>``. | |
| Similarly, when the widget is instantiated from kv, e.g. | |
| .. code-block:: kv | |
| <MyBox@BoxLayout>: | |
| height: 55 | |
| OtherWidget: | |
| width: 124 | |
| ``OtherWidget``'s :meth:`apply_class_lang_rules` is called and it | |
| applies the kv rules of this class - ``<MyWidget>`` and | |
| ``<OtherWidget>``. | |
| .. note:: | |
| It applies only the class rules not the instance rules. I.e. in the | |
| above kv example in the ``MyBox`` rule when ``OtherWidget`` is | |
| instantiated, its :meth:`apply_class_lang_rules` applies the | |
| ``<MyWidget>`` and ``<OtherWidget>`` rules to it - it does not | |
| apply the ``width: 124`` rule. The ``width: 124`` rule is part of | |
| the ``MyBox`` rule and is applied by the ``MyBox``'s instance's | |
| :meth:`apply_class_lang_rules`. | |
| .. versionchanged:: 1.11.0 | |
| ''' | |
| Builder.apply( | |
| self, ignored_consts=ignored_consts, | |
| rule_children=rule_children) | |
| # | |
| # Collision | |
| # | |
| def collide_point(self, x, y): | |
| ''' | |
| Check if a point (x, y) is inside the widget's axis aligned bounding | |
| box. | |
| :Parameters: | |
| `x`: numeric | |
| x position of the point (in parent coordinates) | |
| `y`: numeric | |
| y position of the point (in parent coordinates) | |
| :Returns: | |
| A bool. True if the point is inside the bounding box, False | |
| otherwise. | |
| .. code-block:: python | |
| >>> Widget(pos=(10, 10), size=(50, 50)).collide_point(40, 40) | |
| True | |
| ''' | |
| return self.x <= x <= self.right and self.y <= y <= self.top | |
| def collide_widget(self, wid): | |
| ''' | |
| Check if another widget collides with this widget. This function | |
| performs an axis-aligned bounding box intersection test by default. | |
| :Parameters: | |
| `wid`: :class:`Widget` class | |
| Widget to test collision with. | |
| :Returns: | |
| bool. True if the other widget collides with this widget, False | |
| otherwise. | |
| .. code-block:: python | |
| >>> wid = Widget(size=(50, 50)) | |
| >>> wid2 = Widget(size=(50, 50), pos=(25, 25)) | |
| >>> wid.collide_widget(wid2) | |
| True | |
| >>> wid2.pos = (55, 55) | |
| >>> wid.collide_widget(wid2) | |
| False | |
| ''' | |
| if self.right < wid.x: | |
| return False | |
| if self.x > wid.right: | |
| return False | |
| if self.top < wid.y: | |
| return False | |
| if self.y > wid.top: | |
| return False | |
| return True | |
| def on_motion(self, etype, me): | |
| '''Called when a motion event is received. | |
| :Parameters: | |
| `etype`: `str` | |
| Event type, one of "begin", "update" or "end" | |
| `me`: :class:`~kivy.input.motionevent.MotionEvent` | |
| Received motion event | |
| :Returns: `bool` | |
| `True` to stop event dispatching | |
| .. versionadded:: 2.1.0 | |
| .. warning:: | |
| This is an experimental method and it remains so while this warning | |
| is present. | |
| ''' | |
| if self.disabled or me.dispatch_mode == MODE_DONT_DISPATCH: | |
| return | |
| if me.type_id not in self.motion_filter: | |
| return | |
| filtered = self.motion_filter[me.type_id] | |
| if filtered[0] is self and len(filtered) == 1: | |
| return | |
| if me.dispatch_mode == MODE_DEFAULT_DISPATCH: | |
| last_filtered = filtered[-1] | |
| for widget in self.children[:]: | |
| if widget.dispatch('on_motion', etype, me): | |
| return True | |
| if widget is last_filtered: | |
| return | |
| if me.dispatch_mode == MODE_FILTERED_DISPATCH: | |
| widgets = filtered[1:] if filtered[0] is self else filtered[:] | |
| for widget in widgets: | |
| if widget.dispatch('on_motion', etype, me): | |
| return True | |
| # | |
| # Default event handlers | |
| # | |
| def on_touch_down(self, touch): | |
| '''Receive a touch down event. | |
| :Parameters: | |
| `touch`: :class:`~kivy.input.motionevent.MotionEvent` class | |
| Touch received. The touch is in parent coordinates. See | |
| :mod:`~kivy.uix.relativelayout` for a discussion on | |
| coordinate systems. | |
| :Returns: bool | |
| If True, the dispatching of the touch event will stop. | |
| If False, the event will continue to be dispatched to the rest | |
| of the widget tree. | |
| ''' | |
| if self.disabled and self.collide_point(*touch.pos): | |
| return True | |
| for child in self.children[:]: | |
| if child.dispatch('on_touch_down', touch): | |
| return True | |
| def on_touch_move(self, touch): | |
| '''Receive a touch move event. The touch is in parent coordinates. | |
| See :meth:`on_touch_down` for more information. | |
| ''' | |
| if self.disabled: | |
| return | |
| for child in self.children[:]: | |
| if child.dispatch('on_touch_move', touch): | |
| return True | |
| def on_touch_up(self, touch): | |
| '''Receive a touch up event. The touch is in parent coordinates. | |
| See :meth:`on_touch_down` for more information. | |
| ''' | |
| if self.disabled: | |
| return | |
| for child in self.children[:]: | |
| if child.dispatch('on_touch_up', touch): | |
| return True | |
| def on_kv_post(self, base_widget): | |
| pass | |
| # | |
| # Tree management | |
| # | |
| def add_widget(self, widget, index=0, canvas=None): | |
| '''Add a new widget as a child of this widget. | |
| :Parameters: | |
| `widget`: :class:`Widget` | |
| Widget to add to our list of children. | |
| `index`: int, defaults to 0 | |
| Index to insert the widget in the list. Notice that the default | |
| of 0 means the widget is inserted at the beginning of the list | |
| and will thus be drawn on top of other sibling widgets. For a | |
| full discussion of the index and widget hierarchy, please see | |
| the :doc:`Widgets Programming Guide <guide/widgets>`. | |
| .. versionadded:: 1.0.5 | |
| `canvas`: str, defaults to None | |
| Canvas to add widget's canvas to. Can be 'before', 'after' or | |
| None for the default canvas. | |
| .. versionadded:: 1.9.0 | |
| .. code-block:: python | |
| >>> from kivy.uix.button import Button | |
| >>> from kivy.uix.slider import Slider | |
| >>> root = Widget() | |
| >>> root.add_widget(Button()) | |
| >>> slider = Slider() | |
| >>> root.add_widget(slider) | |
| ''' | |
| if not (isinstance(widget, Widget) or isinstance(widget, CWidget)): | |
| raise WidgetException( | |
| 'add_widget() can be used only with instances' | |
| ' of the Widget class.') | |
| widget = widget.__self__ | |
| if widget is self: | |
| raise WidgetException( | |
| 'Widget instances cannot be added to themselves.') | |
| parent = widget.parent | |
| # Check if the widget is already a child of another widget. | |
| if parent: | |
| raise WidgetException('Cannot add %r, it already has a parent %r' | |
| % (widget, parent)) | |
| widget.parent = parent = self | |
| # Child will be disabled if added to a disabled parent. | |
| widget.inc_disabled(self._disabled_count) | |
| canvas = self.canvas.before if canvas == 'before' else \ | |
| self.canvas.after if canvas == 'after' else self.canvas | |
| if index == 0 or len(self.children) == 0: | |
| self.children.insert(0, widget) | |
| canvas.add(widget.canvas) | |
| else: | |
| canvas = self.canvas | |
| children = self.children | |
| if index >= len(children): | |
| index = len(children) | |
| next_index = canvas.indexof(children[-1].canvas) | |
| else: | |
| next_child = children[index] | |
| next_index = canvas.indexof(next_child.canvas) | |
| if next_index == -1: | |
| next_index = canvas.length() | |
| else: | |
| next_index += 1 | |
| children.insert(index, widget) | |
| # We never want to insert widget _before_ canvas.before. | |
| if next_index == 0 and canvas.has_before: | |
| next_index = 1 | |
| canvas.insert(next_index, widget.canvas) | |
| for type_id in widget.motion_filter: | |
| self.register_for_motion_event(type_id, widget) | |
| widget.fbind('motion_filter', self._update_motion_filter) | |
| def remove_widget(self, widget): | |
| '''Remove a widget from the children of this widget. | |
| :Parameters: | |
| `widget`: :class:`Widget` | |
| Widget to remove from our children list. | |
| .. code-block:: python | |
| >>> from kivy.uix.button import Button | |
| >>> root = Widget() | |
| >>> button = Button() | |
| >>> root.add_widget(button) | |
| >>> root.remove_widget(button) | |
| ''' | |
| if widget not in self.children: | |
| return | |
| self.children.remove(widget) | |
| if widget.canvas in self.canvas.children: | |
| self.canvas.remove(widget.canvas) | |
| elif widget.canvas in self.canvas.after.children: | |
| self.canvas.after.remove(widget.canvas) | |
| elif widget.canvas in self.canvas.before.children: | |
| self.canvas.before.remove(widget.canvas) | |
| for type_id in widget.motion_filter: | |
| self.unregister_for_motion_event(type_id, widget) | |
| widget.funbind('motion_filter', self._update_motion_filter) | |
| widget.parent = None | |
| widget.dec_disabled(self._disabled_count) | |
| def clear_widgets(self, children=None): | |
| ''' | |
| Remove all (or the specified) :attr:`~Widget.children` of this widget. | |
| If the 'children' argument is specified, it should be a list (or | |
| filtered list) of children of the current widget. | |
| .. versionchanged:: 1.8.0 | |
| The `children` argument can be used to specify the children you | |
| want to remove. | |
| .. versionchanged:: 2.1.0 | |
| Specifying an empty ``children`` list leaves the widgets unchanged. | |
| Previously it was treated like ``None`` and all children were | |
| removed. | |
| ''' | |
| if children is None or children is self.children: | |
| children = self.children[:] | |
| remove_widget = self.remove_widget | |
| for child in children: | |
| remove_widget(child) | |
| def _update_motion_filter(self, child_widget, child_motion_filter): | |
| old_events = [] | |
| for type_id, widgets in self.motion_filter.items(): | |
| if child_widget in widgets: | |
| old_events.append(type_id) | |
| for type_id in old_events: | |
| if type_id not in child_motion_filter: | |
| self.unregister_for_motion_event(type_id, child_widget) | |
| for type_id in child_motion_filter: | |
| if type_id not in old_events: | |
| self.register_for_motion_event(type_id, child_widget) | |
| def _find_index_in_motion_filter(self, type_id, widget): | |
| if widget is self: | |
| return 0 | |
| find_index = self.children.index | |
| max_index = find_index(widget) + 1 | |
| motion_widgets = self.motion_filter[type_id] | |
| insert_index = 1 if motion_widgets[0] is self else 0 | |
| for index in range(insert_index, len(motion_widgets)): | |
| if find_index(motion_widgets[index]) < max_index: | |
| insert_index += 1 | |
| else: | |
| break | |
| return insert_index | |
| def register_for_motion_event(self, type_id, widget=None): | |
| '''Register to receive motion events of `type_id`. | |
| Override :meth:`on_motion` or bind to `on_motion` event to handle | |
| the incoming motion events. | |
| :Parameters: | |
| `type_id`: `str` | |
| Motion event type id (eg. "touch", "hover", etc.) | |
| `widget`: `Widget` | |
| Child widget or `self` if omitted | |
| .. versionadded:: 2.1.0 | |
| .. note:: | |
| Method can be called multiple times with the same arguments. | |
| .. warning:: | |
| This is an experimental method and it remains so while this warning | |
| is present. | |
| ''' | |
| a_widget = widget or self | |
| motion_filter = self.motion_filter | |
| if type_id not in motion_filter: | |
| motion_filter[type_id] = [a_widget] | |
| elif widget not in motion_filter[type_id]: | |
| index = self._find_index_in_motion_filter(type_id, a_widget) | |
| motion_filter[type_id].insert(index, a_widget) | |
| def unregister_for_motion_event(self, type_id, widget=None): | |
| '''Unregister to receive motion events of `type_id`. | |
| :Parameters: | |
| `type_id`: `str` | |
| Motion event type id (eg. "touch", "hover", etc.) | |
| `widget`: `Widget` | |
| Child widget or `self` if omitted | |
| .. versionadded:: 2.1.0 | |
| .. note:: | |
| Method can be called multiple times with the same arguments. | |
| .. warning:: | |
| This is an experimental method and it remains so while this warning | |
| is present. | |
| ''' | |
| a_widget = widget or self | |
| motion_filter = self.motion_filter | |
| if type_id in motion_filter: | |
| if a_widget in motion_filter[type_id]: | |
| motion_filter[type_id].remove(a_widget) | |
| if not motion_filter[type_id]: | |
| del motion_filter[type_id] | |
| def export_to_png(self, filename, *args, **kwargs): | |
| '''Saves an image of the widget and its children in png format at the | |
| specified filename. Works by removing the widget canvas from its | |
| parent, rendering to an :class:`~kivy.graphics.fbo.Fbo`, and calling | |
| :meth:`~kivy.graphics.texture.Texture.save`. | |
| .. note:: | |
| The image includes only this widget and its children. If you want | |
| to include widgets elsewhere in the tree, you must call | |
| :meth:`~Widget.export_to_png` from their common parent, or use | |
| :meth:`~kivy.core.window.WindowBase.screenshot` to capture the | |
| whole window. | |
| .. note:: | |
| The image will be saved in png format, you should include the | |
| extension in your filename. | |
| .. versionadded:: 1.9.0 | |
| :Parameters: | |
| `filename`: str | |
| The filename with which to save the png. | |
| `scale`: float | |
| The amount by which to scale the saved image, defaults to 1. | |
| .. versionadded:: 1.11.0 | |
| ''' | |
| self.export_as_image(*args, **kwargs).save(filename, flipped=False) | |
| def export_as_image(self, *args, **kwargs): | |
| '''Return an core :class:`~kivy.core.image.Image` of the actual | |
| widget. | |
| .. versionadded:: 1.11.0 | |
| ''' | |
| from kivy.core.image import Image | |
| scale = kwargs.get('scale', 1) | |
| if self.parent is not None: | |
| canvas_parent_index = self.parent.canvas.indexof(self.canvas) | |
| if canvas_parent_index > -1: | |
| self.parent.canvas.remove(self.canvas) | |
| fbo = Fbo(size=(self.width * scale, self.height * scale), | |
| with_stencilbuffer=True) | |
| with fbo: | |
| ClearColor(0, 0, 0, 0) | |
| ClearBuffers() | |
| Scale(1, -1, 1) | |
| Scale(scale, scale, 1) | |
| Translate(-self.x, -self.y - self.height, 0) | |
| fbo.add(self.canvas) | |
| fbo.draw() | |
| img = Image(fbo.texture) | |
| fbo.remove(self.canvas) | |
| if self.parent is not None and canvas_parent_index > -1: | |
| self.parent.canvas.insert(canvas_parent_index, self.canvas) | |
| return img | |
| def get_root_window(self): | |
| '''Return the root window. | |
| :Returns: | |
| Instance of the root window. Can be a | |
| :class:`~kivy.core.window.WindowBase` or | |
| :class:`Widget`. | |
| ''' | |
| if self.parent: | |
| return self.parent.get_root_window() | |
| def get_parent_window(self): | |
| '''Return the parent window. | |
| :Returns: | |
| Instance of the parent window. Can be a | |
| :class:`~kivy.core.window.WindowBase` or | |
| :class:`Widget`. | |
| ''' | |
| if self.parent: | |
| return self.parent.get_parent_window() | |
| def _walk(self, restrict=False, loopback=False, index=None): | |
| # We pass index only when we are going on the parent | |
| # so don't yield the parent as well. | |
| if index is None: | |
| index = len(self.children) | |
| yield self | |
| for child in reversed(self.children[:index]): | |
| for walk_child in child._walk(restrict=True): | |
| yield walk_child | |
| # If we want to continue with our parent, just do it. | |
| if not restrict: | |
| parent = self.parent | |
| try: | |
| if parent is None or not isinstance(parent, Widget): | |
| raise ValueError | |
| index = parent.children.index(self) | |
| except ValueError: | |
| # Self is root, if we want to loopback from the first element: | |
| if not loopback: | |
| return | |
| # If we started with root (i.e. index==None), then we have to | |
| # start from root again, so we return self again. Otherwise, we | |
| # never returned it, so return it now starting with it. | |
| parent = self | |
| index = None | |
| for walk_child in parent._walk(loopback=loopback, index=index): | |
| yield walk_child | |
| def walk(self, restrict=False, loopback=False): | |
| ''' Iterator that walks the widget tree starting with this widget and | |
| goes forward returning widgets in the order in which layouts display | |
| them. | |
| :Parameters: | |
| `restrict`: bool, defaults to False | |
| If True, it will only iterate through the widget and its | |
| children (or children of its children etc.). Defaults to False. | |
| `loopback`: bool, defaults to False | |
| If True, when the last widget in the tree is reached, | |
| it'll loop back to the uppermost root and start walking until | |
| we hit this widget again. Naturally, it can only loop back when | |
| `restrict` is False. Defaults to False. | |
| :return: | |
| A generator that walks the tree, returning widgets in the | |
| forward layout order. | |
| For example, given a tree with the following structure: | |
| .. code-block:: kv | |
| GridLayout: | |
| Button | |
| BoxLayout: | |
| id: box | |
| Widget | |
| Button | |
| Widget | |
| walking this tree: | |
| .. code-block:: python | |
| >>> # Call walk on box with loopback True, and restrict False | |
| >>> [type(widget) for widget in box.walk(loopback=True)] | |
| [<class 'BoxLayout'>, <class 'Widget'>, <class 'Button'>, | |
| <class 'Widget'>, <class 'GridLayout'>, <class 'Button'>] | |
| >>> # Now with loopback False, and restrict False | |
| >>> [type(widget) for widget in box.walk()] | |
| [<class 'BoxLayout'>, <class 'Widget'>, <class 'Button'>, | |
| <class 'Widget'>] | |
| >>> # Now with restrict True | |
| >>> [type(widget) for widget in box.walk(restrict=True)] | |
| [<class 'BoxLayout'>, <class 'Widget'>, <class 'Button'>] | |
| .. versionadded:: 1.9.0 | |
| ''' | |
| gen = self._walk(restrict, loopback) | |
| yield next(gen) | |
| for node in gen: | |
| if node is self: | |
| return | |
| yield node | |
| def _walk_reverse(self, loopback=False, go_up=False): | |
| # process is walk up level, walk down its children tree, then walk up | |
| # next level etc. | |
| # default just walk down the children tree | |
| root = self | |
| index = 0 | |
| # we need to go up a level before walking tree | |
| if go_up: | |
| root = self.parent | |
| try: | |
| if root is None or not isinstance(root, Widget): | |
| raise ValueError | |
| index = root.children.index(self) + 1 | |
| except ValueError: | |
| if not loopback: | |
| return | |
| index = 0 | |
| go_up = False | |
| root = self | |
| # now walk children tree starting with last-most child | |
| for child in islice(root.children, index, None): | |
| for walk_child in child._walk_reverse(loopback=loopback): | |
| yield walk_child | |
| # we need to return ourself last, in all cases | |
| yield root | |
| # if going up, continue walking up the parent tree | |
| if go_up: | |
| for walk_child in root._walk_reverse(loopback=loopback, | |
| go_up=go_up): | |
| yield walk_child | |
| def walk_reverse(self, loopback=False): | |
| ''' Iterator that walks the widget tree backwards starting with the | |
| widget before this, and going backwards returning widgets in the | |
| reverse order in which layouts display them. | |
| This walks in the opposite direction of :meth:`walk`, so a list of the | |
| tree generated with :meth:`walk` will be in reverse order compared | |
| to the list generated with this, provided `loopback` is True. | |
| :Parameters: | |
| `loopback`: bool, defaults to False | |
| If True, when the uppermost root in the tree is | |
| reached, it'll loop back to the last widget and start walking | |
| back until after we hit widget again. Defaults to False. | |
| :return: | |
| A generator that walks the tree, returning widgets in the | |
| reverse layout order. | |
| For example, given a tree with the following structure: | |
| .. code-block:: kv | |
| GridLayout: | |
| Button | |
| BoxLayout: | |
| id: box | |
| Widget | |
| Button | |
| Widget | |
| walking this tree: | |
| .. code-block:: python | |
| >>> # Call walk on box with loopback True | |
| >>> [type(widget) for widget in box.walk_reverse(loopback=True)] | |
| [<class 'Button'>, <class 'GridLayout'>, <class 'Widget'>, | |
| <class 'Button'>, <class 'Widget'>, <class 'BoxLayout'>] | |
| >>> # Now with loopback False | |
| >>> [type(widget) for widget in box.walk_reverse()] | |
| [<class 'Button'>, <class 'GridLayout'>] | |
| >>> forward = [w for w in box.walk(loopback=True)] | |
| >>> backward = [w for w in box.walk_reverse(loopback=True)] | |
| >>> forward == backward[::-1] | |
| True | |
| .. versionadded:: 1.9.0 | |
| ''' | |
| for node in self._walk_reverse(loopback=loopback, go_up=True): | |
| yield node | |
| if node is self: | |
| return | |
| def to_widget(self, x, y, relative=False): | |
| '''Convert the coordinate from window to local (current widget) | |
| coordinates. | |
| See :mod:`~kivy.uix.relativelayout` for details on the coordinate | |
| systems. | |
| ''' | |
| if self.parent: | |
| x, y = self.parent.to_widget(x, y) | |
| return self.to_local(x, y, relative=relative) | |
| def to_window(self, x, y, initial=True, relative=False): | |
| """If ``initial`` is True, the default, it transforms **parent** | |
| coordinates to window coordinates. Otherwise, it transforms **local** | |
| (current widget) coordinates to window coordinates. | |
| See :mod:`~kivy.uix.relativelayout` for details on the coordinate | |
| systems. | |
| """ | |
| if not initial: | |
| x, y = self.to_parent(x, y, relative=relative) | |
| if self.parent: | |
| return self.parent.to_window(x, y, initial=False, | |
| relative=relative) | |
| return (x, y) | |
| def to_parent(self, x, y, relative=False): | |
| """Transform local (current widget) coordinates to parent coordinates. | |
| See :mod:`~kivy.uix.relativelayout` for details on the coordinate | |
| systems. | |
| :Parameters: | |
| `relative`: bool, defaults to False | |
| Change to True if you want to translate relative positions from | |
| a widget to its parent coordinates. | |
| """ | |
| if relative: | |
| return (x + self.x, y + self.y) | |
| return (x, y) | |
| def to_local(self, x, y, relative=False): | |
| """Transform parent coordinates to local (current widget) coordinates. | |
| See :mod:`~kivy.uix.relativelayout` for details on the coordinate | |
| systems. | |
| :Parameters: | |
| `relative`: bool, defaults to False | |
| Change to True if you want to translate coordinates to | |
| relative widget coordinates. | |
| """ | |
| if relative: | |
| return (x - self.x, y - self.y) | |
| return (x, y) | |
| def _apply_transform(self, m, pos=None): | |
| if self.parent: | |
| x, y = self.parent.to_widget(relative=True, | |
| *self.to_window(*(pos or self.pos))) | |
| m.translate(x, y, 0) | |
| m = self.parent._apply_transform(m) if self.parent else m | |
| return m | |
| def get_window_matrix(self, x=0, y=0): | |
| '''Calculate the transformation matrix to convert between window and | |
| widget coordinates. | |
| :Parameters: | |
| `x`: float, defaults to 0 | |
| Translates the matrix on the x axis. | |
| `y`: float, defaults to 0 | |
| Translates the matrix on the y axis. | |
| ''' | |
| m = Matrix() | |
| m.translate(x, y, 0) | |
| m = self._apply_transform(m) | |
| return m | |
| x = NumericProperty(0) | |
| '''X position of the widget. | |
| :attr:`x` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. | |
| ''' | |
| y = NumericProperty(0) | |
| '''Y position of the widget. | |
| :attr:`y` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. | |
| ''' | |
| width = NumericProperty(100) | |
| '''Width of the widget. | |
| :attr:`width` is a :class:`~kivy.properties.NumericProperty` and defaults | |
| to 100. | |
| .. warning:: | |
| Keep in mind that the `width` property is subject to layout logic and | |
| that this has not yet happened at the time of the widget's `__init__` | |
| method. | |
| .. warning:: | |
| A negative width is not supported. | |
| ''' | |
| height = NumericProperty(100) | |
| '''Height of the widget. | |
| :attr:`height` is a :class:`~kivy.properties.NumericProperty` and defaults | |
| to 100. | |
| .. warning:: | |
| Keep in mind that the `height` property is subject to layout logic and | |
| that this has not yet happened at the time of the widget's `__init__` | |
| method. | |
| .. warning:: | |
| A negative height is not supported. | |
| ''' | |
| pos = ReferenceListProperty(x, y) | |
| '''Position of the widget. | |
| :attr:`pos` is a :class:`~kivy.properties.ReferenceListProperty` of | |
| (:attr:`x`, :attr:`y`) properties. | |
| ''' | |
| size = ReferenceListProperty(width, height) | |
| '''Size of the widget. | |
| :attr:`size` is a :class:`~kivy.properties.ReferenceListProperty` of | |
| (:attr:`width`, :attr:`height`) properties. | |
| ''' | |
| def get_right(self): | |
| return self.x + self.width | |
| def set_right(self, value): | |
| self.x = value - self.width | |
| right = AliasProperty(get_right, set_right, | |
| bind=('x', 'width'), | |
| cache=True, watch_before_use=False) | |
| '''Right position of the widget. | |
| :attr:`right` is an :class:`~kivy.properties.AliasProperty` of | |
| (:attr:`x` + :attr:`width`). | |
| ''' | |
| def get_top(self): | |
| return self.y + self.height | |
| def set_top(self, value): | |
| self.y = value - self.height | |
| top = AliasProperty(get_top, set_top, | |
| bind=('y', 'height'), | |
| cache=True, watch_before_use=False) | |
| '''Top position of the widget. | |
| :attr:`top` is an :class:`~kivy.properties.AliasProperty` of | |
| (:attr:`y` + :attr:`height`). | |
| ''' | |
| def get_center_x(self): | |
| return self.x + self.width / 2. | |
| def set_center_x(self, value): | |
| self.x = value - self.width / 2. | |
| center_x = AliasProperty(get_center_x, set_center_x, | |
| bind=('x', 'width'), | |
| cache=True, watch_before_use=False) | |
| '''X center position of the widget. | |
| :attr:`center_x` is an :class:`~kivy.properties.AliasProperty` of | |
| (:attr:`x` + :attr:`width` / 2.). | |
| ''' | |
| def get_center_y(self): | |
| return self.y + self.height / 2. | |
| def set_center_y(self, value): | |
| self.y = value - self.height / 2. | |
| center_y = AliasProperty(get_center_y, set_center_y, | |
| bind=('y', 'height'), | |
| cache=True, watch_before_use=False) | |
| '''Y center position of the widget. | |
| :attr:`center_y` is an :class:`~kivy.properties.AliasProperty` of | |
| (:attr:`y` + :attr:`height` / 2.). | |
| ''' | |
| center = ReferenceListProperty(center_x, center_y) | |
| '''Center position of the widget. | |
| :attr:`center` is a :class:`~kivy.properties.ReferenceListProperty` of | |
| (:attr:`center_x`, :attr:`center_y`) properties. | |
| ''' | |
| cls = ListProperty([]) | |
| '''Class of the widget, used for styling. | |
| ''' | |
| children = ListProperty([]) | |
| '''List of children of this widget. | |
| :attr:`children` is a :class:`~kivy.properties.ListProperty` and | |
| defaults to an empty list. | |
| Use :meth:`add_widget` and :meth:`remove_widget` for manipulating the | |
| children list. Don't manipulate the children list directly unless you know | |
| what you are doing. | |
| ''' | |
| parent = ObjectProperty(None, allownone=True, rebind=True) | |
| '''Parent of this widget. The parent of a widget is set when the widget | |
| is added to another widget and unset when the widget is removed from its | |
| parent. | |
| :attr:`parent` is an :class:`~kivy.properties.ObjectProperty` and | |
| defaults to None. | |
| ''' | |
| size_hint_x = NumericProperty(1, allownone=True) | |
| '''x size hint. Represents how much space the widget should use in the | |
| direction of the x axis relative to its parent's width. | |
| Only the :class:`~kivy.uix.layout.Layout` and | |
| :class:`~kivy.core.window.Window` classes make use of the hint. | |
| The size_hint is used by layouts for two purposes: | |
| - When the layout considers widgets on their own rather than in | |
| relation to its other children, the size_hint_x is a direct proportion | |
| of the parent width, normally between 0.0 and 1.0. For instance, a | |
| widget with ``size_hint_x=0.5`` in | |
| a vertical BoxLayout will take up half the BoxLayout's width, or | |
| a widget in a FloatLayout with ``size_hint_x=0.2`` will take up 20% | |
| of the FloatLayout width. If the size_hint is greater than 1, the | |
| widget will be wider than the parent. | |
| - When multiple widgets can share a row of a layout, such as in a | |
| horizontal BoxLayout, their widths will be their size_hint_x as a | |
| fraction of the sum of widget size_hints. For instance, if the | |
| size_hint_xs are (0.5, 1.0, 0.5), the first widget will have a | |
| width of 25% of the parent width. | |
| :attr:`size_hint_x` is a :class:`~kivy.properties.NumericProperty` and | |
| defaults to 1. | |
| ''' | |
| size_hint_y = NumericProperty(1, allownone=True) | |
| '''y size hint. | |
| :attr:`size_hint_y` is a :class:`~kivy.properties.NumericProperty` and | |
| defaults to 1. | |
| See :attr:`size_hint_x` for more information, but with widths and heights | |
| swapped. | |
| ''' | |
| size_hint = ReferenceListProperty(size_hint_x, size_hint_y) | |
| '''Size hint. | |
| :attr:`size_hint` is a :class:`~kivy.properties.ReferenceListProperty` of | |
| (:attr:`size_hint_x`, :attr:`size_hint_y`) properties. | |
| See :attr:`size_hint_x` for more information. | |
| ''' | |
| pos_hint = ObjectProperty({}) | |
| '''Position hint. This property allows you to set the position of | |
| the widget inside its parent layout (similar to | |
| size_hint). | |
| For example, if you want to set the top of the widget to be at 90% | |
| height of its parent layout, you can write:: | |
| widget = Widget(pos_hint={'top': 0.9}) | |
| The keys 'x', 'right' and 'center_x' will use the parent width. | |
| The keys 'y', 'top' and 'center_y' will use the parent height. | |
| See :doc:`api-kivy.uix.floatlayout` for further reference. | |
| .. note:: | |
| :attr:`pos_hint` is not used by all layouts. Check the documentation | |
| of the layout in question to see if it supports pos_hint. | |
| :attr:`pos_hint` is an :class:`~kivy.properties.ObjectProperty` | |
| containing a dict. | |
| ''' | |
| size_hint_min_x = NumericProperty(None, allownone=True) | |
| '''When not None, the x-direction minimum size (in pixels, | |
| like :attr:`width`) when :attr:`size_hint_x` is also not None. | |
| When :attr:`size_hint_x` is not None, it is the minimum width that the | |
| widget will be set due to the :attr:`size_hint_x`. I.e. when a smaller size | |
| would be set, :attr:`size_hint_min_x` is the value used instead for the | |
| widget width. When None, or when :attr:`size_hint_x` is None, | |
| :attr:`size_hint_min_x` doesn't do anything. | |
| Only the :class:`~kivy.uix.layout.Layout` and | |
| :class:`~kivy.core.window.Window` classes make use of the hint. | |
| :attr:`size_hint_min_x` is a :class:`~kivy.properties.NumericProperty` and | |
| defaults to None. | |
| .. versionadded:: 1.10.0 | |
| ''' | |
| size_hint_min_y = NumericProperty(None, allownone=True) | |
| '''When not None, the y-direction minimum size (in pixels, | |
| like :attr:`height`) when :attr:`size_hint_y` is also not None. | |
| When :attr:`size_hint_y` is not None, it is the minimum height that the | |
| widget will be set due to the :attr:`size_hint_y`. I.e. when a smaller size | |
| would be set, :attr:`size_hint_min_y` is the value used instead for the | |
| widget height. When None, or when :attr:`size_hint_y` is None, | |
| :attr:`size_hint_min_y` doesn't do anything. | |
| Only the :class:`~kivy.uix.layout.Layout` and | |
| :class:`~kivy.core.window.Window` classes make use of the hint. | |
| :attr:`size_hint_min_y` is a :class:`~kivy.properties.NumericProperty` and | |
| defaults to None. | |
| .. versionadded:: 1.10.0 | |
| ''' | |
| size_hint_min = ReferenceListProperty(size_hint_min_x, size_hint_min_y) | |
| '''Minimum size when using :attr:`size_hint`. | |
| :attr:`size_hint_min` is a :class:`~kivy.properties.ReferenceListProperty` | |
| of (:attr:`size_hint_min_x`, :attr:`size_hint_min_y`) properties. | |
| .. versionadded:: 1.10.0 | |
| ''' | |
| size_hint_max_x = NumericProperty(None, allownone=True) | |
| '''When not None, the x-direction maximum size (in pixels, | |
| like :attr:`width`) when :attr:`size_hint_x` is also not None. | |
| Similar to :attr:`size_hint_min_x`, except that it sets the maximum width. | |
| :attr:`size_hint_max_x` is a :class:`~kivy.properties.NumericProperty` and | |
| defaults to None. | |
| .. versionadded:: 1.10.0 | |
| ''' | |
| size_hint_max_y = NumericProperty(None, allownone=True) | |
| '''When not None, the y-direction maximum size (in pixels, | |
| like :attr:`height`) when :attr:`size_hint_y` is also not None. | |
| Similar to :attr:`size_hint_min_y`, except that it sets the maximum height. | |
| :attr:`size_hint_max_y` is a :class:`~kivy.properties.NumericProperty` and | |
| defaults to None. | |
| .. versionadded:: 1.10.0 | |
| ''' | |
| size_hint_max = ReferenceListProperty(size_hint_max_x, size_hint_max_y) | |
| '''Maximum size when using :attr:`size_hint`. | |
| :attr:`size_hint_max` is a :class:`~kivy.properties.ReferenceListProperty` | |
| of (:attr:`size_hint_max_x`, :attr:`size_hint_max_y`) properties. | |
| .. versionadded:: 1.10.0 | |
| ''' | |
| ids = DictProperty({}) | |
| '''This is a dictionary of ids defined in your kv language. This will only | |
| be populated if you use ids in your kv language code. | |
| .. versionadded:: 1.7.0 | |
| :attr:`ids` is a :class:`~kivy.properties.DictProperty` and defaults to an | |
| empty dict {}. | |
| The :attr:`ids` are populated for each root level widget definition. For | |
| example: | |
| .. code-block:: kv | |
| # in kv | |
| <MyWidget@Widget>: | |
| id: my_widget | |
| Label: | |
| id: label_widget | |
| Widget: | |
| id: inner_widget | |
| Label: | |
| id: inner_label | |
| TextInput: | |
| id: text_input | |
| OtherWidget: | |
| id: other_widget | |
| <OtherWidget@Widget> | |
| id: other_widget | |
| Label: | |
| id: other_label | |
| TextInput: | |
| id: other_textinput | |
| Then, in python: | |
| .. code-block:: python | |
| >>> widget = MyWidget() | |
| >>> print(widget.ids) | |
| {'other_widget': <weakproxy at 041CFED0 to OtherWidget at 041BEC38>, | |
| 'inner_widget': <weakproxy at 04137EA0 to Widget at 04138228>, | |
| 'inner_label': <weakproxy at 04143540 to Label at 04138260>, | |
| 'label_widget': <weakproxy at 04137B70 to Label at 040F97A0>, | |
| 'text_input': <weakproxy at 041BB5D0 to TextInput at 041BEC00>} | |
| >>> print(widget.ids['other_widget'].ids) | |
| {'other_textinput': <weakproxy at 041DBB40 to TextInput at 041BEF48>, | |
| 'other_label': <weakproxy at 041DB570 to Label at 041BEEA0>} | |
| >>> print(widget.ids['label_widget'].ids) | |
| {} | |
| ''' | |
| opacity = NumericProperty(1.0) | |
| '''Opacity of the widget and all its children. | |
| .. versionadded:: 1.4.1 | |
| The opacity attribute controls the opacity of the widget and its children. | |
| Be careful, it's a cumulative attribute: the value is multiplied by the | |
| current global opacity and the result is applied to the current context | |
| color. | |
| For example, if the parent has an opacity of 0.5 and a child has an | |
| opacity of 0.2, the real opacity of the child will be 0.5 * 0.2 = 0.1. | |
| Then, the opacity is applied by the shader as: | |
| .. code-block:: python | |
| frag_color = color * vec4(1.0, 1.0, 1.0, opacity); | |
| :attr:`opacity` is a :class:`~kivy.properties.NumericProperty` and defaults | |
| to 1.0. | |
| ''' | |
| def on_opacity(self, instance, value): | |
| canvas = self.canvas | |
| if canvas is not None: | |
| canvas.opacity = value | |
| # canvas = None | |
| # '''Canvas of the widget. | |
| # | |
| # The canvas is a graphics object that contains all the drawing instructions | |
| # for the graphical representation of the widget. | |
| # | |
| # There are no general properties for the Widget class, such as background | |
| # color, to keep the design simple and lean. Some derived classes, such as | |
| # Button, do add such convenience properties but generally the developer is | |
| # responsible for implementing the graphics representation for a custom | |
| # widget from the ground up. See the derived widget classes for patterns to | |
| # follow and extend. | |
| # | |
| # See :class:`~kivy.graphics.Canvas` for more information about the usage. | |
| # ''' | |
| def get_disabled(self): | |
| return self._disabled_count > 0 | |
| def set_disabled(self, value): | |
| # Necessary to ensure a change between value of equal truthiness | |
| # doesn't mess up the count | |
| value = bool(value) | |
| if value != self._disabled_value: | |
| self._disabled_value = value | |
| if value: | |
| self.inc_disabled() | |
| else: | |
| self.dec_disabled() | |
| return True | |
| def inc_disabled(self, count=1): | |
| self._disabled_count += count | |
| if self._disabled_count - count < 1 <= self._disabled_count: | |
| self.property('disabled').dispatch(self) | |
| for c in self.children: | |
| c.inc_disabled(count) | |
| def dec_disabled(self, count=1): | |
| self._disabled_count -= count | |
| if self._disabled_count <= 0 < self._disabled_count + count: | |
| self.property('disabled').dispatch(self) | |
| for c in self.children: | |
| c.dec_disabled(count) | |
| disabled = AliasProperty(get_disabled, set_disabled, watch_before_use=False) | |
| '''Indicates whether this widget can interact with input or not. | |
| :attr:`disabled` is an :class:`~kivy.properties.AliasProperty` and | |
| defaults to False. | |
| .. note:: | |
| 1. Child Widgets, when added to a disabled widget, will be disabled | |
| automatically. | |
| 2. Disabling/enabling a parent disables/enables all | |
| of its children. | |
| .. versionadded:: 1.8.0 | |
| .. versionchanged:: 1.10.1 | |
| :attr:`disabled` was changed from a | |
| :class:`~kivy.properties.BooleanProperty` to an | |
| :class:`~kivy.properties.AliasProperty` to allow access to its | |
| previous state when a parent's disabled state is changed. | |
| ''' | |
| motion_filter = DictProperty() | |
| '''Holds a dict of `type_id` to `list` of child widgets registered to | |
| receive motion events of `type_id`. | |
| Don't change the property directly but use | |
| :meth:`register_for_motion_event` and :meth:`unregister_for_motion_event` | |
| to register and unregister for motion events. If `self` is registered it | |
| will always be the first element in the list. | |
| .. versionadded:: 2.1.0 | |
| .. warning:: | |
| This is an experimental property and it remains so while this warning | |
| is present. | |
| ''' | |
| Factory.register('CWidget', cls=CWidget) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment